Back to home page

EIC code displayed by LXR

 
 

    


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

0001 #pragma once
0002 /**
0003 stree.h : minimal representation of the structural geometry tree
0004 =====================================================================
0005 
0006 This is exploring a minimal approach to geometry translation
0007 
0008 * u4/U4Tree.h populates stree.h from traversals of Geant4 volumes.
0009 * stree.h replaces lots of GGeo code, notably: GInstancer.cc GNode.cc
0010 
0011 * mat/sur/bnd materials/surfaces/boundaries are handled by sstandard
0012 
0013 Example of stree.h population
0014 ------------------------------
0015 
0016 ~/o/u4/tests/U4TreeCreateTest.sh
0017 
0018 
0019 Lifecycle
0020 ------------
0021 
0022 * Canonical stree instance is SSim member instanciated by SSim::SSim
0023 * stree is populated by U4Tree::Create
0024 
0025 
0026 SSim+stree vs CSGFoundry
0027 --------------------------
0028 
0029 Some duplication between these is inevitable, however they have
0030 different objectives:
0031 
0032 * *SSim+stree* aims to collect and persist all needed info from Geant4
0033 * *CSGFoundry* aims to prepare the subset that needs to be uploaded to GPU
0034 
0035   * narrowing to float is something that could be done when going from stree->CSGFoundry
0036 
0037 
0038 Users of stree.h
0039 -------------------
0040 
0041 u4/U4Tree.h
0042     heavy lifting of populating stree.h
0043 
0044 CSG_GGeo/CSG_GGeo_Convert.cc
0045     stree.h/tree member obtained from "SSim::Get()->get_tree()"
0046     tree used from CSG_GGeo_Convert::addInstances to stree::lookup_sensor_identifier
0047     the sensor_id and sensor_index are incorporated into the CSGFoundry instances
0048     (so this usage is "precache")
0049 
0050     NB : THIS IS UNHOLY MIX OF OLD AND NEW : NOW  REPLACED
0051 
0052 ggeo/tests/GGeoLoadFromDirTest.cc
0053     dev of the interim stree GGeo integration for sensor info
0054 
0055     NOW REPLACED
0056 
0057 ggeo/GGeo.cc
0058     GGeo:m_tree with setTree/getTree : but treated as foreign member, only GGeo::save saves it
0059     this m_tree was used for debugging a now resolved discrepancy between X4+GGeo and U4Test
0060     transforms : that is suspected but not confirmed to have been caused by a
0061     stree parent pointer bug
0062 
0063     NOW REPLACED
0064 
0065 extg4/X4PhysicalVolume.cc
0066     X4PhysicalVolume::convertStructure creates stree.h and setTree into GGeo
0067     X4PhysicalVolume::convertStructure_r collects snode.h and transforms into the GGeo/stree
0068 
0069     NOW REPLACED
0070 
0071 sysrap/SBnd.h
0072     SBnd::FillMaterialLine uses the boundary specs to convert stree.h mtname into mtline
0073     for texture lookups and material index to line mappings needed at genstep collection
0074 
0075 
0076 
0077 Find users of stree.h
0078 ------------------------
0079 
0080 ::
0081 
0082     epsilon:opticks blyth$ opticks-fl stree.h | grep -v stree
0083     ./CSG/CSGTarget.cc
0084     ./CSG/tests/CSGFoundryLoadTest.cc
0085     ./extg4/X4PhysicalVolume.cc
0086     ./sysrap/CMakeLists.txt
0087     ./sysrap/SBnd.h
0088     ./sysrap/sphit.h
0089     ./sysrap/SSim.cc
0090     ./ggeo/GGeo.cc
0091     ./ggeo/tests/GGeoLoadFromDirTest.cc
0092     ./u4/U4Tree.h
0093     ./CSG_GGeo/CSG_GGeo_Convert.cc
0094 
0095 
0096 
0097 TODO Overview
0098 -------------------
0099 
0100 * maintain correspondence between source nodes and destination nodes thru the factorization
0101 * triplet_identity ?
0102 * transform rebase
0103 * solids nsphere ncone etc... into a more uniform direct to CSGPrim approach
0104 
0105 
0106 
0107 WIP: standardize to using NPFold.h serialize/import pattern
0108 
0109 
0110 Optimization ideas
0111 --------------------
0112 
0113 Work out how the time is split and consider pruning,
0114 not everything needs to be persisted eg
0115 
0116 gtd
0117 inst_f4
0118 iinst_f4
0119 
0120 POSSIBLY : make saving full nds nodes optional as the
0121 factorization might be made to replace the full info ?
0122 
0123 
0124 
0125 
0126 TODO : mapping from "factorized" instances back to origin PV and vice-versa
0127 -----------------------------------------------------------------------------
0128 
0129 Excluding the remainder, the instances each correspond to contiguous ranges of nidx.
0130 So can return back to the origin pv volumes using "U4Tree::get_pv(int nidx)" so long
0131 as store the outer nidx and number of nodes within the instances.
0132 
0133 Actually as all factorized instances of each type will have the same number of
0134 subtree nodes it makes no sense to duplicate that info : better to store the node counts
0135 in an "srepeat" instance held within stree.h and just store the first nidx within the instances.
0136 The natural place for this is within the spare 4th column of the instance transforms.
0137 
0138 TODO: modify sqat4.h::setIdentity to store this nidx and populate it
0139 HMM : actually better to defer until do stree->CSGFoundry direct translation
0140 
0141 For going in the other direction "int U4Tree::get_nidx(const G4VPhysicalVolume* pv) const"
0142 provides the nidx of a pv. Then can search the factorized instances for that nidx
0143 using the start nidx of each instance and number of nidx from the "srepeat".
0144 When the query nidx is not found within the instances it must be from the remainder.
0145 
0146 HMM: it would be faster to include the triplet_identity within the snode then
0147 can avoid the searching
0148 
0149 * need API to jump to the object within the CF model given the triplet_identity
0150 
0151 The natural place to keep map back info is within the instance transforms.
0152 
0153 
0154 mapping stree.h/nidx to CSGFoundry/globalPrimIdx ?
0155 ----------------------------------------------------
0156 
0157 see populate_prim_nidx
0158 
0159 
0160 mapping for the remainder non-instanced volumes
0161 -------------------------------------------------
0162 
0163 For the global remainder "instance" things are not so straightforward as there is only
0164 one of them with an identity transform and the nodes within it are not contiguous, they
0165 are what is left when all the repeated subtrees have been removed : so it will
0166 start from root nidx:0 and will have lots of gaps.
0167 
0168 Actually the natural place to keep "map-back" info for the remainder
0169 is withing the CSGPrim.  Normally use of that is for identity purposes is restricted
0170 because tthe CSGPrim are references from all the instances but for the remainder
0171 the CSGPrim are only referenced once.
0172 
0173 TODO: collect the nidx of the remainder into stree.h ?
0174 
0175 
0176 static log level control
0177 -------------------------
0178 
0179 As stree (and also U4Tree) are header only they cannot easily
0180 have a static EnvLevel as initing a static in header only situation
0181 is complicated in C++11.
0182 With C++17 can supposedly do this easily with "inline static". See
0183 
0184 https://stackoverflow.com/questions/11709859/how-to-have-static-data-members-in-a-header-only-library
0185 
0186 Pragmatic workaround for runtime logging control is to
0187 rely on the "guardian" of stree, which is SSim,
0188 as that instanciates stree in can easily set a member.
0189 
0190 So the stree::set_level is invoked from SSim::init based on the envvar::
0191 
0192     export SSim__stree_level=0    # no logging
0193     export SSim__stree_level=1    # minimal logging
0194     export SSim__stree_level=2    # some logging
0195     export SSim__stree_level=3    # verbose logging
0196 
0197 When SSim not in use can also use::
0198 
0199     export stree_level=1
0200 
0201 
0202 **/
0203 
0204 #include <cstdint>
0205 #include <csignal>
0206 #include <vector>
0207 #include <string>
0208 #include <sstream>
0209 #include <map>
0210 #include <functional>
0211 
0212 #include <glm/glm.hpp>
0213 #include <glm/gtc/type_ptr.hpp>
0214 
0215 #include "NP.hh"
0216 #include "NPX.h"
0217 #include "NPFold.h"
0218 #include "OpticksCSG.h"
0219 
0220 #include "ssys.h"
0221 #include "sstr.h"
0222 
0223 #include "scuda.h"   // HMM: needs vector_functions from CUDA
0224 
0225 #include "snode.h"
0226 #include "sdigest.h"
0227 #include "sfreq.h"
0228 #include "sstr.h"
0229 #include "strid.h"
0230 #include "sfactor.h"
0231 #include "stran.h"
0232 #include "stra.h"
0233 #include "slist.h"
0234 
0235 #include "s_csg.h"
0236 #include "sn.h"
0237 #include "s_unique.h"
0238 
0239 #include "stra.h"
0240 #include "sstandard.h"
0241 #include "smatsur.h"
0242 #include "snam.h"
0243 #include "SBnd.h"
0244 #include "SCenterExtentFrame.h"
0245 
0246 // transitional ?
0247 #include "sframe.h"
0248 
0249 #include "sphoton.h"
0250 #include "sphotonlite.h"
0251 #include "sphit.h"
0252 
0253 
0254 
0255 struct stree_standin
0256 {
0257     std::vector<snode> nds ;               // snode info for all structural nodes, the volumes
0258     std::vector<glm::tmat4x4<double>> m2w ; // model2world transforms for all nodes
0259     std::vector<glm::tmat4x4<double>> gtd ; // GGeo Transform Debug, added from X4PhysicalVolume::convertStructure_r
0260 };
0261 
0262 
0263 struct stree
0264 {
0265     typedef std::array<double,6> BB ;
0266     typedef std::vector<BB> VBB ;
0267     typedef glm::tmat4x4<double> TR ;
0268     typedef std::vector<TR> VTR ;
0269     typedef std::vector<snode> VND ;
0270 
0271 
0272 #ifdef STREE_CAREFUL
0273     static constexpr const char* stree__populate_prim_nidx = "stree__populate_prim_nidx" ;
0274     static constexpr const char* stree__populate_nidx_prim = "stree__populate_nidx_prim" ;
0275 #endif
0276 
0277     static constexpr const char* EXTENT_PFX = "EXTENT:" ;
0278     static constexpr const char* CE_PFX = "CE:" ;
0279     static constexpr const char* TE_PFX = "TE:" ;
0280     static constexpr const char* AXIS_PFX = "AXIS:" ;
0281     static constexpr const char* NIDX_PFX = "NIDX:" ;
0282     static constexpr const char* PRIM_PFX = "PRIM:" ;
0283     static constexpr const char* INST_PFX = "INST:" ;
0284     static constexpr const char* SID_PFX = "SID:" ;
0285     static constexpr const char* SIDX_PFX = "SIDX:" ;
0286     static constexpr const char* LVID_COPYNO_PFX = "LVID_COPYNO:" ;
0287 
0288     static constexpr const char* stree__force_triangulate_solid = "stree__force_triangulate_solid" ;
0289     static constexpr const char* stree__get_frame_dump = "stree__get_frame_dump" ;
0290 
0291     static constexpr const int MAXDEPTH = 15 ; // presentational limit only
0292 
0293     static constexpr const char* _FREQ_CUT = "stree__FREQ_CUT" ; // only active at geometry translation
0294     static constexpr const int    FREQ_CUT_DEFAULT = 500 ;   // HMM GInstancer using 400
0295     // subtree digests with less repeats than the FREQ_CUT within the entire geometry
0296     // are not regarded as repeats for instancing factorization purposes
0297     //
0298     static constexpr const char* BASE = "$CFBaseFromGEOM/CSGFoundry/SSim" ;
0299     static constexpr const char* RELDIR = "stree" ;
0300     static constexpr const char* DESC = "desc" ;
0301 
0302     static constexpr const char* NDS = "nds.npy" ;
0303     static constexpr const char* NDS_NOTE = "snode.h structural volume nodes" ;
0304     static constexpr const char* REM = "rem.npy" ;
0305     static constexpr const char* TRI = "tri.npy" ;
0306     static constexpr const char* M2W = "m2w.npy" ;
0307     static constexpr const char* W2M = "w2m.npy" ;
0308     static constexpr const char* GTD = "gtd.npy" ;  // GGeo transform debug, populated in X4PhysicalVolume::convertStructure_r
0309     static constexpr const char* TRS = "trs.npy" ;  // optional, when use save_trs
0310     static constexpr const char* MTNAME = "mtname.txt" ;
0311     static constexpr const char* MTNAME_NO_RINDEX = "mtname_no_rindex.txt" ;
0312     static constexpr const char* MTINDEX = "mtindex.npy" ;
0313     static constexpr const char* MTLINE = "mtline.npy" ;
0314     static constexpr const char* SUNAME = "suname.txt" ;
0315     static constexpr const char* IMPLICIT = "implicit.txt" ;
0316 
0317     static constexpr const char* FORCE_TRIANGULATE_LVID = "force_triangulate_lvid.npy" ;
0318 
0319 
0320 #ifdef DEBUG_IMPLICIT
0321     static constexpr const char* IMPLICIT_ISUR = "implicit_isur.npy" ;
0322     static constexpr const char* IMPLICIT_OSUR = "implicit_osur.npy" ;
0323 #endif
0324 
0325 
0326 
0327     static constexpr const char* SONAME = "soname.txt" ;
0328     static constexpr const char* CSG = "csg" ;
0329     static constexpr const char* _CSG = "_csg" ;
0330     static constexpr const char* SN = "sn" ;
0331     static constexpr const char* DIGS = "digs.txt" ;
0332     static constexpr const char* SUBS = "subs.txt" ;
0333     static constexpr const char* SUBS_FREQ = "subs_freq" ;
0334     static constexpr const char* FACTOR = "factor.npy" ;
0335     static constexpr const char* MATERIAL = "material" ;
0336     static constexpr const char* SURFACE = "surface" ;
0337     static constexpr const char* MESH = "mesh" ;
0338     static constexpr const char* STANDARD = "standard" ;
0339 
0340 
0341     static constexpr const char* INST = "inst.npy" ;
0342     static constexpr const char* IINST = "iinst.npy" ;
0343     static constexpr const char* INST_F4 = "inst_f4.npy" ;
0344     static constexpr const char* IINST_F4 = "iinst_f4.npy" ;
0345 
0346     static constexpr const char* SENSOR_ID = "sensor_id.npy" ;
0347     static constexpr const char* SENSOR_NAME = "sensor_name.npy" ;
0348     static constexpr const char* MTINDEX_TO_MTLINE = "mtindex_to_mtline.npy" ;
0349 
0350     static constexpr const char* INST_INFO = "inst_info.npy" ;
0351     static constexpr const char* INST_NIDX = "inst_nidx.npy" ;
0352 
0353     static constexpr const char* PRIM_NIDX = "prim_nidx.npy" ;
0354     static constexpr const char* NIDX_PRIM = "nidx_prim.npy" ;
0355     static constexpr const char* PRNAME = "prname.txt" ;
0356 
0357 
0358     int level ;                            // verbosity
0359     int FREQ_CUT ;
0360     const char*      force_triangulate_solid ;
0361     std::vector<int> force_triangulate_lvid ;
0362     bool get_frame_dump ;
0363 
0364 
0365     std::vector<std::string> mtname ;       // unique material names
0366     std::vector<std::string> mtname_no_rindex ;
0367     std::vector<int>         mtindex ;      // G4Material::GetIndex 0-based creation indices
0368     std::vector<int>         mtline ;
0369     std::map<int,int>        mtindex_to_mtline ;   // filled from mtindex and mtline via init_material_mapping
0370 
0371     std::vector<std::string> suname_raw ;   // surface names, direct from Geant4
0372     std::vector<std::string> suname ;       // surface names
0373     //std::vector<int>         suindex ;      // HMM: is this needed, its just 0,1,2,...
0374     std::vector<int4>        vbd ;
0375     std::vector<std::string> bdname ;
0376     std::vector<std::string> implicit ;  // names of implicit surfaces
0377 
0378     std::vector<std::string> soname_raw ;   // solid names, my have 0x pointer suffix
0379     std::vector<std::string> soname ;       // unique solid names, created with sstr::StripTail_Unique with _1 _2 ... uniqing
0380     std::vector<sn*>         solids ;       // used from U4Tree::initSolid but not available postcache, instead use sn::Get methods
0381 
0382     std::vector<glm::tmat4x4<double>> m2w ; // local (relative to parent) "model2world" transforms for all nodes
0383     std::vector<glm::tmat4x4<double>> w2m ; // local (relative to parent( "world2model" transforms for all nodes
0384     std::vector<glm::tmat4x4<double>> gtd ; // global (relative to root) "GGeo Transform Debug" transforms for all nodes
0385     // "gtd" formerly from X4PhysicalVolume::convertStructure_r
0386 
0387     std::vector<snode> nds ;               // snode info for all structural nodes, the volumes
0388     std::vector<snode> rem ;               // subset of nds with the remainder nodes
0389     std::vector<snode> tri ;               // subset of nds which are configured to be force triangulated (expected to otherwise be remainder nodes)
0390     std::vector<std::string> digs ;        // per-node digest for all nodes
0391     std::vector<std::string> subs ;        // subtree digest for all nodes
0392     std::vector<sfactor> factor ;          // small number of unique subtree factor, digest and freq
0393 
0394     std::vector<int> sensor_id ;           // updated by reorderSensors
0395     unsigned sensor_count ;
0396     std::vector<std::string> sensor_name ;
0397 
0398 
0399     sfreq* subs_freq ;                     // occurence frequency of subtree digests in entire tree
0400                                            // subs are collected in stree::classifySubtrees
0401 
0402     s_csg* _csg ;                          // sn.h based csg node trees
0403 
0404     sstandard* standard ;                  // mat/sur/bnd/bd/optical/wavelength/energy/rayleigh
0405 
0406     NPFold* material ;   // material properties from G4 MPTs
0407     NPFold* surface ;    // surface properties from G4 MPTs, includes OpticalSurfaceName osn in metadata
0408     NPFold* mesh ; // triangulation of all solids
0409     const char* MOI ;
0410 
0411 
0412     std::vector<glm::tmat4x4<double>> inst ;
0413     std::vector<glm::tmat4x4<float>>  inst_f4 ;
0414     std::vector<glm::tmat4x4<double>> iinst ;
0415     std::vector<glm::tmat4x4<float>>  iinst_f4 ;
0416 
0417     std::vector<int4>                 inst_info ;
0418     std::vector<int>                  inst_nidx ;
0419 
0420     std::vector<int>                  prim_nidx ; // experimental: see populate_prim_nidx
0421     std::vector<int>                  nidx_prim ; // experimental: see populate_nidx_prim
0422     std::vector<std::string>          prname ;    // prim names from faux_importPrim
0423     const char*                       loaddir ;
0424 
0425     stree();
0426 
0427     void init();
0428     void set_level(int level_);
0429 
0430     void save_desc(const char* base, const char* midl) const ;
0431     void save_desc(const char* base) const ;
0432     void save_desc_(const char* fold) const ;
0433     void populate_descMap( std::map<std::string, std::function<std::string()>>& m ) const ;
0434 
0435 
0436     std::string desc() const ;
0437     std::string desc_id() const ;
0438     std::string desc_meta() const ;
0439     std::string desc_soname() const ;
0440     std::string desc_lvid() const ;
0441     std::string desc_lvid_unique(const std::vector<int>& some_lvid) const ;
0442 
0443     std::string desc_size(char div='\n') const ;
0444     std::string desc_vec() const ;
0445     std::string desc_sub(bool all=false) const ;
0446     std::string desc_sub(const char* sub) const ;
0447 
0448 
0449     static std::string Digest(int lvid, const glm::tmat4x4<double>& tr );
0450     static std::string Digest(int lvid );
0451 
0452     template<typename T>
0453     static int GetPointerIndex( const std::vector<const T*>& vec, const T* obj) ;
0454     template<typename T>
0455     static int GetValueIndex( const std::vector<T>& vec, const T& obj) ;
0456 
0457 
0458     void get_children(std::vector<int>& children, int nidx) const ;   // immediate children
0459     void get_progeny( std::vector<int>& progeny, int nidx ) const ;   // recursively get children and all their children and so on...
0460     std::string desc_progeny(int nidx, int edge=1000) const ;  // edge=0 disables summarization
0461 
0462     void traverse(int nidx=0) const ;
0463     void traverse_r(int nidx, int depth, int sibdex) const ;
0464 
0465     void reorderSensors();
0466     void reorderSensors_r(int nidx);
0467     void get_sensor_id( std::vector<int>& arg_sensor_id ) const ;
0468 
0469     void find_nodes_with_sensor_id( std::vector<snode>& nodes, int q_sensor_id ) const ;
0470     int  get_ordinal_nidx_with_sensor_id( int q_sensor_id, int ordinal ) const ;
0471 
0472     void find_nodes_with_sensor_index( std::vector<snode>& nodes, int q_sensor_index ) const ;
0473     int  get_ordinal_nidx_with_sensor_index( int q_sensor_index, int ordinal ) const ;
0474 
0475 
0476 
0477 
0478 
0479     void postcreate() const ;
0480 
0481     std::string desc_sensor() const ;
0482     int get_num_nd_sensor() const ;
0483     void get_sensor_nidx( std::vector<int>& sensor_nidx ) const ;
0484 
0485     std::string desc_sensor_nd(int edge) const ;
0486 
0487     std::string desc_sensor_id(unsigned edge=10) const ;
0488     static std::string DescSensor( const std::vector<int>& sensor_id, const std::vector<int>& sensor_idx, unsigned edge=10 );
0489 
0490     void lookup_sensor_identifier(
0491          std::vector<int>& arg_sensor_identifier,
0492          const std::vector<int>& arg_sensor_index,
0493          bool one_based_index, bool verbose=false, unsigned edge=10 ) const ;
0494 
0495     sfreq* make_progeny_freq(int nidx) const ;
0496     sfreq* make_freq(const std::vector<int>& nodes ) const ;
0497 
0498 
0499 
0500     int  find_lvid(const char* soname_, bool starting=true  ) const ;
0501 
0502     const std::vector<snode>* get_node_vector(      char _src ) const ; // 'N':nds 'R':rem 'T':tri
0503     const char*               get_node_vector_name( char _src ) const ;
0504 
0505     void find_lvid_nodes_( std::vector<snode>& nodes, int lvid, char _src ) const ;
0506     void find_lvid_nodes(  std::vector<int>& nodes, int lvid, char _src ) const ;
0507 
0508     void find_lvid_copyno_nodes( std::vector<int>& nodes, int q_lvid, int q_copyno, char _src ) const ;
0509 
0510     int count_lvid_nodes( int lvid, char _src='N' ) const ;
0511 
0512 
0513 
0514     void find_lvid_nodes( std::vector<int>& nodes, const char* soname_, bool starting ) const ;
0515     int  find_lvid_node( const char* q_soname, int ordinal ) const ;
0516     int  find_lvid_node( const char* q_spec ) const ; // eg HamamatsuR12860sMask_virtual:0:1000
0517 
0518 
0519 
0520 
0521     const snode* pick_lvid_ordinal_node( int lvid, int ordinal, char ridx_type ) const ;
0522     const snode* _pick_lvid_ordinal_node( int lvid, int ordinal, char ridx_type ) const ;
0523 
0524     int pick_lvid_ordinal_repeat_ordinal_inst_( int lvid, int lvid_ordinal, int repeat_ordinal ) const ;
0525     int parse_spec(int& lvid, int& lvid_ordinal, int& repeat_ordinal, const char* q_spec ) const ;
0526     int pick_lvid_ordinal_repeat_ordinal_inst( const char* q_spec ) const ;
0527 
0528 
0529    // transitional method for matching with CSGFoundry::getFrame
0530     void get_frame_f4( sframe& fr, int idx ) const ;
0531 
0532 
0533     sfr  get_frame_moi() const ;
0534     sfr  get_frame(const char* q_spec ) const ;
0535 
0536     sfr  get_frame_top() const ;
0537     sfr  get_frame_extent(const char* s_extent ) const ;
0538     sfr  get_frame_ce(const char* s_ce ) const ;
0539     sfr  get_frame_ce(const float*  ce ) const ;
0540     sfr  get_frame_te(const char* s_te ) const ;
0541     sfr  get_frame_te(const float*  te ) const ;
0542 
0543 
0544     sfr  get_frame_axis(const char* s_axis ) const ;
0545     sfr  get_frame_prim(const char* s_prim ) const ;
0546     sfr  get_frame_nidx(const char* s_nidx ) const ;
0547     sfr  get_frame_inst(const char* s_inst ) const ;
0548     sfr  get_frame_sid( const char* s_sid ) const ;
0549     sfr  get_frame_sidx( const char* s_sidx ) const ;
0550     sfr  get_frame_lvid_copyno( const char* s_lvid_copyno ) const ;
0551 
0552     sfr  get_frame_prim(int prim ) const ;
0553     sfr  get_frame_nidx(int nidx ) const ;
0554     sfr  get_frame_inst(int inst ) const ;
0555     sfr  get_frame_sid( int sid  ) const ;
0556     sfr  get_frame_sidx( int sidx  ) const ;
0557     sfr  get_frame_lvid_copyno(int q_lvid, int q_copyno, char q_src ) const ;
0558 
0559 
0560     int  get_frame_from_npyfile(sfr& f, const char* q_spec ) const ;
0561     int  get_frame_from_triplet(sfr& f, const char* q_spec ) const ;
0562     int  get_frame_from_coords( sfr& f, const char* q_spec ) const ;
0563     int  get_frame_from_transform( sfr& f, const char* q_spec ) const ;
0564 
0565     bool has_frame(const char* q_spec) const ;
0566 
0567 
0568     int get_frame_instanced(  sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal, std::ostream* out = nullptr, VTR* t_stack = nullptr ) const ;
0569     int get_frame_inst(sfr& f, int ii, std::ostream* out = nullptr, VTR* t_stack = nullptr ) const ;
0570 
0571 
0572     int get_frame_remainder(  sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal ) const ;
0573     int get_frame_triangulate(sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal ) const ;
0574     int get_frame_global(     sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal ) const ;
0575     int _get_frame_global(     sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal, char ridx_type ) const ;
0576 
0577     static constexpr const char* stree__get_frame_global_LVID = "stree__get_frame_global_LVID" ;
0578     static constexpr const char* stree__get_node_bb_LVID      = "stree__get_node_bb_LVID" ;
0579     static constexpr const char* stree__get_node_bb_CONSTITUENT = "stree__get_node_bb_CONSTITUENT" ;
0580 
0581     int get_node_ce_bb(  std::array<double,4>& ce , std::array<double,6>& bb, const snode& node, VBB* contrib_bb = nullptr, VTR* contrib_tr = nullptr ) const ;
0582     int get_node_bb(     std::array<double,6>& bb ,                           const snode& node, VBB* contrib_bb = nullptr, VTR* contrib_tr = nullptr ) const ;
0583 
0584     template<typename T>
0585     void find_nodes_containing_point(std::vector<snode>& nodes, const T* xyz ) const ;
0586 
0587     template<typename T>
0588     void        find_nodes_with_center_within_bb(std::vector<snode>& nodes, const T* qbb ) const ;
0589 
0590     template<typename T>
0591     void        find_nodes_with_center_within_ce(std::vector<snode>& nodes, const T* qce ) const ;
0592 
0593     template<typename T>
0594     std::string desc_nodes_with_center_within_ce( const T* qce ) const ;
0595 
0596 
0597     void get_sub_sonames( std::vector<std::string>& sonames ) const ;
0598     const char* get_sub_soname(const char* sub) const ;
0599 
0600     static std::string Name( const std::string& name, bool strip_tail );
0601     std::string get_lvid_soname(int lvid, bool strip ) const ;
0602     const std::string& get_lvid_soname_(int lvid) const ;
0603 
0604     void        get_meshname( std::vector<std::string>& names) const ;  // match CSGFoundry
0605     void        get_mmlabel(  std::vector<std::string>& names) const ;  // match CSGFoundry
0606 
0607 
0608     int         get_num_nodes() const ;
0609     const char* get_soname(int nidx) const ;
0610     const char* get_sub(   int nidx) const ;
0611     int         get_depth( int nidx) const ;
0612     int         get_parent(int nidx) const ;
0613     int         get_lvid(  int nidx) const ;
0614     int         get_copyno(int nidx) const ;
0615 
0616     const snode* get_top() const ;
0617     const snode* get_node(int nidx) const ;
0618     const snode* get_parent_node(int nidx) const ;
0619     bool         is_outer_node(int nidx) const ;
0620 
0621     void         get_ancestors(std::vector<int>& ancestors, int nidx, bool local, std::ostream* out ) const ;
0622     std::string desc_ancestors(                             int nidx, bool local ) const ;
0623 
0624 
0625     void get_node_transform( glm::tmat4x4<double>& m2w_, glm::tmat4x4<double>& w2m_, int nidx ) const ;
0626     void get_node_product(
0627            glm::tmat4x4<double>& m2w_,
0628            glm::tmat4x4<double>& w2m_, int nidx, bool local, bool reverse, std::ostream* out, VTR* t_stack ) const ;
0629 
0630     std::string desc_node_product(   glm::tmat4x4<double>& m2w_, glm::tmat4x4<double>& w2m_, int nidx, bool local, bool reverse ) const ;
0631 
0632 
0633 
0634 
0635     void get_combined_transform(
0636              glm::tmat4x4<double>& t,
0637              glm::tmat4x4<double>& v,
0638              const snode& node,
0639              const sn* nd,
0640              std::ostream* out,
0641              VTR* t_stack
0642              ) const ;
0643 
0644     std::string desc_combined_transform(
0645              glm::tmat4x4<double>& t,
0646              glm::tmat4x4<double>& v,
0647              const snode& node,
0648              const sn* nd
0649              ) const ;
0650 
0651     const Tran<double>* get_combined_tran_and_aabb(
0652              double* aabb,
0653              const snode& node,
0654              const sn* nd,
0655              std::ostream* out,
0656              VTR* t_stack
0657              ) const ;
0658 
0659     void get_transformed_aabb(
0660              double* aabb,
0661              const snode& node,
0662              const sn* nd,
0663              std::ostream* out,
0664              VTR* t_stack
0665              ) const ;
0666 
0667     void get_prim_aabb( double* aabb, const snode& node, std::ostream* out, VTR* t_stack ) const ;
0668     void get_prim_aabb( std::vector<std::array<double,6>>& vbb, const std::vector<snode>& nodes) const ;
0669 
0670 
0671     void get_nodes(std::vector<int>& nodes, const char* sub) const ;
0672     void get_depth_range(unsigned& mn, unsigned& mx, const char* sub) const ;
0673     int get_first( const char* sub ) const ;
0674 
0675     const char* get_tree_digest() const ;
0676     std::string make_tree_digest(const std::vector<unsigned char>& extra) const ;
0677 
0678     std::string subtree_digest( int nidx ) const ;
0679     std::string subtree_digest_plus(int nidx, const std::vector<unsigned char>& extra) const ;
0680 
0681 
0682     static std::string depth_spacer(int depth);
0683 
0684     std::string desc_node_(int nidx, const sfreq* sf ) const ;
0685     std::string desc_node(int nidx, bool show_sub=false) const ;
0686 
0687     std::string desc_nodes( const std::vector<int>&   nn, int edgeitems=10) const ;
0688     std::string desc_nodes_(const std::vector<snode>& nn, int edgeitems=10) const ;
0689     std::string desc_node_solids() const ;
0690     std::string desc_solids_0() const ;
0691     std::string desc_solids() const ;
0692     std::string desc_triangulate() const ;
0693     std::string desc_solid(int lvid) const ;
0694 
0695 
0696     NP* make_trs() const ;
0697     void save_trs(const char* fold) const ;
0698 
0699 
0700     void save_( const char* fold ) const ;
0701     void save( const char* base ) const ;
0702     NPFold* serialize() const ;
0703 
0704 
0705     template<typename S, typename T>   // S:compound type T:atomic "transport" type
0706     static void ImportArray( std::vector<S>& vec, const NP* a, const char* label );
0707 
0708     static void ImportNames( std::vector<std::string>& names, const NP* a, const char* label );
0709 
0710 
0711     static stree* Load(const char* base=BASE , const char* reldir=RELDIR );
0712 
0713     int load( const char* base, const char* reldir=RELDIR );
0714     int load_( const char* fold );
0715     void import_(const NPFold* fold);
0716 
0717 
0718     static int Compare( const std::vector<int>& a, const std::vector<int>& b ) ;
0719     static std::string Desc(const std::vector<int>& a, unsigned edgeitems=10 ) ;
0720 
0721     static void FindForceTriangulateLVID(std::vector<int>& lvid, const std::vector<std::string>& _sonames, const char* _force_triangulate_solid, char delim=','  );
0722     std::string descForceTriangulateLVID() const ;
0723     bool        is_force_triangulate( int lvid ) const ; // HMM: is_manual_triangulate would be better name
0724 
0725     static constexpr const char* stree__is_auto_triangulate_NAMES = "stree__is_auto_triangulate_NAMES" ;
0726     bool        is_auto_triangulate( int lvid ) const ;  // WIP: automate decision, avoiding hassle with geometry updates that change/add solid names
0727     bool        is_triangulate(int lvid) const ;  // OR of the above
0728 
0729 
0730     void classifySubtrees();
0731     bool is_contained_repeat(const char* sub) const ;
0732     void disqualifyContainedRepeats();
0733     void sortSubtrees();
0734     void enumerateFactors();
0735     void labelFactorSubtrees();
0736     void findForceTriangulateLVID();
0737     void collectGlobalNodes();
0738     std::string descNodes() const;
0739 
0740     static constexpr const char* _findForceTriangulateLVID_DUMP = "stree__findForceTriangulateLVID_DUMP" ;
0741 
0742     void factorize();
0743 
0744     int get_num_factor() const ;
0745     sfactor& get_factor_(unsigned idx) ;
0746     const sfactor& get_factor(unsigned idx) const ;
0747 
0748     int      get_factor_subtree(unsigned idx) const ;
0749     int      get_factor_olvid(unsigned idx) const ;   // outer-lv-id
0750 
0751     int      get_remainder_subtree() const ;
0752     int      get_triangulate_subtree() const ;
0753     int      get_remainder_olvid() const  ;
0754     int      get_triangulate_olvid() const  ;
0755 
0756     int      get_ridx_subtree(unsigned ridx) const ;
0757     int      get_ridx_olvid(  unsigned ridx) const ;
0758 
0759     int      get_num_ridx() const ;
0760     int      get_num_ridx_(char ridx_type) const ;
0761     int      get_num_remainder() const ;
0762     int      get_num_triangulated() const ;
0763     char     get_ridx_type(int ridx) const ;
0764 
0765     void get_global_nodes(std::vector<snode>& nodes ) const ;
0766     void get_factor_nodes(std::vector<int>& nodes, unsigned idx) const ;
0767     std::string desc_factor_nodes(unsigned idx) const ;
0768     std::string desc_factor() const ;
0769 
0770     static bool SelectNode( const snode& nd, int q_repeat_index, int q_repeat_ordinal );
0771     // q_repeat_ordinal:-2 selects all repeat_ordinal
0772 
0773     void get_repeat_field(std::vector<int>& result, char q_field , int q_repeat_index, int q_repeat_ordinal ) const ;
0774     void get_repeat_lvid( std::vector<int>& lvids, int q_repeat_index, int q_repeat_ordinal=-2 ) const ;
0775     void get_repeat_nidx( std::vector<int>& nidxs, int q_repeat_index, int q_repeat_ordinal=-2 ) const ;
0776 
0777     void get_remainder_nidx(std::vector<int>& nidxs ) const ;
0778 
0779     void get_repeat_node( std::vector<snode>& nodes, int q_repeat_index, int q_repeat_ordinal ) const ;
0780     std::string desc_repeat_node(int q_repeat_index, int q_repeat_ordinal) const ;
0781 
0782     std::string desc_repeat_nodes() const ;
0783 
0784     std::string desc_NRT() const ;
0785     std::string desc_nds() const ;
0786     std::string desc_rem() const ;
0787     std::string desc_tri() const ;
0788     std::string desc_NRT(char NRT) const ;
0789 
0790     size_t get_repeat_index_zero_count() const ;
0791     void count_repeat_index( std::map<size_t,size_t>& m, char NRT ) const ;
0792     std::string desc_repeat_index() const ;
0793     std::string desc_repeat_index(char NRT, size_t* _tot, size_t* _num0 ) const ;
0794 
0795     NPFold* get_global_aabb() const;
0796     NPFold* get_global_aabb_sibling_overlaps() const;
0797 
0798     std::string desc_node_ELVID() const ;
0799     std::string desc_node_ECOPYNO() const ;
0800     std::string desc_node_EBOUNDARY() const ;
0801     std::string desc_node_elist(const char* etag, const char* fallback) const ;
0802 
0803 
0804     void add_inst( glm::tmat4x4<double>& m2w, glm::tmat4x4<double>& w2m, int gas_idx, int nidx );
0805     void add_inst_identity( int gas_idx, int nidx );
0806     void add_inst();
0807 
0808     void narrow_inst();
0809     void clear_inst();
0810     std::string desc_inst() const ;
0811     std::string desc_inst_info() const ;
0812     std::string desc_inst_info_check() const;
0813 
0814     int find_inst_gas(        int q_gas_idx, int q_gas_ordinal ) const ;
0815     int find_inst_gas_slowly( int q_gas_idx, int q_gas_ordinal ) const ;
0816     void find_inst_gas_slowly_( std::vector<int>& v_inst_idx , int q_gas_idx ) const ;
0817 
0818 
0819 
0820     int get_inst_identity( glm::tvec4<int64_t>& col3, int ii ) const ;
0821     int get_inst_identity( int& inst_idx, int& gas_idx, int& sensor_identifier, int& sensor_index, int ii ) const ;
0822 
0823     // these DO have identity info in forth column
0824     const glm::tmat4x4<double>* get_inst(int idx) const ;
0825     const glm::tmat4x4<double>* get_iinst(int idx) const ;
0826     const glm::tmat4x4<float>*  get_inst_f4(int idx) const ;
0827     const glm::tmat4x4<float>*  get_iinst_f4(int idx) const ;
0828 
0829 
0830 
0831 
0832 
0833     void get_mtindex_range(int& mn, int& mx ) const ;
0834     std::string desc_mt() const ;
0835     std::string desc_bd() const ;
0836 
0837     void initStandard() ;
0838 
0839     static constexpr const char* _init_material_mapping_DUMP = "stree__init_material_mapping_DUMP" ;
0840     void init_material_mapping();
0841 
0842     int add_material( const char* name, unsigned g4index );
0843     int num_material() const ;
0844 
0845     int add_extra_surface( const char* name );
0846     int add_extra_surface( const std::vector<std::string>& names  );
0847 
0848     int get_surface( const char* name ) const ;
0849     int num_surface() const ;   // total including implicit
0850 
0851 
0852     int add_surface_implicit( const char* name );
0853     int get_surface_implicit( const char* name ) const ;
0854     int num_surface_implicit() const ;
0855     int num_surface_standard() const ; // total with implicit subtracted
0856 
0857 
0858     int add_boundary( const int4& bd_ );
0859 
0860     const char* get_material_name(int idx) const ;
0861     const char* get_surface_name(int idx) const ;
0862     std::string get_boundary_name( const int4& bd_, char delim ) const ;
0863 
0864     NPFold* get_surface_subfold(int idx) const ;
0865 
0866     //void import_bnd(const NP* bnd);
0867     void init_mtindex_to_mtline();
0868     int lookup_mtline( int mtindex ) const ;
0869 
0870     // experimental
0871     void populate_prim_nidx();
0872     void faux_importSolid();
0873     void faux_importSolidGlobal(int ridx, char ridx_type);
0874     void faux_importSolidFactor(int ridx, char ridx_type);
0875     int  faux_importPrim(int primIdx, const snode& node );
0876     const char* get_prname(int globalPrimIdx) const ;
0877     int  search_prim_for_nidx_first(int nidx) const ;
0878 
0879     void populate_nidx_prim();
0880     void check_nidx_prim() const ;
0881     int  get_prim_for_nidx(int nidx) const ;
0882     int  get_nidx_for_prim(int prim) const ;
0883     std::string desc_prim() const ;
0884     std::string desc_prim(int prim) const ;
0885 
0886 
0887     void localize_photon_inplace( sphoton& p ) const ;
0888     NP* localize_photon(const NP* photon, bool consistency_check) const ;
0889     void transform_consistency_check( const sphoton& l ) const;
0890 
0891     NP* create_photonlite_from_photon( const NP* photon ) const ;
0892     void create_photonlite_from_photon( sphotonlite& l, const sphoton& p ) const ;
0893 
0894 };
0895 
0896 
0897 /**
0898 stree::stree
0899 --------------
0900 
0901 Q: why empty NPFold material and surface instead of nullptr ?
0902 
0903 
0904      wavelength(nullptr),
0905      energy(nullptr),
0906      rayleigh(nullptr),
0907      mat(nullptr),
0908      sur(nullptr),
0909      bd(nullptr),
0910      bnd(nullptr),
0911      optical(nullptr)
0912 
0913 HMM the force_triangulate_solid envvar only relevant for stree creation, not with loaded stree ?
0914 
0915 **/
0916 
0917 
0918 inline stree::stree()
0919     :
0920     level(ssys::getenvint("stree__level", 0)),
0921     FREQ_CUT(ssys::getenvint(_FREQ_CUT, FREQ_CUT_DEFAULT)),
0922     force_triangulate_solid(ssys::getenvvar(stree__force_triangulate_solid,nullptr)),
0923     get_frame_dump(ssys::getenvbool(stree__get_frame_dump)),
0924     sensor_count(0),
0925     subs_freq(new sfreq),
0926     _csg(new s_csg),
0927     standard(new sstandard),
0928     material(new NPFold),
0929     surface(new NPFold),
0930     mesh(new NPFold),
0931     MOI(ssys::getenvvar("MOI", "0:0:-1")),
0932     loaddir(nullptr)
0933 {
0934     init();
0935 }
0936 
0937 inline void stree::init()
0938 {
0939     if(level > 0) std::cout
0940          << "stree::init "
0941          << " force_triangulate_solid [" << ( force_triangulate_solid ? force_triangulate_solid : "-" ) << "]"
0942          << std::endl
0943          ;
0944 }
0945 
0946 
0947 inline void stree::set_level(int level_)
0948 {
0949     level = level_ ;
0950     if(level > 0)
0951     {
0952         std::cout
0953             << "stree::set_level " << level
0954             << " [adjust via envvar SSim__stree_level ]"
0955             << std::endl
0956             ;
0957     }
0958 }
0959 
0960 inline std::string stree::desc_size(char div) const
0961 {
0962     std::stringstream ss ;
0963     ss
0964        << std::endl
0965        << "[stree::desc_size" << div
0966        << " mtname " << mtname.size() << div
0967        << " mtname_no_rindex " << mtname_no_rindex.size() << div
0968        << " mtindex " << mtindex.size() << div
0969        << " mtline " << mtline.size() << div
0970        << " mtindex_to_mtline " << mtindex_to_mtline.size() << div
0971        << " suname_raw " << suname_raw.size() << div
0972        << " suname " << suname.size() << div
0973        //<< " suindex " << suindex.size() << div
0974        << " vbd " << vbd.size() << div
0975        << " bdname " << bdname.size() << div
0976        << " implicit " << implicit.size() << div
0977        << " soname " << soname.size() << div
0978        << " force_triangulate_lvid " << force_triangulate_lvid.size() << div
0979        << " solids " << solids.size() << div
0980        << " sensor_count " << sensor_count << div
0981        << " nds " << nds.size() << div
0982        << " rem " << rem.size() << div
0983        << " tri " << tri.size() << div
0984        << " m2w " << m2w.size() << div
0985        << " w2m " << w2m.size() << div
0986        << " gtd " << gtd.size() << div
0987        << " digs " << digs.size() << div
0988        << " subs " << subs.size() << div
0989        << " soname " << soname.size() << div
0990        << " factor " << factor.size() << div
0991        << std::endl
0992        ;
0993 
0994     std::string str = ss.str();
0995     return str ;
0996 }
0997 
0998 
0999 
1000 /**
1001 stree::save_desc
1002 ------------------
1003 
1004 Save .txt files for each of the listed desc methods
1005 within a *reldir* which defaults to "desc" which is
1006 created within the *base* directory.
1007 
1008 **/
1009 
1010 inline void stree::save_desc(const char* base, const char* midl) const
1011 {
1012     const char* fold = U::Resolve(base, midl, DESC);
1013     save_desc_(fold);
1014 }
1015 inline void stree::save_desc(const char* base) const
1016 {
1017     const char* fold = U::Resolve(base, DESC);
1018     save_desc_(fold);
1019 }
1020 
1021 inline void stree::save_desc_(const char* fold) const
1022 {
1023     std::cout << "[stree::save_desc_ fold [" << ( fold ? fold : "-" ) << "]\n" ;
1024 
1025     std::map<std::string, std::function<std::string()>> descMap ;
1026     populate_descMap(descMap);
1027 
1028     for (auto const& [k, fn] : descMap)
1029     {
1030         std::string name = k + ".txt" ;
1031         std::cout << "-stree::save_desc_ name [" << name.c_str() << "]\n" ;
1032 
1033         std::string desc = fn(); // move this after output as errors likely here
1034 
1035         U::WriteString( fold, name.c_str(), desc.c_str() );
1036     }
1037     std::cout << "]stree::save_desc_ fold [" << ( fold ? fold : "-" ) << "]\n" ;
1038 }
1039 
1040 inline void stree::populate_descMap( std::map<std::string, std::function<std::string()>>& m ) const
1041 {
1042     int NIDX = ssys::getenvint("NIDX", 0);
1043     int EDGE = ssys::getenvint("EDGE", 10);
1044     int PROGENY_EDGE = ssys::getenvint("PROGENY_EDGE", 1000);
1045 
1046     m["id"] = [this](){ return this->desc_id(); };
1047     m["meta"] = [this](){ return this->desc_meta(); };
1048     m["soname"] = [this](){ return this->desc_soname(); };
1049     m["lvid"] = [this](){ return this->desc_lvid(); };
1050 
1051     m["size"] = [this](){ return this->desc_size(); };
1052     m["vec"] = [this](){ return this->desc_vec(); };
1053     m["sub"] = [this](){ return this->desc_sub(); };
1054     m["progeny"] = [this,NIDX,PROGENY_EDGE](){ return this->desc_progeny(NIDX,PROGENY_EDGE); };
1055 
1056     m["sensor"] = [this](){ return this->desc_sensor(); };
1057     m["sensor_nd"] = [this,EDGE](){ return this->desc_sensor_nd(EDGE); };
1058     m["sensor_id"] = [this,EDGE](){ return this->desc_sensor_id(EDGE); };
1059     m["node_solids"] = [this](){ return this->desc_node_solids(); };
1060     m["nodes"] = [this](){ return this->descNodes(); };
1061     m["solids"] = [this](){ return this->desc_solids(); };
1062     m["prim"] = [this](){ return this->desc_prim(); };
1063     m["triangulate"] = [this](){ return this->desc_triangulate(); };
1064     m["factor"] = [this](){ return this->desc_factor(); };
1065     m["repeat_nodes"] = [this](){ return this->desc_repeat_nodes(); };
1066 
1067     m["NRT"] = [this](){ return this->desc_NRT(); };
1068     m["nds"] = [this](){ return this->desc_nds(); };
1069     m["rem"] = [this](){ return this->desc_rem(); };
1070     m["tri"] = [this](){ return this->desc_tri(); };
1071 
1072     m["node_ELVID"] = [this](){ return this->desc_node_ELVID(); };
1073     m["node_ECOPYNO"] = [this](){ return this->desc_node_ECOPYNO(); };
1074     m["node_EBOUNDARY"] = [this](){ return this->desc_node_EBOUNDARY(); };
1075     m["inst"] = [this](){ return this->desc_inst(); };
1076     m["inst_info"] = [this](){ return this->desc_inst_info(); };
1077     m["inst_info_check"] = [this](){ return this->desc_inst_info_check(); };
1078     m["mt"] = [this](){ return this->desc_mt(); };
1079     m["bd"] = [this](){ return this->desc_bd(); };
1080 
1081     m["subs_freq"] = [this](){ return this->subs_freq ? this->subs_freq->desc() : "-" ; };
1082     m["material"] = [this](){ return this->material ? this->material->desc() : "-" ; };
1083     m["surface"] = [this](){ return this->surface ? this->surface->desc() : "-" ; };
1084     m["mesh"] = [this](){ return this->mesh ? this->mesh->desc() : "-" ; };
1085     m["_csg"] = [this](){ return this->_csg ? this->_csg->desc() : "-" ; };
1086 
1087 }
1088 
1089 inline std::string stree::desc() const
1090 {
1091     std::stringstream ss ;
1092     ss
1093        << std::endl
1094        << "[stree::desc"
1095        << desc_id()
1096        << desc_meta()
1097        << desc_size()
1098        << " stree.desc.subs_freq "
1099        << std::endl
1100        << ( subs_freq ? subs_freq->desc() : "-" )
1101        << std::endl
1102        << desc_factor()
1103        << std::endl
1104        << desc_repeat_nodes()
1105        << std::endl
1106        << desc_lvid()
1107        << std::endl
1108        ;
1109 
1110     if(level > 2) ss
1111        << "stree::desc.material "
1112        << std::endl
1113        << ( material ? material->desc() : "-" )
1114        << std::endl
1115        << " stree::desc.surface "
1116        << std::endl
1117        << ( surface ? surface->desc() : "-" )
1118        << std::endl
1119        << " stree::desc.mesh "
1120        << std::endl
1121        << ( mesh ? mesh->desc() : "-" )
1122        << std::endl
1123        << " stree::desc.csg "
1124        << std::endl
1125        << ( _csg ? _csg->desc() : "-" )
1126        << std::endl
1127        << "]stree::desc"
1128        << std::endl
1129        ;
1130 
1131     std::string str = ss.str();
1132     return str ;
1133 }
1134 
1135 inline std::string stree::desc_id() const
1136 {
1137     std::stringstream ss ;
1138     ss << "[stree::desc_id\n" ;
1139     ss << " loaddir " << ( loaddir ? loaddir : "-" ) << "\n" ;
1140     ss << "]stree::desc_id\n" ;
1141     std::string str = ss.str();
1142     return str ;
1143 }
1144 inline std::string stree::desc_meta() const
1145 {
1146     std::stringstream ss ;
1147     ss << "[stree::desc_meta\n" ;
1148     ss << "level:" << level << "\n" ;
1149     ss << "FREQ_CUT:" << FREQ_CUT << "\n" ;
1150     ss << "FREQ_CUT_DEFAULT:" << FREQ_CUT_DEFAULT << "\n" ;
1151     ss << "]stree::desc_meta\n" ;
1152     std::string str = ss.str();
1153     return str ;
1154 }
1155 
1156 inline std::string stree::desc_soname() const
1157 {
1158     std::stringstream ss ;
1159     ss << "[stree::desc_soname\n" ;
1160     for(int i=0 ; i < int(soname.size()) ; i++) ss << "[" << soname[i] << "]\n" ;
1161     ss << "]stree::desc_soname\n" ;
1162     std::string str = ss.str();
1163     return str ;
1164 }
1165 
1166 inline std::string stree::desc_lvid() const
1167 {
1168     std::stringstream ss ;
1169     ss << "[stree::desc_lvid\n" ;
1170     ss << "force_triangulate_lvid.size " << force_triangulate_lvid.size() << "\n" ;
1171 
1172     int sum[3] = {0,0,0} ;
1173 
1174     for(int i=0 ; i < int(soname.size()) ; i++)
1175     {
1176         const char* lvn = soname[i].c_str();
1177         bool starting = false ; // ie MatchAll not MatchStart
1178         int lvid = find_lvid(lvn, starting);
1179         bool ift = is_force_triangulate(lvid) ;
1180 
1181         int count_N = count_lvid_nodes(lvid, 'N' );
1182         int count_R = count_lvid_nodes(lvid, 'R' );
1183         int count_T = count_lvid_nodes(lvid, 'T' );
1184 
1185         ss
1186            << " i " << std::setw(4) << i
1187            << " lvid " << std::setw(4) << lvid
1188            << " is_force_triangulate " << ( ift ? "YES" : "NO " )
1189            << " count_N/R/T "
1190            << std::setw(6) << count_N
1191            << std::setw(6) << count_R
1192            << std::setw(6) << count_T
1193            << " lvn " << lvn
1194            << "\n"
1195            ;
1196 
1197         sum[0] += count_N ;
1198         sum[1] += count_R ;
1199         sum[2] += count_T ;
1200 
1201     }
1202 
1203     ss << "   " << "    "
1204        << "      " << "    "
1205        << "                      " << "   "
1206        << "   sum_N/R/T "
1207        << std::setw(6) << sum[0]
1208        << std::setw(6) << sum[1]
1209        << std::setw(6) << sum[2]
1210        << "\n"
1211        ;
1212 
1213     ss << " nds.size " << nds.size() << "\n" ;
1214     ss << " rem.size " << rem.size() << "\n" ;
1215     ss << " tri.size " << tri.size() << "\n" ;
1216     ss << "]stree::desc_lvid\n" ;
1217     std::string str = ss.str();
1218     return str ;
1219 }
1220 
1221 /**
1222 stree::desc_lvid_unique
1223 ------------------------
1224 
1225 Counts unique lvid from some_lvid and presents unique table
1226 with occurence counts and solid names from soname. The table
1227 is ordered by descending occurence counts.
1228 
1229 **/
1230 
1231 
1232 inline std::string stree::desc_lvid_unique(const std::vector<int>& some_lvid) const
1233 {
1234     // count unique lvid
1235     std::vector<int>           u_lvid ;
1236     std::vector<std::size_t>   c_lvid ;  // count
1237     std::vector<std::size_t>   o_lvid ;  // order
1238     std::vector<std::size_t>   x_lvid ;  // first index
1239 
1240     s_unique( u_lvid, some_lvid.begin(), some_lvid.end(), &c_lvid, &o_lvid, &x_lvid );
1241 
1242     std::stringstream ss ;
1243     ss
1244         << "[stree::desc_unique_lvid\n"
1245         << " some_lvid.size " << some_lvid.size() << "\n"
1246         << " u_lvid.size " << u_lvid.size() << "\n"
1247         << " c_lvid.size " << c_lvid.size() << "\n"
1248         << " o_lvid.size " << o_lvid.size() << "\n"
1249         << " x_lvid.size " << x_lvid.size() << "\n"
1250         << s_unique_desc( u_lvid, &soname, &c_lvid, &o_lvid, &x_lvid ) << "\n"
1251         << "]stree::desc_unique_lvid\n"
1252         ;
1253 
1254     std::string str = ss.str();
1255     return str ;
1256 }
1257 
1258 
1259 
1260 
1261 
1262 /**
1263 stree::desc_vec
1264 -----------------
1265 
1266 Description of subs_freq showing all subs and frequencies without any cut.
1267 
1268 **/
1269 
1270 inline std::string stree::desc_vec() const
1271 {
1272     const sfreq::VSU& vsu = subs_freq->vsu ;
1273     std::stringstream ss ;
1274     for(unsigned i=0 ; i < vsu.size() ; i++)
1275     {
1276         const sfreq::SU& su = vsu[i] ;
1277         const char* sub = su.first.c_str() ;
1278         int freq = su.second ;
1279 
1280         ss << std::setw(3) << i
1281            << " : "
1282            << std::setw(32) << sub
1283            << " : "
1284            << std::setw(6) << freq
1285            << std::endl
1286            ;
1287     }
1288     std::string s = ss.str();
1289     return s;
1290 }
1291 
1292 /**
1293 stree::desc_sub
1294 ----------------
1295 
1296 Description of subtree digests and their frequencies obtained from "(sfreq*)subs_freq"
1297 For all:false only qualified repeats are listed.
1298 
1299 The depth range of all nodes with each subtree digest is listed
1300 as well as the nidx of the first node with the digest and the
1301 soname corresponding to that first nidx.::
1302 
1303     st.desc_sub(false)
1304         0 : 1af760275cafe9ea890bfa01b0acb1d1 : 25600 de:( 6  6) 1st:194249 PMT_3inch_pmt_solid0x66e59f0
1305         1 : 1e410142530e54d54db8aaaccb63b834 : 12612 de:( 6  6) 1st: 70965 NNVTMCPPMTsMask_virtual0x5f5f900
1306         2 : 0077df3ebff8aeec56c8a21518e3c887 :  5000 de:( 6  6) 1st: 70972 HamamatsuR12860sMask_virtual0x5f50d40
1307         3 : 019f9eccb5cf94cce23ff7501c807475 :  2400 de:( 4  4) 1st:322253 mask_PMT_20inch_vetosMask_virtual0x5f62e40
1308         4 : c051c1bb98b71ccb15b0cf9c67d143ee :   590 de:( 6  6) 1st: 68493 sStrutBallhead0x5853640
1309         5 : 5e01938acb3e0df0543697fc023bffb1 :   590 de:( 6  6) 1st: 69083 uni10x5832ff0
1310         6 : cdc824bf721df654130ed7447fb878ac :   590 de:( 6  6) 1st: 69673 base_steel0x58d3270
1311         7 : 3fd85f9ee7ca8882c8caa747d0eef0b3 :   590 de:( 6  6) 1st: 70263 uni_acrylic10x597c090
1312         8 : 7d9a644fae10bdc1899c0765077e7a33 :   504 de:( 7  7) 1st:    15 sPanel0x71a8d90
1313 
1314 25600+12612+5000+2400+590*4+504+1=48477 this matches the number of GGeo->CSGFoundry inst
1315 
1316 **/
1317 
1318 inline std::string stree::desc_sub(bool all) const
1319 {
1320     unsigned num = subs_freq->get_num();
1321     std::stringstream ss ;
1322     for(unsigned i=0 ; i < num ; i++)
1323     {
1324         const char* sub = subs_freq->get_key(i);
1325         int freq = subs_freq->get_freq(i);   // -ve freq means disqualified
1326         if(all == false && freq < FREQ_CUT) continue ;
1327         ss << desc_sub(sub) << std::endl ;
1328     }
1329     std::string str = ss.str();
1330     return str ;
1331 }
1332 
1333 inline std::string stree::desc_sub(const char* sub) const
1334 {
1335     int first_nidx = get_first(sub);
1336 
1337     unsigned mn, mx ;
1338     get_depth_range(mn,mx,sub);
1339 
1340     std::stringstream ss ;
1341     ss << subs_freq->desc(sub)
1342        << " de:"
1343        << "(" << std::setw(2) << mn
1344        << " " << std::setw(2) << mx
1345        << ")"
1346        << " 1st:" << std::setw(6) << first_nidx
1347        << " " <<  get_soname(first_nidx)
1348        ;
1349     std::string str = ss.str();
1350     return str ;
1351 }
1352 
1353 
1354 
1355 /**
1356 stree::Digest
1357 ----------------------
1358 
1359 Progeny digest needs to include transforms + lvid of subnodes,
1360 but only lvid of the upper subtree node.
1361 
1362 **/
1363 
1364 inline std::string stree::Digest(int lvid, const glm::tmat4x4<double>& tr ) // static
1365 {
1366     sdigest u ;
1367     u.add( lvid );
1368     u.add( (char*)glm::value_ptr(tr), sizeof(double)*16 ) ;
1369     std::string dig = u.finalize();
1370     return dig ;
1371 }
1372 
1373 inline std::string stree::Digest(int lvid ) // static
1374 {
1375     return sdigest::Int(lvid);
1376 }
1377 
1378 
1379 template<typename T>
1380 inline int stree::GetPointerIndex( const std::vector<const T*>& vec, const T* obj) // static
1381 {
1382     if( obj == nullptr || vec.size() == 0 ) return -1 ;
1383     size_t idx = std::distance( vec.begin(), std::find(vec.begin(), vec.end(), obj ));
1384     return idx < vec.size() ? int(idx) : -1 ;
1385 }
1386 
1387 template<typename T>
1388 inline int stree::GetValueIndex( const std::vector<T>& vec, const T& obj) // static
1389 {
1390     size_t idx = std::distance( vec.begin(), std::find(vec.begin(), vec.end(), obj ));
1391     return idx < vec.size() ? int(idx) : -1 ;
1392 }
1393 
1394 
1395 
1396 inline void stree::get_children( std::vector<int>& children , int nidx ) const
1397 {
1398     int num_nd = nds.size();
1399     bool nidx_expect = nidx < num_nd ;
1400     if(!nidx_expect) std::cerr
1401         << "stree::get_children"
1402         << " nidx_expect " << ( nidx_expect ? "YES" : "NO ")
1403         << " num_nd " << num_nd
1404         << " nidx " << nidx
1405         << "\n"
1406         ;
1407     if(!nidx_expect) return ;
1408 
1409 
1410     const snode& nd = nds[nidx];
1411     bool nd_expect = nd.index == nidx ;
1412     if(!nd_expect) std::cerr
1413         << "stree::get_children"
1414         << " nd.index " << nd.index
1415         << " nidx " << nidx
1416         << " num_nd " << num_nd
1417         << " nidx_expect " << ( nidx_expect ? "YES" : "NO " )
1418         << " nd_expect " << ( nd_expect ? "YES" : "NO " )
1419         << "\n"
1420         ;
1421     assert( nd_expect );
1422 
1423     int ch = nd.first_child ;
1424     while( ch > -1 )
1425     {
1426         const snode& child = nds[ch] ;
1427         assert( child.parent == nd.index );
1428         children.push_back(child.index);
1429         ch = child.next_sibling ;
1430     }
1431     assert( int(children.size()) == nd.num_child );
1432 }
1433 
1434 inline void stree::get_progeny( std::vector<int>& progeny , int nidx ) const
1435 {
1436     std::vector<int> children ;
1437     get_children(children, nidx);
1438     std::copy(children.begin(), children.end(), std::back_inserter(progeny));
1439     for(unsigned i=0 ; i < children.size() ; i++) get_progeny(progeny, children[i] );
1440 }
1441 
1442 
1443 inline std::string stree::desc_progeny(int nidx, int edge) const
1444 {
1445     int num_nd = nds.size();
1446     bool nidx_valid = nidx < num_nd ;
1447 
1448     std::stringstream ss ;
1449     ss << "stree::desc_progeny\n"
1450        << " nidx " << nidx << "\n"
1451        << " nidx_valid " << ( nidx_valid ? "YES" : "NO " ) << "\n"
1452        ;
1453 
1454 
1455     if( nidx_valid )
1456     {
1457         std::vector<int> progeny ;
1458         get_progeny(progeny, nidx );
1459         sfreq* sf = make_freq(progeny);
1460         sf->sort();
1461         int num_progeny = progeny.size() ;
1462 
1463         ss
1464            << " num_progeny " << num_progeny << "\n"
1465            << " edge " << edge << "\n"
1466            << "[sf.desc\n"
1467            << sf->desc()
1468            << "]sf.desc\n"
1469            << " i " << std::setw(6) << -1
1470            << desc_node(nidx, true )
1471            << "\n"
1472            ;
1473 
1474 
1475         for(int i=0 ; i < num_progeny ; i++)
1476         {
1477             int nix = progeny[i] ;
1478             int depth = get_depth(nix);
1479 
1480             if( edge == 0 || i < edge || i > num_progeny - edge )
1481             {
1482                 ss
1483                     << " i " << std::setw(6) << i
1484                     << " depth " << std::setw(2) << depth
1485                     << desc_node_(nix, sf )
1486                     << "\n"
1487                     ;
1488             }
1489             else if( i == edge )
1490             {
1491                 ss
1492                     << " i " << std::setw(6) << i
1493                     << " depth " << std::setw(2) << depth
1494                     << " ... "
1495                     << "\n"
1496                     ;
1497             }
1498         }
1499     }
1500 
1501 
1502     std::string str = ss.str();
1503     return str ;
1504 }
1505 
1506 
1507 inline void stree::traverse(int nidx) const
1508 {
1509     traverse_r(nidx, 0, -1);
1510 }
1511 
1512 inline void stree::traverse_r(int nidx, int depth, int sibdex) const
1513 {
1514     std::vector<int> children ;
1515     get_children(children, nidx);
1516 
1517     const snode& nd = nds[nidx] ;
1518 
1519     assert( nd.index == nidx );
1520     assert( nd.depth == depth );
1521     assert( nd.sibdex == sibdex );
1522     assert( nd.num_child == int(children.size()) );
1523 
1524     const char* so = get_soname(nidx);
1525 
1526     if(nd.sensor_id > -1 )
1527     if(level > 0) std::cout
1528         << "stree::traverse_r"
1529         << " "
1530         << nd.desc()
1531         << " so " << so
1532         << std::endl
1533         ;
1534 
1535     for(unsigned i=0 ; i < children.size() ; i++) traverse_r(children[i], depth+1, i );
1536 }
1537 
1538 
1539 /**
1540 stree::reorderSensors : changes nd.sensor_index across entire tree
1541 ----------------------------------------------------------------------
1542 
1543 Invoked from U4Tree::identifySensitive
1544 
1545 1. recursive node traverse changing nd.sensor_index
1546    into preorder traverse order
1547 
1548 2. node loop collecting nd.sensor_id and updating
1549    stree::sensor_id vector
1550 
1551 
1552 This attempts to mimic the preorder traverse sensor order
1553 used by GGeo/CSG_GGeo to facilitate comparison.
1554 
1555 HMM: I expect the same thing could be done by simply iterating over nds
1556 as the snode are collected in preorder ?
1557 
1558 **/
1559 
1560 inline void stree::reorderSensors()
1561 {
1562     if(level > 0) std::cout
1563         << "[ stree::reorderSensors"
1564         << std::endl
1565         ;
1566 
1567     sensor_count = 0 ;
1568     reorderSensors_r(0);
1569 
1570     if(level > 0) std::cout
1571         << "] stree::reorderSensors"
1572         << " sensor_count " << sensor_count
1573         << std::endl
1574         ;
1575 
1576     // change sensor_id vector by looping over
1577     // all nodes collecting it when > -1
1578     get_sensor_id(sensor_id);
1579 
1580     assert( sensor_count == sensor_id.size() );
1581 }
1582 
1583 /**
1584 stree::reorderSensors_r
1585 ------------------------
1586 
1587 For nodes with sensor_id > -1 change the sensor_index
1588 into a 0-based preorder traversal count index.
1589 
1590 **/
1591 
1592 inline void stree::reorderSensors_r(int nidx)
1593 {
1594     snode& nd = nds[nidx] ;
1595 
1596     if( nd.sensor_id > -1 )
1597     {
1598         nd.sensor_index = sensor_count ;
1599         sensor_count += 1 ;
1600     }
1601 
1602     std::vector<int> children ;
1603     get_children(children, nidx);
1604     for(unsigned i=0 ; i < children.size() ; i++) reorderSensors_r(children[i]);
1605 }
1606 
1607 
1608 /**
1609 stree::get_sensor_id from snode nds
1610 -------------------------------------
1611 
1612 List *nd.sensor_id* obtained by iterating over all *nds* of the geometry.
1613 As the *nds* vector is in preorder traversal order, the order of
1614 the *sensor_id* should correspond to *sensor_index* from 0 to num_sensor-1.
1615 
1616 **/
1617 
1618 inline void stree::get_sensor_id( std::vector<int>& arg_sensor_id ) const
1619 {
1620     arg_sensor_id.clear();
1621     for(unsigned nidx=0 ; nidx < nds.size() ; nidx++)
1622     {
1623         const snode& nd = nds[nidx] ;
1624         if( nd.sensor_id > -1 ) arg_sensor_id.push_back(nd.sensor_id) ;
1625     }
1626 }
1627 
1628 
1629 /**
1630 stree::find_nodes_with_sensor_id
1631 ---------------------------------
1632 
1633 off-by-one suspicion::
1634 
1635     MOI=SID:50055 cxr_min.sh  # staked PMT
1636 
1637 But the identity from cxt_min.sh is 50056
1638 
1639 **/
1640 
1641 
1642 inline void stree::find_nodes_with_sensor_id( std::vector<snode>& nodes, int q_sensor_id ) const
1643 {
1644     unsigned num_nd = nds.size();
1645     for(unsigned nidx=0 ; nidx < num_nd ; nidx++)
1646     {
1647         const snode& nd = nds[nidx] ;
1648         if( nd.sensor_id == q_sensor_id ) nodes.push_back(nd);
1649     }
1650 }
1651 inline int stree::get_ordinal_nidx_with_sensor_id( int q_sensor_id, int ordinal ) const
1652 {
1653     std::vector<snode> nodes ;
1654     find_nodes_with_sensor_id( nodes, q_sensor_id );
1655     int num = nodes.size() ;
1656     return ordinal > -1 && ordinal < num ? nodes[ordinal].index : -1 ;
1657 }
1658 
1659 
1660 
1661 inline void stree::find_nodes_with_sensor_index( std::vector<snode>& nodes, int q_sensor_index ) const
1662 {
1663     unsigned num_nd = nds.size();
1664     for(unsigned nidx=0 ; nidx < num_nd ; nidx++)
1665     {
1666         const snode& nd = nds[nidx] ;
1667         if( nd.sensor_index == q_sensor_index ) nodes.push_back(nd);
1668     }
1669 }
1670 inline int stree::get_ordinal_nidx_with_sensor_index( int q_sensor_index, int ordinal ) const
1671 {
1672     std::vector<snode> nodes ;
1673     find_nodes_with_sensor_index( nodes, q_sensor_index );
1674     int num = nodes.size() ;
1675     return ordinal > -1 && ordinal < num ? nodes[ordinal].index : -1 ;
1676 }
1677 
1678 
1679 
1680 
1681 
1682 
1683 
1684 /**
1685 stree::postcreate
1686 ------------------
1687 
1688 Called for reporting from U4Tree::Create
1689 
1690 **/
1691 
1692 inline void stree::postcreate() const
1693 {
1694     std::cout << "[stree::postcreate" << std::endl ;
1695 
1696     std::cout << desc_sensor() ;
1697     std::cout << desc_sensor_nd(0) ;
1698     std::cout << desc_sensor_id(10) ;
1699 
1700     std::cout << "]stree::postcreate" << std::endl ;
1701 }
1702 
1703 inline std::string stree::desc_sensor() const
1704 {
1705     int num_sensor = sensor_name.size() ;
1706     std::stringstream ss ;
1707     ss << "stree::desc_sensor" << std::endl
1708        << " sensor_id.size " << sensor_id.size() << std::endl
1709        << " sensor_count " << sensor_count << std::endl
1710        << " sensor_name.size " << num_sensor << std::endl
1711        ;
1712 
1713     int edgeitems = 20 ;
1714 
1715     ss << "sensor_name[" << std::endl  ;
1716     for(int i=0; i < num_sensor ; i++)
1717     {
1718         if( i < edgeitems || i > num_sensor - edgeitems )
1719         {
1720             ss << sensor_name[i].c_str() << std::endl ;
1721         }
1722         else if( i == edgeitems )
1723         {
1724             ss << "..." << std::endl ;
1725         }
1726     }
1727     ss << "]" << std::endl  ;
1728     std::string str = ss.str();
1729     return str ;
1730 }
1731 
1732 inline int stree::get_num_nd_sensor() const
1733 {
1734     int num_nd = nds.size() ;
1735     int num_nd_sensor = 0 ;
1736     for(int nidx=0 ; nidx < num_nd ; nidx++) if(nds[nidx].sensor_id > -1) num_nd_sensor += 1 ;
1737     return num_nd_sensor ;
1738 }
1739 
1740 inline void stree::get_sensor_nidx( std::vector<int>& sensor_nidx ) const
1741 {
1742     int num_nd = nds.size() ;
1743     for(int nidx=0 ; nidx < num_nd ; nidx++)
1744         if(nds[nidx].sensor_id > -1 )
1745             sensor_nidx.push_back(nidx) ;
1746 }
1747 
1748 /**
1749 stree::desc_sensor_nd
1750 -----------------------
1751 
1752 
1753 **/
1754 
1755 
1756 inline std::string stree::desc_sensor_nd(int edge) const
1757 {
1758     int num_nd = nds.size() ;
1759     int num_nd_sensor = get_num_nd_sensor() ;
1760 
1761     std::vector<int> sensor_nidx ;
1762     get_sensor_nidx(sensor_nidx);
1763 
1764     int num_sid = sensor_nidx.size() ;
1765     assert( num_sid == num_nd_sensor );
1766 
1767     std::stringstream ss ;
1768     ss << "[stree::desc_sensor_nd" << std::endl ;
1769     ss << " edge            " << edge << std::endl ;
1770     ss << " num_nd          " << num_nd << std::endl ;
1771     ss << " num_nd_sensor   " << num_nd_sensor << std::endl ;
1772     ss << " num_sid         " << num_sid << std::endl ;
1773 
1774     int offset = -1 ;
1775 
1776     for(int i=0 ; i < num_sid ; i++)
1777     {
1778         int nidx = sensor_nidx[i] ;
1779         int n_nidx = i < num_sid - 1 ? sensor_nidx[i+1] : sensor_nidx[i] ;
1780 
1781         const snode& nd = nds[nidx] ;
1782         const snode& n_nd = nds[n_nidx] ;
1783 
1784         int sid = nd.sensor_id ;
1785         int n_sid = n_nd.sensor_id ;
1786 
1787         assert( sid > -1 );
1788         assert( n_sid > -1 );
1789 
1790         bool head = i < edge ;
1791         bool tail = (i > (num_sid - edge)) ;
1792         bool tran = std::abs(n_sid - sid) > 1 ;
1793 
1794         if(tran) offset=0 ;
1795         bool tran_post = offset > -1 && offset < 4 ;
1796 
1797         if(head || tail || tran || tran_post)
1798         {
1799             ss
1800                 << " nidx " << std::setw(6) << nidx
1801                 << " i " << std::setw(6) << i
1802                 << " sensor_id " << std::setw(6) << nd.sensor_id
1803                 << " sensor_index " << std::setw(6) << nd.sensor_index
1804                 << " sensor_name " << std::setw(6) << nd.sensor_name
1805                 << std::endl
1806                 ;
1807         }
1808         else if(i == edge)
1809         {
1810             ss << "..." << std::endl ;
1811         }
1812         offset += 1 ;
1813     }
1814     ss << "]stree::desc_sensor_nd" << std::endl ;
1815     std::string str = ss.str();
1816     return str ;
1817 }
1818 
1819 
1820 
1821 inline std::string stree::desc_sensor_id(unsigned edge) const
1822 {
1823     unsigned num_sid = sensor_id.size() ;
1824     std::stringstream ss ;
1825     ss << "stree::desc_sensor_id sensor_id.size "
1826        << num_sid
1827        << std::endl
1828        << "["
1829        << std::endl
1830        ;
1831 
1832     int offset = -1 ;
1833     for(unsigned i=0 ; i < num_sid ; i++)
1834     {
1835         int sid = sensor_id[i] ;
1836         int nid = i < num_sid - 1 ? sensor_id[i+1] : sid ;
1837 
1838         bool head = i < edge ;
1839         bool tail = (i > (num_sid - edge)) ;
1840         bool tran = std::abs(nid - sid) > 1 ;
1841 
1842         if(tran) offset=0 ;
1843         bool tran_post = offset > -1 && offset < 4 ;
1844 
1845         if(head || tail || tran || tran_post)
1846         {
1847             ss << std::setw(7) << i << " sid " << std::setw(8) << sid << std::endl ;
1848         }
1849         else if(i == edge)
1850         {
1851             ss << "..." << std::endl ;
1852         }
1853         offset += 1 ;
1854     }
1855     ss << "]" ;
1856     std::string s = ss.str();
1857     return s ;
1858 }
1859 
1860 inline std::string stree::DescSensor( const std::vector<int>& sensor_identifier, const std::vector<int>& sensor_index, unsigned edge )  // static
1861 {
1862     assert( sensor_identifier.size() == sensor_index.size() );
1863     unsigned num_sensor = sensor_identifier.size() ;
1864 
1865     std::stringstream ss ;
1866     ss << "stree::DescSensor num_sensor " << num_sensor << std::endl ;
1867     unsigned offset = 0 ;
1868     for(unsigned i=0 ; i < num_sensor ; i++)
1869     {
1870         int s_index      = sensor_index[i] ;
1871         int s_identifier = sensor_identifier[i] ;
1872         int n_identifier = i < num_sensor - 1 ? sensor_identifier[i+1] : s_identifier ;
1873 
1874         bool jump = std::abs(n_identifier - s_identifier) > 1 ; // sensor identifier transition
1875         if(jump) offset = 0 ;
1876 
1877         if( i < edge || i > num_sensor - edge || offset < 5 )
1878         {
1879             ss
1880                 << " i " << std::setw(7) << i
1881                 << " s_index " << std::setw(7) << s_index
1882                 << " s_identifier " << std::setw(7) << s_identifier
1883                 << std::endl
1884                 ;
1885         }
1886         else if( i == edge || i == num_sensor - edge )
1887         {
1888             ss << "..." << std::endl ;
1889         }
1890         offset += 1 ;
1891     }
1892 
1893     std::string s = ss.str();
1894     return s ;
1895 }
1896 
1897 
1898 
1899 
1900 
1901 /**
1902 stree::lookup_sensor_identifier
1903 ---------------------------------
1904 
1905 1. arg_sensor_identifier vector is resized to match the size of arg_sensor_index
1906 2. for arg_sensor_index that are not "not-a-sensor" returns s_identifier from stree::sensor_id vector
1907 
1908 This is used from CSG_GGeo_Convert::addInstances
1909 
1910 **/
1911 
1912 inline void stree::lookup_sensor_identifier(
1913              std::vector<int>& arg_sensor_identifier,
1914        const std::vector<int>& arg_sensor_index,
1915        bool one_based_index,
1916        bool verbose,
1917        unsigned edge ) const
1918 {
1919     if(verbose) std::cerr
1920          << "stree::lookup_sensor_identifier.0"
1921          << " arg_sensor_identifier.size " << arg_sensor_identifier.size()
1922          << " arg_sensor_index.size " << arg_sensor_index.size()
1923          << " sensor_id.size " << sensor_id.size()
1924          << " edge " << edge
1925          << std::endl
1926          ;
1927 
1928     arg_sensor_identifier.resize(arg_sensor_index.size());
1929 
1930     unsigned num_lookup = arg_sensor_index.size() ;
1931     for(unsigned i=0 ; i < num_lookup ; i++)
1932     {
1933         int s_index = one_based_index ? arg_sensor_index[i] - 1 : arg_sensor_index[i] ;
1934         // "correct" 1-based to be 0-based
1935         bool s_index_inrange = s_index > -1 && s_index < int(sensor_id.size()) ;
1936         int s_identifier = s_index_inrange ? sensor_id[s_index] : -1 ;
1937         arg_sensor_identifier[i] = s_identifier ;
1938 
1939         if(verbose)
1940         {
1941             if( i < edge || i > num_lookup - edge)
1942             {
1943                 std::cerr
1944                     << "stree::lookup_sensor_identifier.1"
1945                     << " i " << std::setw(3) << i
1946                     << " s_index " << std::setw(7) << s_index
1947                     << " s_index_inrange " << std::setw(1) << s_index_inrange
1948                     << " s_identifier " << std::setw(7) << s_identifier
1949                     << " sensor_id.size " << std::setw(7) << sensor_id.size()
1950                     << std::endl
1951                     ;
1952             }
1953             else if( i == edge )
1954             {
1955                 std::cerr
1956                     << "stree::lookup_sensor_identifier.1"
1957                     << " i " << std::setw(3) << i
1958                     << " ... "
1959                     << std::endl
1960                     ;
1961             }
1962         }      // verbose
1963     }          // over num_lookup
1964 }
1965 
1966 
1967 inline sfreq* stree::make_progeny_freq(int nidx) const
1968 {
1969     std::vector<int> progeny ;
1970     get_progeny(progeny, nidx );
1971     return make_freq(progeny);
1972 }
1973 
1974 inline sfreq* stree::make_freq(const std::vector<int>& nodes ) const
1975 {
1976     sfreq* sf = new sfreq ;
1977     for(unsigned i=0 ; i < nodes.size() ; i++)
1978     {
1979         int nidx = nodes[i];
1980         sf->add(get_sub(nidx));
1981     }
1982     return sf ;
1983 }
1984 
1985 /**
1986 stree::find_lvid
1987 ------------------
1988 
1989 Find lvid index of solid with name q_soname
1990 
1991 **/
1992 
1993 inline int stree::find_lvid(const char* q_soname, bool starting ) const
1994 {
1995     int lvid = -1 ;
1996     for(unsigned i=0 ; i < soname.size() ; i++)
1997     {
1998         const char* name = soname[i].c_str();
1999         if(sstr::Match( name, q_soname, starting))
2000         {
2001             lvid = i ;
2002             break ;
2003         }
2004     }
2005     return lvid ;
2006 }
2007 
2008 
2009 /**
2010 stree::get_node_vector
2011 -----------------------
2012 
2013 The *nds* vector includes all the structural nodes (aka volumes) with the
2014 *rem* and *tri* vectors being subsets of those.
2015 
2016 **/
2017 
2018 inline const std::vector<snode>* stree::get_node_vector( char _src ) const
2019 {
2020     const std::vector<snode>* src = nullptr ;
2021     switch( _src )
2022     {
2023         case 'N': src = &nds ; break ;
2024         case 'R': src = &rem ; break ;
2025         case 'T': src = &tri ; break ;
2026     }
2027     return src ;
2028 }
2029 
2030 inline const char* stree::get_node_vector_name( char _src ) const
2031 {
2032     const char* name = nullptr ;
2033     switch( _src )
2034     {
2035         case 'N': name = NDS ; break ;
2036         case 'R': name = REM ; break ;
2037         case 'T': name = TRI ; break ;
2038     }
2039     return name ;
2040 }
2041 
2042 
2043 
2044 
2045 
2046 /**
2047 stree::find_lvid_nodes_
2048 -------------------------
2049 
2050 Collect all snode from src vector nds/rem/tri which have the provided lvid shape into nodes vector.
2051 
2052 **/
2053 
2054 inline void stree::find_lvid_nodes_( std::vector<snode>& nodes, int lvid, char _src ) const
2055 {
2056     const std::vector<snode>* src = get_node_vector(_src);
2057     for(unsigned i=0 ; i < src->size() ; i++)
2058     {
2059         const snode& sn = (*src)[i] ;
2060         if( _src == 'N' )
2061         {
2062             assert( int(i) == sn.index );
2063         }
2064         if(sn.lvid == lvid) nodes.push_back(sn) ;
2065     }
2066 }
2067 
2068 /**
2069 stree::find_lvid_nodes
2070 -----------------------
2071 
2072 Collect all snode::index from src vector nds/rem/tri which have the provided lvid shape into nodes vector.
2073 NB this should correpond to the absolute nidx indices not the indices into the selected src (unless the
2074 src is nds which corresponds to all nodes)
2075 
2076 **/
2077 
2078 inline void stree::find_lvid_nodes( std::vector<int>& nodes, int q_lvid, char _src ) const
2079 {
2080     const std::vector<snode>* src = get_node_vector(_src);
2081     for(unsigned i=0 ; i < src->size() ; i++)
2082     {
2083         const snode& sn = (*src)[i] ;
2084         if( _src == 'N' )
2085         {
2086             assert( int(i) == sn.index );
2087         }
2088         if(sn.lvid == q_lvid) nodes.push_back(sn.index) ;
2089     }
2090 }
2091 
2092 inline void stree::find_lvid_copyno_nodes( std::vector<int>& nodes, int q_lvid, int q_copyno, char _src ) const
2093 {
2094     const std::vector<snode>* src = get_node_vector(_src);
2095     for(unsigned i=0 ; i < src->size() ; i++)
2096     {
2097         const snode& sn = (*src)[i] ;
2098         if( _src == 'N' )
2099         {
2100             assert( int(i) == sn.index );
2101         }
2102         if(sn.lvid == q_lvid && sn.copyno == q_copyno) nodes.push_back(sn.index) ;
2103     }
2104 }
2105 
2106 
2107 
2108 
2109 
2110 
2111 
2112 
2113 
2114 
2115 
2116 
2117 /**
2118 stree::find_lvid_nodes
2119 -------------------------
2120 
2121 1. lookup int:lvid from q_soname, for starting:true only a string start match is used
2122 2. collect snode::index of all structural snode with int:lvid
2123 
2124 **/
2125 
2126 inline void stree::find_lvid_nodes( std::vector<int>& nodes, const char* q_soname, bool starting ) const
2127 {
2128     int lvid = find_lvid(q_soname, starting);
2129     find_lvid_nodes(nodes, lvid, 'N' );
2130 }
2131 
2132 
2133 inline int stree::count_lvid_nodes( int lvid, char _src ) const
2134 {
2135     std::vector<int> nodes ;
2136     find_lvid_nodes( nodes, lvid, _src );
2137     return nodes.size();
2138 }
2139 
2140 
2141 
2142 /**
2143 stree::find_lvid_node
2144 ---------------------
2145 
2146 1. find all lvid node index
2147 2. return the ordinal-th node index
2148 
2149 **/
2150 inline int stree::find_lvid_node( const char* q_soname, int ordinal ) const
2151 {
2152     std::vector<int> nodes ;
2153     bool starting = true ;
2154     find_lvid_nodes(nodes, q_soname, starting );
2155     if(ordinal < 0) ordinal += nodes.size() ; // -ve ordinal counts from back
2156 
2157     return ordinal > -1 && ordinal < int(nodes.size()) ? nodes[ordinal] : -1 ;
2158 }
2159 
2160 /**
2161 stree::find_lvid_node
2162 ----------------------
2163 
2164 1. split q_spec on ':' into vector of elements (str:q_soname, int:middle, int:q_ordinal)
2165 
2166    * default ints are zero
2167    * HMM: MIDDLE REQUIRED TO BE ZERO CURRENTLY
2168 
2169 2. lookup nidx using (str:q_soname,int:ordinal)
2170 
2171 
2172 **/
2173 inline int stree::find_lvid_node( const char* q_spec ) const
2174 {
2175     std::vector<std::string> elem ;
2176     sstr::Split(q_spec, ':', elem );
2177 
2178     const char* q_soname  = elem.size() > 0 ? elem[0].c_str() : nullptr ;
2179     const char* q_middle  = elem.size() > 1 ? elem[1].c_str() : nullptr ;
2180     const char* q_ordinal = elem.size() > 2 ? elem[2].c_str() : nullptr ;
2181 
2182     int middle  = q_middle  ? std::atoi(q_middle)  : 0 ;
2183     int ordinal = q_ordinal ? std::atoi(q_ordinal) : 0 ;
2184 
2185     bool middle_expect = middle == 0  ;
2186     if(!middle_expect) std::cerr << "stree::find_lvid_node middle_expect " << std::endl ;
2187     assert( middle_expect ); // slated for use with global addressing (like MOI)
2188 
2189     int nidx = find_lvid_node(q_soname, ordinal);
2190     return nidx ;
2191 }
2192 
2193 
2194 
2195 
2196 
2197 
2198 
2199 
2200 
2201 
2202 
2203 /**
2204 stree::pick_lvid_ordinal_node
2205 -------------------------------
2206 
2207 For ridx_type '?' look for the frame first using rem 'R' nodes and then tri 'T' nodes
2208 
2209 **/
2210 inline const snode* stree::pick_lvid_ordinal_node( int lvid, int lvid_ordinal, char ridx_type  ) const
2211 {
2212     const snode* _node = nullptr ;
2213     assert( ridx_type == 'R' || ridx_type == 'T' || ridx_type == '?' );
2214     if( ridx_type == 'R' || ridx_type == 'T' )  // remainder OR triangulated
2215     {
2216         _node = _pick_lvid_ordinal_node(lvid, lvid_ordinal, ridx_type );
2217     }
2218     else if( ridx_type == '?' )
2219     {
2220         _node = _pick_lvid_ordinal_node(lvid, lvid_ordinal, 'R' );
2221         if(_node == nullptr)
2222         {
2223             _node = _pick_lvid_ordinal_node(lvid, lvid_ordinal, 'T' );
2224         }
2225     }
2226     return _node ;
2227 }
2228 
2229 
2230 /**
2231 stree::_pick_lvid_ordinal_node
2232 -------------------------------
2233 
2234 Returns selected node (pointer into nds vector)
2235 
2236 1. collect indices of all snode (volumes) with lvid shape from the ridx_type vector nds/rem/tri
2237 2. select the ordinal-th snode (-ve ordinal counts from back of the selected set)
2238 3. use selected nn node index from find_lvid_nodes, which is an absolute nidx
2239    to return pointer into the nds vector or nullptr
2240 
2241 **/
2242 inline const snode* stree::_pick_lvid_ordinal_node( int lvid, int lvid_ordinal, char ridx_type  ) const
2243 {
2244     std::vector<int> nn ;
2245     find_lvid_nodes( nn, lvid, ridx_type );
2246 
2247     int num = nn.size() ;
2248     if( lvid_ordinal < 0 ) lvid_ordinal += num ;
2249     bool valid =  lvid_ordinal > -1 && lvid_ordinal < num ;
2250     int nidx = valid ? nn[lvid_ordinal] : -1 ;
2251 
2252     const snode* nds_data = nds.data() ;
2253     return nidx > -1 && nidx < int(nds.size()) ? nds_data + nidx : nullptr ;
2254 }
2255 
2256 /**
2257 stree::pick_lvid_ordinal_repeat_ordinal_inst_
2258 ----------------------------------------------
2259 
2260 1. pick_lvid_ordinal_node provides the snode, giving the repeat_index (aka gas_idx)
2261 2. find_inst_gas uses that repeat_index and the repeat_ordinal to access the instance index
2262 
2263 This is trying to repeat the MOI logic from CSGTarget::getInstanceTransform
2264 
2265 **/
2266 
2267 inline int stree::pick_lvid_ordinal_repeat_ordinal_inst_( int lvid, int lvid_ordinal, int repeat_ordinal ) const
2268 {
2269     const snode* n = _pick_lvid_ordinal_node(lvid, lvid_ordinal, 'N' );
2270     if( n == nullptr ) return -1 ;
2271 
2272     int q_gas_idx = n->repeat_index ;  // aka gas_idx
2273     int q_gas_ordinal = repeat_ordinal ;
2274     int inst_idx = find_inst_gas( q_gas_idx, q_gas_ordinal );
2275 
2276     return inst_idx ;
2277 }
2278 
2279 
2280 /**
2281 stree::parse_spec
2282 ------------------
2283 
2284 Parse string of below form into lvid index by name lookup
2285 from 1st field and integers lvid_ordinal repeat_ordinal from 2nd and 3rd::
2286 
2287     Hama:0:0
2288     NNVT:0:0
2289     Hama:0:1000
2290     NNVT:0:1000
2291     sDeadWater:0:0
2292     GZ1.A06_07_FlangeI_Web_FlangeII:0:0
2293     GZ1.B06_07_FlangeI_Web_FlangeII:0:0
2294     GZ1.A06_07_FlangeI_Web_FlangeII:15:0
2295     GZ1.B06_07_FlangeI_Web_FlangeII:15:0
2296     0:0:0
2297 
2298 When no 2nd and 3rd field is provided eg with "sDeadWater" the
2299 ordinals default to 0.
2300 
2301 A integer string in the first field is converted to lvid int.
2302 
2303 TODO: get this to ignore comments in the q_spec line like::
2304 
2305     sDeadWater:0:-1   # some comment
2306 
2307 **/
2308 
2309 inline int stree::parse_spec(
2310     int& lvid,
2311     int& lvid_ordinal,
2312     int& repeat_ordinal,
2313     const char* q_spec ) const
2314 {
2315     std::vector<std::string> elem ;
2316     sstr::Split(q_spec, ':', elem );
2317 
2318     const char* q_soname  = elem.size() > 0 ? elem[0].c_str() : nullptr ;
2319     const char* q_lvid_ordinal  = elem.size() > 1 ? elem[1].c_str() : nullptr ;
2320     const char* q_repeat_ordinal = elem.size() > 2 ? elem[2].c_str() : nullptr ;
2321 
2322 
2323     if(sstr::IsInteger(q_soname))
2324     {
2325         lvid = sstr::To<int>(q_soname) ;
2326     }
2327     else
2328     {
2329         bool starting = true ;
2330         lvid = find_lvid(q_soname, starting);
2331     }
2332 
2333     if(lvid == -1 )
2334     {
2335         std::cerr << "stree::parse_spec FAILED to find lvid for q_soname [" << ( q_soname ? q_soname : "-" ) << "]\n" ;
2336         std::cerr << desc_soname() ;
2337     }
2338     if( lvid == -1 ) return -1 ;
2339 
2340     lvid_ordinal = q_lvid_ordinal  ? std::atoi(q_lvid_ordinal) : 0 ;
2341     repeat_ordinal = q_repeat_ordinal ? std::atoi(q_repeat_ordinal)  : 0 ;
2342 
2343     return 0 ;
2344 }
2345 
2346 inline int stree::pick_lvid_ordinal_repeat_ordinal_inst( const char* q_spec ) const
2347 {
2348     int lvid ;
2349     int lvid_ordinal ;
2350     int repeat_ordinal ;
2351     [[maybe_unused]] int rc = parse_spec( lvid, lvid_ordinal, repeat_ordinal, q_spec );
2352     assert( rc == 0 );
2353     int inst_idx = pick_lvid_ordinal_repeat_ordinal_inst_( lvid, lvid_ordinal, repeat_ordinal );
2354     return inst_idx ;
2355 }
2356 
2357 
2358 
2359 /**
2360 stree::get_frame_f4
2361 --------------------
2362 
2363 transitional method to match with CSGFoundry::getFrame
2364 
2365 See ~/o/notes/issues/sframe_dtor_double_free_from_CSGOptiX__initFrame.rst
2366 
2367 **/
2368 
2369 inline void stree::get_frame_f4( sframe& fr, int idx ) const
2370 {
2371     typedef glm::tmat4x4<float> M44 ;
2372 
2373     const M44* _m2w = get_inst_f4(idx);
2374     const M44* _w2m = get_iinst_f4(idx);
2375 
2376     assert( sizeof(M44) == sizeof(fr.m2w ) );
2377     memcpy( fr.m2w.data(), _m2w , sizeof(M44) );
2378     memcpy( fr.w2m.data(), _w2m , sizeof(M44) );
2379 }
2380 
2381 
2382 /**
2383 stree::get_frame_moi
2384 ---------------------
2385 
2386 This is invoked from high level initialization stacks such as::
2387 
2388     CSGOptiXRenderInteractiveTest::init
2389     SGLM::setTreeScene
2390 
2391 Special cased MOI envvar starting "EXTENT:" normally MOI is of the below form::
2392 
2393     sWaterTube:0:-1
2394 
2395 
2396 **/
2397 
2398 inline sfr stree::get_frame_moi() const
2399 {
2400     return get_frame(MOI);
2401 }
2402 
2403 
2404 /**
2405 stree::get_frame
2406 -----------------
2407 
2408 
2409 Q: An instance may encompasses multiple lv (and multiple snode)
2410    so which nidx is collected together with the inst
2411    transforms into inst_nidx ? The outer one would be most useful.
2412 
2413 A: By observation the outer instance node is collected into inst_nidx
2414 
2415 TODO: AVOID DUPLICATION BETWEEN THIS AND CSGFoundry::getFrame
2416 
2417 **/
2418 
2419 inline sfr stree::get_frame(const char* q_spec ) const
2420 {
2421     char delim = ',' ;
2422 
2423     bool is_TOP = q_spec == nullptr || strcmp(q_spec, "-1") == 0 || strcmp(q_spec, "") == 0 ;
2424     bool is_EXTENT = sstr::StartsWith(q_spec, EXTENT_PFX) ;
2425     bool is_CE = sstr::StartsWith(q_spec, CE_PFX) ;
2426     bool is_TE = sstr::StartsWith(q_spec, TE_PFX) ;
2427     bool is_AXIS = sstr::StartsWith(q_spec,  AXIS_PFX) ;
2428     bool is_NIDX = sstr::StartsWith(q_spec,  NIDX_PFX) ;
2429     bool is_PRIM = sstr::StartsWith(q_spec,  PRIM_PFX) ;
2430     bool is_INST = sstr::StartsWith(q_spec,  INST_PFX) ;
2431     bool is_SID = sstr::StartsWith(q_spec,  SID_PFX) ;
2432     bool is_SIDX = sstr::StartsWith(q_spec,  SIDX_PFX) ;
2433     bool is_LVID_COPYNO = sstr::StartsWith(q_spec,  LVID_COPYNO_PFX) ;
2434     bool is_NPYFILE =  sstr::EndsWith(q_spec, ".npy");
2435     bool is_TRIPLET = sstr::StartsWithLetterAZaz(q_spec) || strstr(q_spec, ":") || strcmp(q_spec,"-1") == 0 ;
2436     bool is_COORDS = sstr::looks_like_list(q_spec, delim, 1, 4) ;
2437     bool is_TRANSFORM = sstr::looks_like_list(q_spec, delim, 16, 16) ;
2438 
2439     sfr f = {} ;
2440     f.set_name(q_spec);
2441     f.set_treedir(loaddir);
2442 
2443     int rc = 0 ;
2444 
2445     if( is_TOP )
2446     {
2447         f = get_frame_top() ;
2448     }
2449     else if( is_EXTENT )
2450     {
2451         f = get_frame_extent( q_spec + strlen(EXTENT_PFX) );
2452     }
2453     else if( is_CE )
2454     {
2455         f = get_frame_ce( q_spec + strlen(CE_PFX) );
2456     }
2457     else if( is_TE )
2458     {
2459         f = get_frame_te( q_spec + strlen(TE_PFX) );
2460     }
2461     else if( is_AXIS )
2462     {
2463         f = get_frame_axis( q_spec + strlen(AXIS_PFX) );
2464     }
2465     else if( is_NIDX )
2466     {
2467         f = get_frame_nidx( q_spec + strlen(NIDX_PFX) );
2468     }
2469     else if( is_PRIM )
2470     {
2471         f = get_frame_prim( q_spec + strlen(PRIM_PFX) );
2472     }
2473     else if( is_INST )
2474     {
2475         f = get_frame_inst( q_spec + strlen(INST_PFX) );
2476     }
2477     else if( is_SID )
2478     {
2479         f = get_frame_sid( q_spec + strlen(SID_PFX) );
2480     }
2481     else if( is_SIDX )
2482     {
2483         f = get_frame_sidx( q_spec + strlen(SIDX_PFX) );
2484     }
2485     else if( is_LVID_COPYNO )
2486     {
2487         f = get_frame_lvid_copyno( q_spec + strlen(LVID_COPYNO_PFX) );
2488     }
2489     else if( is_NPYFILE )
2490     {
2491         rc = get_frame_from_npyfile(f, q_spec);
2492     }
2493     else if( is_TRIPLET )
2494     {
2495         rc = get_frame_from_triplet(f, q_spec);
2496     }
2497     else if( is_COORDS )
2498     {
2499         rc = get_frame_from_coords(f, q_spec);
2500     }
2501     else if( is_TRANSFORM )
2502     {
2503         rc = get_frame_from_transform(f, q_spec);
2504     }
2505     else
2506     {
2507         rc = 1 ;
2508     }
2509 
2510     if(rc != 0) std::cerr
2511         << "stree::get_frame FAIL "
2512         << " q_spec[" << ( q_spec ? q_spec : "-" ) << "]"
2513         << " rc " << rc
2514         << "\n"
2515         ;
2516 
2517     return f ;
2518 }
2519 
2520 /**
2521 stree::get_frame_top
2522 ---------------------
2523 
2524 Contrast the simplicity of this with the contortions done in CSGFoundry::iasBB
2525 **/
2526 
2527 inline sfr stree::get_frame_top() const
2528 {
2529     return get_frame_nidx(0);
2530 }
2531 
2532 
2533 inline sfr stree::get_frame_extent(const char* s_extent ) const
2534 {
2535     return sfr::MakeFromExtent<float>( s_extent );
2536 }
2537 
2538 inline sfr stree::get_frame_ce(const char* s_ce ) const
2539 {
2540     return sfr::MakeFromCE<float>( s_ce, ',' );
2541 }
2542 inline sfr stree::get_frame_ce(const float* _ce ) const
2543 {
2544     return sfr::MakeFromCE<float>( _ce );
2545 }
2546 
2547 inline sfr stree::get_frame_te(const char* s_te ) const
2548 {
2549     return sfr::MakeFromTranslateExtent<float>( s_te, ',' );
2550 }
2551 inline sfr stree::get_frame_te(const float* _te ) const
2552 {
2553     return sfr::MakeFromTranslateExtent<float>( _te );
2554 }
2555 
2556 
2557 
2558 inline sfr stree::get_frame_axis(const char* s_axis ) const
2559 {
2560     return sfr::MakeFromAxis<float>(s_axis, ',');
2561 }
2562 
2563 /**
2564 stree::get_frame_prim
2565 -----------------------
2566 
2567 ::
2568 
2569      MOI=PRIM:691 cxr_min.sh
2570 
2571 **/
2572 
2573 inline sfr stree::get_frame_prim(const char* s_prim ) const
2574 {
2575     std::string name = PRIM_PFX ;
2576     name += s_prim ;
2577     int prim = sstr::AsInt(s_prim, -1);
2578     sfr fr = get_frame_prim( prim );
2579     fr.set_name(name.c_str());
2580     return fr ;
2581 }
2582 inline sfr stree::get_frame_nidx(const char* s_nidx ) const
2583 {
2584     std::string name = NIDX_PFX ;
2585     name += s_nidx ;
2586     int nidx = sstr::AsInt(s_nidx, -1);
2587     sfr fr = get_frame_nidx( nidx );
2588     fr.set_name(name.c_str());
2589     return fr ;
2590 }
2591 
2592 inline sfr stree::get_frame_inst(const char* s_inst ) const
2593 {
2594     std::string name = INST_PFX ;
2595     name += s_inst ;
2596     int ii = sstr::AsInt(s_inst, -1);
2597     sfr fr = get_frame_inst( ii );
2598     fr.set_name(name.c_str());
2599     return fr ;
2600 }
2601 
2602 inline sfr stree::get_frame_sid(const char* s_sid ) const
2603 {
2604     std::string name = SID_PFX ;
2605     name += s_sid ;
2606     int sid = sstr::AsInt(s_sid, -1);
2607     sfr fr = get_frame_sid( sid );
2608     fr.set_name(name.c_str());
2609     return fr ;
2610 }
2611 
2612 inline sfr stree::get_frame_sidx(const char* s_sidx ) const
2613 {
2614     std::string name = SIDX_PFX ;
2615     name += s_sidx ;
2616     int sidx = sstr::AsInt(s_sidx, -1);
2617     sfr fr = get_frame_sidx( sidx );
2618     fr.set_name(name.c_str());
2619     return fr ;
2620 }
2621 
2622 /**
2623 stree::get_frame_lvid_copyno
2624 -----------------------------
2625 
2626 ::
2627 
2628     MOI=LVID_COPYNO:66/3305 cxr_min.sh
2629     MOI=LVID_COPYNO:s_EMF_bar_box_810mm/3305 cxr_min.sh
2630 
2631 **/
2632 
2633 
2634 inline sfr stree::get_frame_lvid_copyno(const char* s_lvid_copyno ) const
2635 {
2636     std::string name = LVID_COPYNO_PFX ;
2637     name += s_lvid_copyno ;
2638 
2639     std::vector<std::string> elem ;
2640     sstr::Split(s_lvid_copyno, '/', elem );
2641 
2642     const char* s_lvid  = elem.size() > 0 ? elem[0].c_str() : nullptr ;
2643     const char* s_copyno  = elem.size() > 1 ? elem[1].c_str() : nullptr ;
2644     const char* s_src = elem.size() > 2 ? elem[2].c_str() : nullptr ;
2645 
2646     int lvid = -1 ;
2647     if(s_lvid && sstr::StartsWithLetterAZaz(s_lvid))
2648     {
2649         const char* soname = s_lvid ;
2650         bool starting = false ; // require exact match
2651         lvid = find_lvid(soname, starting);
2652         if( lvid < 0 ) std::cerr
2653             << "stree::get_frame_lvid_copyno"
2654             << " find_lvid FAILED "
2655             << " soname[" << ( soname ? soname : "-" ) << "]"
2656             << " lvid " << lvid
2657             << "\n"
2658             ;
2659     }
2660     else
2661     {
2662         lvid = s_lvid ? std::atoi(s_lvid) : 0 ;
2663     }
2664 
2665 
2666     int copyno = s_copyno ? std::atoi(s_copyno) : 0 ;
2667     char src = s_src ? s_src[0] : 'N' ;
2668     assert( src == 'N' || src == 'R' || src == 'T' );
2669 
2670     sfr fr = get_frame_lvid_copyno( lvid, copyno, src );
2671     fr.set_name(name.c_str());
2672     return fr ;
2673 }
2674 
2675 
2676 
2677 
2678 
2679 /**
2680 stree::get_frame_prim
2681 ----------------------
2682 
2683 For instanced prim this will give the frame of the
2684 first nidx node. To access the frame of a specific node of
2685 the tree rather than just an example first node
2686 use stree::get_frame_nidx
2687 
2688 **/
2689 
2690 
2691 inline sfr stree::get_frame_prim(int prim) const
2692 {
2693     int nidx = get_nidx_for_prim(prim); // many nidx will havse same prim
2694     sfr fr = get_frame_nidx(nidx);
2695     fr.set_prim(prim);
2696     return fr ;
2697 }
2698 inline sfr stree::get_frame_nidx(int nidx) const
2699 {
2700     assert( nidx > -1 );
2701     const snode& node = nds[nidx];
2702     BB bb ;
2703     get_prim_aabb( bb.data(), node, nullptr, nullptr );
2704 
2705     sfr f = {} ;
2706     f.m2w = m2w[nidx] ;
2707     f.w2m = w2m[nidx] ;
2708     s_bb::CenterExtent<double>( f.ce_data(), bb.data() );
2709     f.set_bb(bb.data());
2710     f.set_nidx(nidx);
2711 
2712     int prim = get_prim_for_nidx(nidx);
2713     f.set_prim(prim);
2714 
2715     return f ;
2716 }
2717 
2718 
2719 /**
2720 stree::get_frame_inst
2721 ----------------------
2722 
2723 THIS NEEDS TO MATCH : CSGTarget::getFrame(sframe& fr, int inst_idx )
2724 CSGImport::importInst uses inst_f4 so can do it with better precision here
2725 
2726 **/
2727 
2728 inline sfr stree::get_frame_inst(int ii) const
2729 {
2730     sfr f = {} ;
2731 
2732     std::ostream* out = nullptr ;
2733     VTR* t_stack = nullptr ;
2734 
2735     get_frame_inst(f, ii, out, t_stack );
2736 
2737     return f ;
2738 }
2739 
2740 
2741 inline sfr stree::get_frame_sid(int sid) const
2742 {
2743     std::vector<snode> nodes ;
2744     find_nodes_with_sensor_id( nodes, sid );
2745     int num = nodes.size();
2746     int nidx = num > 0 ? nodes[0].index : -1 ;
2747     if( nidx == -1 )
2748     {
2749         std::cerr
2750            << "stree::get_frame_sid"
2751            << " sid " << sid
2752            << " num " << num
2753            << " nidx " << nidx
2754            << " FAILED TO FIND NODES FOR SID - FALLBACK TO nidx 0 "
2755            << "\n"
2756            ;
2757         nidx = 0 ;
2758     }
2759     sfr f = get_frame_nidx( nidx );
2760     return f ;
2761 }
2762 
2763 
2764 inline sfr stree::get_frame_sidx(int sidx) const
2765 {
2766     std::vector<snode> nodes ;
2767     find_nodes_with_sensor_index( nodes, sidx );
2768     int num = nodes.size();
2769     int nidx = num > 0 ? nodes[0].index : -1 ;
2770     if( nidx == -1 )
2771     {
2772         std::cerr
2773            << "stree::get_frame_sidx"
2774            << " sidx " << sidx
2775            << " num " << num
2776            << " nidx " << nidx
2777            << " failed to find nodes for sidx - fallback to nidx 0 "
2778            << "\n"
2779            ;
2780         nidx = 0 ;
2781     }
2782     sfr f = get_frame_nidx( nidx );
2783     return f ;
2784 }
2785 
2786 inline sfr stree::get_frame_lvid_copyno(int q_lvid, int q_copyno, char q_src) const
2787 {
2788     std::vector<int> nodes ;
2789     find_lvid_copyno_nodes( nodes, q_lvid, q_copyno, q_src );
2790 
2791     int num = nodes.size();
2792     int nidx = num > 0 ? nodes[0] : -1 ;
2793 
2794     if( nidx == -1 )
2795     {
2796         std::cerr
2797            << "stree::get_frame_lvid_copyno"
2798            << " q_lvid " << q_lvid
2799            << " q_copyno " << q_copyno
2800            << " q_src " << q_src
2801            << " num " << num
2802            << " nidx " << nidx
2803            << " failed to find nodes for lvid_copyno - fallback to nidx 0 "
2804            << "\n"
2805            ;
2806         nidx = 0 ;
2807     }
2808     sfr f = get_frame_nidx( nidx );
2809     return f ;
2810 }
2811 
2812 
2813 
2814 
2815 
2816 
2817 
2818 inline int stree::get_frame_from_npyfile(sfr& f, const char* q_spec ) const
2819 {
2820     NP* a = NP::Load(q_spec);
2821     if(!a) return 1 ;
2822     if(a->uifc != 'f') return 2 ;
2823     if(!a->has_shape(4,4)) return 3 ;
2824 
2825 
2826     if(a->ebyte == 8)
2827     {
2828         const double* aa = a->values<double>();
2829         f.set_m2w(aa);
2830 
2831         double extent = a->get_meta<double>("extent", 1000.);
2832         f.set_extent(extent);
2833     }
2834     else if(a->ebyte == 4)
2835     {
2836         const float* aa = a->values<float>();
2837         f.set_m2w(aa);
2838 
2839         float extent = a->get_meta<float>("extent", 1000.f);
2840         f.set_extent(extent);
2841     }
2842     else
2843     {
2844         return 4;
2845     }
2846 
2847 
2848     std::cout << "stree::get_frame_from_npyfile\n" << f.desc() << "\n" ;
2849 
2850     return 0 ;
2851 }
2852 
2853 
2854 
2855 
2856 /**
2857 stree::get_frame_from_triplet
2858 -----------------------------
2859 
2860 1. parse_spec from q_spec get (lvid, lvid_ordinal, repeat_ordinal)
2861 
2862 
2863 repeat_ordinal:-1/-2/-3
2864    get_frame_global
2865 
2866 repeat_ordinal:0,...
2867    get_frame_instanced
2868 
2869 
2870 **/
2871 
2872 
2873 inline int stree::get_frame_from_triplet(sfr& f, const char* q_spec ) const
2874 {
2875     int lvid ;
2876     int lvid_ordinal ;
2877     int repeat_ordinal ;
2878     int parse_rc = parse_spec( lvid, lvid_ordinal, repeat_ordinal, q_spec );
2879 
2880     if(parse_rc != 0) std::cerr
2881         << "stree::get_frame"
2882         << " FATAL parse_spec failed "
2883         << " q_spec [" << ( q_spec ? q_spec : "-" ) << "]"
2884         << " parse_rc " << parse_rc
2885         << "\n"
2886         ;
2887     assert( parse_rc == 0 );
2888 
2889 
2890     [[maybe_unused]] int get_rc = 0 ;
2891     if( repeat_ordinal == -1 || repeat_ordinal == -2 || repeat_ordinal == -3 )
2892     {
2893         get_rc = get_frame_global(  f,  lvid, lvid_ordinal, repeat_ordinal );
2894     }
2895     else
2896     {
2897         get_rc = get_frame_instanced(f,  lvid, lvid_ordinal, repeat_ordinal );
2898     }
2899 
2900     if(get_rc != 0 ) std::cerr
2901         << "stree::get_frame FAIL q_spec[" << ( q_spec ? q_spec : "-" ) << "]\n"
2902         << " THIS CAN BE CAUSED BY NOT USING REPEAT_ORDINAL -1 (LAST OF TRIPLET) FOR GLOBAL GEOMETRY "
2903         << "\n"
2904         ;
2905 
2906     assert( get_rc == 0 );
2907     return get_rc ;
2908 }
2909 
2910 
2911 
2912 
2913 /**
2914 stree::get_frame_from_coords
2915 ------------------------------
2916 
2917 HMM: perhaps move into sfr.h
2918 
2919 This enables manual targetting some position::
2920 
2921      MOI=3345.569,20623.73,21000,1000 ssst.sh
2922 
2923 **/
2924 
2925 
2926 inline int stree::get_frame_from_coords(sfr& f, const char* q_spec ) const
2927 {
2928     char delim = ',' ;
2929     std::vector<double> elem ;
2930     sstr::split<double>( elem, q_spec, delim );
2931     int num_elem = elem.size();
2932     bool expect_elem = num_elem == 4 || num_elem == 3 || num_elem == 2 || num_elem == 1 ;
2933     std::cout
2934         << "stree::get_frame_from_coords"
2935         << " num_elem " << num_elem
2936         << " expect_elem " << ( expect_elem ? "YES" : "NO " )
2937         << " elem " << sstr::desc<double>(elem)
2938         << "\n"
2939         ;
2940     if(!expect_elem) return 1 ;
2941 
2942     std::array<double,4> ce = {} ;
2943     ce[0] = num_elem > 0 ? elem[0] : 0. ;
2944     ce[1] = num_elem > 1 ? elem[1] : 0. ;
2945     ce[2] = num_elem > 2 ? elem[2] : 0. ;
2946     ce[3] = num_elem > 3 ? elem[3] : 1000. ;
2947 
2948     f.set_ce(ce.data() );
2949 
2950     std::array<double,6> bb = {};
2951     bb[0] = ce[0] - ce[3] ;
2952     bb[1] = ce[1] - ce[3] ;
2953     bb[2] = ce[2] - ce[3] ;
2954     bb[3] = ce[0] + ce[3] ;
2955     bb[4] = ce[1] + ce[3] ;
2956     bb[5] = ce[2] + ce[3] ;
2957     f.set_bb( bb.data() );
2958     f.set_prim(-1);
2959 
2960 
2961     bool rtp_tangential = false ;
2962     bool extent_scale = false ;
2963     SCenterExtentFrame<double> cef(ce[0], ce[1], ce[2], ce[3], rtp_tangential, extent_scale ) ;
2964     f.m2w = cef.model2world ;
2965     f.w2m = cef.world2model ;
2966 
2967     return 0;
2968 }
2969 
2970 
2971 inline int stree::get_frame_from_transform(sfr& f, const char* q_spec ) const
2972 {
2973     char delim = ',' ;
2974     std::vector<double> elem ;
2975     sstr::split<double>( elem, q_spec, delim );
2976     int num_elem = elem.size();
2977     bool expect_elem = num_elem == 16  ;
2978     std::cout
2979         << "stree::get_frame_from_transform"
2980         << " num_elem " << num_elem
2981         << " expect_elem " << ( expect_elem ? "YES" : "NO " )
2982         << " elem " << sstr::desc<double>(elem)
2983         << "\n"
2984         ;
2985     if(!expect_elem) return 1 ;
2986 
2987     f.set_m2w(elem.data() );
2988 
2989     return 0;
2990 }
2991 
2992 
2993 
2994 
2995 /**
2996 stree::has_frame
2997 ------------------
2998 
2999 The spec are of form::
3000 
3001     Hama:0:1000
3002     solidXJanchor:20:-1
3003     sSurftube_38V1_0:0:-1
3004 
3005 Where the three fields provide the ints::
3006 
3007     (lvid, lvid_ordinal, repeat_ordinal)
3008 
3009 
3010 From v0.3.7
3011    returns false when q_spec is invalid (formerly asserted),
3012    Invalid q_spec is usually because the lv name starting the q_spec
3013    is not present in the geometry
3014 
3015 **/
3016 
3017 inline bool stree::has_frame(const char* q_spec) const
3018 {
3019     int lvid ;
3020     int lvid_ordinal ;
3021     int repeat_ordinal ;
3022     int parse_rc = parse_spec( lvid, lvid_ordinal, repeat_ordinal, q_spec );
3023     //assert( parse_rc == 0 );
3024     if(parse_rc != 0)
3025     {
3026         std::cerr
3027             << "stree::has_frame"
3028             << " FATAL parse_spec failed "
3029             << " q_spec [" << ( q_spec ? q_spec : "-" ) << "]"
3030             << " parse_rc " << parse_rc
3031             << "\n"
3032             ;
3033         return false ;
3034     }
3035 
3036     sfr f ;
3037     f.set_name(q_spec);
3038 
3039     int get_rc = 0 ;
3040     if( repeat_ordinal == -1 || repeat_ordinal == -2 || repeat_ordinal == -3)
3041     {
3042         get_rc = get_frame_global(  f,  lvid, lvid_ordinal, repeat_ordinal );
3043     }
3044     else
3045     {
3046         get_rc = get_frame_instanced(f,  lvid, lvid_ordinal, repeat_ordinal );
3047     }
3048 
3049     if(get_rc != 0 ) std::cerr
3050         << "stree::has_frame FAIL q_spec[" << ( q_spec ? q_spec : "-" ) << "]\n"
3051         << " THIS CAN BE CAUSED BY NOT USING REPEAT_ORDINAL -1 (LAST OF TRIPLET) FOR GLOBAL GEOMETRY "
3052         << "\n"
3053         ;
3054 
3055     return get_rc == 0 ;
3056 }
3057 
3058 
3059 
3060 /**
3061 stree::get_frame_instanced
3062 ----------------------------
3063 
3064 1. pick_lvid_ordinal_repeat_ordinal_inst_ gives ii from (lvid, lvid_ordinal, repeat_ordinal)
3065 
3066    * for non-instanced *ii* "instance-index" will be zero
3067 
3068 2. get_inst, get_iinst : lookup the instance transforms using *ii*
3069 
3070    * for non-instanced will yield identity transforms
3071 
3072 3.  use inst_nidx and nds to get the snode for the ii
3073 
3074    * for global that will be the outer world volume
3075 
3076 4. get_prim_aabb : find bounding box of the snode by delving into
3077    the transformed CSG constituent nds for the node.lvid
3078 
3079 
3080 **/
3081 
3082 inline int stree::get_frame_instanced(sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal, std::ostream* out, VTR* t_stack ) const
3083 {
3084     int ii = pick_lvid_ordinal_repeat_ordinal_inst_( lvid, lvid_ordinal, repeat_ordinal );
3085     int nidx = inst_nidx[ii] ;
3086     const snode& nd = nds[nidx] ;
3087 
3088     //assert( nd.lvid == lvid );
3089     //    lvid will not in general match nd.lvid
3090     //    because there are multiple lv within the instance
3091     //    (would only work for the outer CSGPrim of the CSGSolid presumably)
3092 
3093     //assert( nd.repeat_ordinal == repeat_ordinal );
3094     //    not so for globals
3095 
3096 
3097     if(get_frame_dump) std::cout
3098         << "stree::get_frame_instanced"
3099         << "\n"
3100         << " lvid " << lvid
3101         << " soname[lvid] " << soname[lvid]
3102         << " soname[nd.lvid] " << soname[nd.lvid]
3103         << "\n"
3104         << " lvid_ordinal " << lvid_ordinal
3105         << " repeat_ordinal " << repeat_ordinal
3106         << " ii " << ii
3107         << " nidx " << nidx
3108         << "\n"
3109         << " nd.desc " << nd.desc()
3110         << "\n"
3111         ;
3112     return get_frame_inst(f, ii, out, t_stack );
3113 }
3114 
3115 
3116 inline int stree::get_frame_inst(sfr& f, int ii, std::ostream* out, VTR* t_stack) const
3117 {
3118     const glm::tmat4x4<double>* m2w = get_inst(ii);
3119     const glm::tmat4x4<double>* w2m = get_iinst(ii);
3120     bool missing_transform = !m2w || !w2m ;
3121 
3122     if(missing_transform) std::cerr
3123         << "stree::get_frame_inst FAIL missing_transform "
3124         << " w2m " << ( w2m ? "YES" : "NO " )
3125         << " m2w " << ( m2w ? "YES" : "NO " )
3126         << " ii " << ii
3127         << "\n"
3128         ;
3129 
3130     assert( m2w );
3131     assert( w2m );
3132 
3133     int nidx = inst_nidx[ii] ;
3134     const snode& nd = nds[nidx] ;
3135 
3136     std::array<double,6> bb ;
3137     get_prim_aabb( bb.data(), nd, out, t_stack );
3138 
3139     s_bb::CenterExtent( f.ce_data(), bb.data() );
3140 
3141     f.m2w = *m2w ;
3142     f.w2m = *w2m ;
3143 
3144     f.set_bb(bb.data());
3145     f.set_inst(ii);
3146     f.set_nidx(nidx);
3147 
3148 
3149     int inst_idx = -1 ;
3150     int gas_idx = -1 ;
3151     int sensor_identifier = -1 ;
3152     int sensor_index = -1 ;
3153 
3154     int rc = get_inst_identity( inst_idx, gas_idx, sensor_identifier, sensor_index, ii );
3155     assert( rc == 0 );
3156     assert( inst_idx == ii );
3157     f.set_identity( inst_idx, gas_idx, sensor_identifier, sensor_index );
3158     // cf CSGTarget::getFrame
3159 
3160     return 0 ;
3161 }
3162 
3163 
3164 
3165 
3166 /**
3167 stree::get_frame_remainder
3168 --------------------------------
3169 
3170 Note the similartity to MOI targetting,
3171 at CSG level which is handled by::
3172 
3173     CSGTarget::getFrameComponents
3174     CSGTarget::getGlobalCenterExtent
3175     CSGImport::importSolidRemainder
3176 
3177 The info is definitely here, just have different access
3178 here at stree level.
3179 
3180 * look inside rem snode for the specified (lvid, lvid_ordinal) ?
3181 
3182 TODO: avoid the duplication in frame access impls
3183 
3184 **/
3185 inline int stree::get_frame_remainder(sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal ) const
3186 {
3187     return _get_frame_global( f, lvid, lvid_ordinal, repeat_ordinal, 'R' );
3188 }
3189 inline int stree::get_frame_triangulate(sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal ) const
3190 {
3191     return _get_frame_global( f, lvid, lvid_ordinal, repeat_ordinal, 'T' );
3192 }
3193 inline int stree::get_frame_global(sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal ) const
3194 {
3195     return _get_frame_global( f, lvid, lvid_ordinal, repeat_ordinal, '?' );
3196 }
3197 
3198 
3199 /**
3200 stree::_get_frame_global
3201 --------------------------
3202 
3203 This is called for special cased -ve repeat_ordinal, which
3204 is only appropriate for global non-instanced volumes.
3205 
3206 1. find the snode using (lvid, lvid_ordinal, ridx_type['R','T','?'])
3207 2. compute bounding box and hence center_extent for the snode
3208 3. form frame transforms m2w/w2m using SCenterExtentFrame or not
3209    depending on repeat_ordinal -1/-2/-3
3210 
3211 Global repeat_ordinal special case convention
3212 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3213 
3214 repeat_ordinal:-1
3215    sets CE only, does not set m2w w2m into the frame
3216    [WHAT USE IS THIS ?]
3217 
3218 repeat_ordinal:-2
3219    sets CE, m2w, w2m into the frame using SCenterExtentFrame with rtp_tangential:false
3220 
3221 repeat_ordinal:-3
3222    sets CE, m2w, w2m into the frame using SCenterExtentFrame with rtp_tangential:true
3223 
3224 
3225 27 May 2025 behaviour change for repeat_ordinal:-1
3226 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3227 
3228 WIP: test this
3229 
3230 Formerly the stree::_get_frame_global repeat_ordinal:-1 gave frames
3231 with transforms that CSGTarget::getFrameComponents
3232 would need repeat_ordinal:-2 for.
3233 
3234 The stree::_get_frame_global implementation is
3235 now aligned with CSGTarget::getFrameComponents
3236 to avoid the need to keep swapping MOI -1/-2 arising from
3237 a former difference in the convention used.
3238 
3239 **/
3240 
3241 inline int stree::_get_frame_global(sfr& f, int lvid, int lvid_ordinal, int repeat_ordinal, char ridx_type ) const
3242 {
3243     assert( repeat_ordinal == -1 || repeat_ordinal == -2 || repeat_ordinal == -3 );
3244     const snode* _node = pick_lvid_ordinal_node( lvid, lvid_ordinal, ridx_type );
3245     if(_node == nullptr) return 1 ;
3246 
3247     const snode& node = *_node ;
3248 
3249     std::array<double,4> ce = {} ;
3250     std::array<double,6> bb = {} ;
3251 
3252 
3253     int LVID = ssys::getenvint(stree__get_frame_global_LVID,-1);
3254 
3255     VBB* contrib_bb = nullptr ;
3256     VTR* contrib_tr = nullptr ;
3257     if( node.lvid == LVID )
3258     {
3259         contrib_bb = new VBB ;
3260         contrib_tr = new VTR ;
3261     }
3262 
3263     int rc = get_node_ce_bb( ce, bb, node, contrib_bb, contrib_tr );
3264     f.set_ce(ce.data() );
3265     f.set_bb(bb.data() );
3266 
3267     f.set_nidx( node.index );
3268 
3269     int prim = get_prim_for_nidx( node.index );
3270     f.set_prim( prim );
3271 
3272     f.set_lvid(lvid);
3273     f.set_lvid_ordinal(lvid_ordinal);
3274 
3275 
3276     if(contrib_bb)
3277     {
3278         NP* a = NP::Make<double>( contrib_bb->size(), 6 );
3279         double* aa = a->values<double>();
3280         for(size_t i=0 ; i < contrib_bb->size() ; i++)
3281         {
3282             const BB& cbb = (*contrib_bb)[i] ;
3283             for(size_t j=0 ; j < 6 ; j++) aa[i*6+j] = cbb[j] ;
3284         }
3285         std::stringstream ss ;
3286         ss << stree__get_frame_global_LVID << "_" << LVID << "_contrib_bb.npy" ;
3287         std::string name = ss.str();
3288         std::cout << "stree::_get_frame_global saving [" << name << "]\n" ;
3289         a->save( name.c_str() );
3290     }
3291 
3292     if(contrib_tr)
3293     {
3294         NP* a = NP::Make<double>( contrib_tr->size(), 4, 4 );
3295         double* aa = a->values<double>();
3296         for(size_t i=0 ; i < contrib_tr->size() ; i++)
3297         {
3298             const TR& ctr = (*contrib_tr)[i] ;
3299             for(size_t j=0 ; j < 16 ; j++) aa[i*16+j] = glm::value_ptr(ctr)[j] ; ;
3300         }
3301         std::stringstream ss ;
3302         ss << stree__get_frame_global_LVID << "_" << LVID << "_contrib_tr.npy" ;
3303         std::string name = ss.str();
3304         std::cout << "stree::_get_frame_global saving [" << name << "]\n" ;
3305         a->save( name.c_str() );
3306     }
3307 
3308 
3309 
3310 
3311     if( repeat_ordinal == -2 || repeat_ordinal == -3 )
3312     {
3313         bool rtp_tangential = repeat_ordinal == -3 ? true : false ;
3314         bool extent_scale = false ;
3315         SCenterExtentFrame<double> cef(ce[0], ce[1], ce[2], ce[3], rtp_tangential, extent_scale ) ;
3316         f.m2w = cef.model2world ;
3317         f.w2m = cef.world2model ;
3318     }
3319 
3320     if(get_frame_dump) std::cout
3321         << "stree::get_frame_remainder"
3322         << "\n"
3323         << " lvid " << lvid
3324         << " soname[lvid] " << soname[lvid]
3325         << " soname[node.lvid] " << ( soname[node.lvid] )
3326         << "\n"
3327         << " lvid_ordinal " << lvid_ordinal
3328         << " repeat_ordinal " << repeat_ordinal
3329         << "\n"
3330         << " node.desc " << ( node.desc())
3331         << "\n"
3332         << " bb \n"
3333         << s_bb::Desc( bb.data() )
3334         << "\n"
3335         ;
3336 
3337     return rc ;
3338 }
3339 
3340 
3341 /**
3342 stree::get_node_ce_bb
3343 ------------------------
3344 
3345 1. get bbox for the snode with stree::get_node_bb
3346 2. derive CenterExtent ce from the bbox bb
3347 
3348 **/
3349 
3350 
3351 inline int stree::get_node_ce_bb(    std::array<double,4>& ce , std::array<double,6>& bb,  const snode& node, VBB* contrib_bb, VTR* contrib_tr ) const
3352 {
3353     int rc = get_node_bb(bb, node, contrib_bb, contrib_tr );
3354     s_bb::CenterExtent( ce.data(), bb.data() );
3355     return rc ;
3356 }
3357 
3358 /**
3359 stree::get_node_bb
3360 -------------------
3361 
3362 1. get bds binary tree nodes
3363 2. get lns list nodes
3364 3. iterate over binary tree nodes
3365 
3366 4. A: when a listnode is encountered collect the immediate child nodes into subs
3367 4. B: when a non-listnode leaf is encountered obtain n_bb bounding box and include that into bb
3368 
3369 5. iterate over the subs, which are required to all be leaf nodes, getting their n_bb and including it into bb
3370 
3371 
3372 **/
3373 
3374 inline int stree::get_node_bb(  std::array<double,6>& bb , const snode& node, VBB* contrib_bb, VTR* contrib_tr ) const
3375 {
3376     int lvid = node.lvid ;
3377     int LVID = ssys::getenvint(stree__get_node_bb_LVID,-1);
3378     int CONSTITUENT = ssys::getenvint(stree__get_node_bb_CONSTITUENT,-1);
3379 
3380 
3381     // 1. get bds binary tree nodes
3382 
3383     std::vector<const sn*> bds ;         // binary tree nodes
3384     sn::GetLVNodesComplete(bds, lvid);   // many nullptr in unbalanced deep complete binary trees
3385     int bn = bds.size();                 // number of binary tree nodes
3386 
3387 
3388    // 2. get lns list nodes
3389 
3390     std::vector<const sn*> lns ;
3391     sn::GetLVListnodes( lns, lvid );
3392     //int num_sub_total = sn::GetChildTotal( lns );
3393 
3394     int ln = lns.size();
3395     assert( ln == 0 || ln == 1 ); // simplify initial impl  : see CSGImport::importPrim
3396 
3397     std::ostream* out = contrib_bb ? new std::stringstream : nullptr ;
3398     if(out) *out << "stree::get_node_bb bn " << bn << "\n" ;
3399 
3400 
3401     // 3. iterate over binary tree nodes
3402     std::vector<const sn*> subs ;
3403 
3404     for(int i=0 ; i < bn ; i++)
3405     {
3406         if(out) *out << "stree::get_node_bb.bn_loop.HEAD i[" << i << "/" << bn << "]\n" ;
3407         const sn* n = bds[i];
3408         int  typecode = n ? n->typecode : CSG_ZERO ;
3409 
3410         if(n && n->is_listnode())
3411         {
3412             // 4. A: when a listnode is encountered collect the immediate child nodes into subs
3413             int num_sub = n->child.size() ;
3414             for(int j=0 ; j < num_sub ; j++)
3415             {
3416                 const sn* c = n->child[j];
3417                 subs.push_back(c);
3418             }
3419         }
3420         else
3421         {
3422             // 4. B: when a non-listnode leaf  is encountered obtain n_bb bounding box and include that into bb
3423             bool leaf = CSG::IsLeaf(typecode) ;
3424 
3425             if(0) std::cout
3426                 << "stree::get_node_bb"
3427                 << " i " << std::setw(2) << i
3428                 << " typecode " << typecode
3429                 << " leaf " << ( leaf ? "Y" : "N" )
3430                 << "\n"
3431                 ;
3432 
3433             if(!leaf) continue ;
3434 
3435             std::array<double,6> n_bb0 = {} ;
3436             n->copyBB_data(n_bb0.data()) ; // without transform
3437 
3438             std::array<double,6> n_bb = {} ;
3439             double* n_aabb = leaf ? n_bb.data() : nullptr ;
3440 
3441             if(out) *out << "stree::get_node_bb.bn_loop.GET_COMBINED_TRAN_AND_AABB i[" << i << "]\n" ;
3442 
3443             const Tran<double>* tv = leaf ? get_combined_tran_and_aabb( n_aabb, node, n, out, nullptr ) : nullptr ;
3444             bool is_degenerate = s_bb::Degenerate<double>( n_aabb );
3445 
3446             if(is_degenerate) std::cerr
3447                 << "stree::get_node_bb.tree"
3448                 << " i " << std::setw(2) << i
3449                 << " SKIP DEGENERATE bb "
3450                 << " typecode " << typecode
3451                 << " "
3452                 << s_bb::Desc(n_aabb)
3453                 << "\n"
3454                 ;
3455 
3456             if(tv && leaf && n_aabb && !is_degenerate && !n->is_complement_primitive())
3457             {
3458                 if(contrib_bb) contrib_bb->push_back(n_bb0);
3459                 if(contrib_bb) contrib_bb->push_back(n_bb);
3460                 if(contrib_tr) contrib_tr->push_back(tv->t);
3461                 if(out) *out << "stree::get_node_bb.bn_loop.IncludeAABB i[" << i << "]\n" ;
3462                 s_bb::IncludeAABB( bb.data(), n_aabb, out );
3463             }
3464 
3465         }
3466         if(out) *out << "stree::get_node_bb.bn_loop.TAIL i[" << i << "/" << bn << "]\n" ;
3467     }
3468 
3469 
3470     // NOT FULLY TESTED : but it succeeds to do nothing with subtracted multiunion of holes (that becomes listnode)
3471 
3472     // 5. iterate over the subs, which are required to all be leaf nodes, getting their n_bb and including it into bb
3473 
3474     int num_sub_total = subs.size();
3475     for( int i=0 ; i < num_sub_total ; i++ )
3476     {
3477         if(out) *out << "stree::get_node_bb.sub_loop.HEAD i[" << i << "/" << num_sub_total <<  "]\n" ;
3478         const sn* n = subs[i];
3479         bool leaf = CSG::IsLeaf(n->typecode) ;
3480         assert(leaf);
3481         if(!leaf) continue ;
3482 
3483         std::array<double,6> n_bb0 = {} ;
3484         n->copyBB_data(n_bb0.data()) ; // without transform
3485 
3486         std::array<double,6> n_bb ;
3487         double* n_aabb = n_bb.data() ;
3488 
3489 
3490         if(out) *out << "stree::get_node_bb.sub_loop.GET_COMBINED_TRAN_AND_AABB i[" << i << "]\n" ;
3491 
3492         VTR* isub_t_stack = lvid == LVID && ( CONSTITUENT == -1 || CONSTITUENT == i ) ? new VTR : nullptr ;
3493 
3494         const Tran<double>* tv = get_combined_tran_and_aabb( n_aabb, node, n, out, isub_t_stack  ) ;
3495         bool is_degenerate = s_bb::Degenerate<double>( n_aabb );
3496 
3497         if(is_degenerate) std::cerr
3498             << "stree::get_node_bb.subs"
3499             << " i " << std::setw(2) << i
3500             << " SKIP DEGENERATE bb "
3501             << " typecode " << n->typecode
3502             << " "
3503             << s_bb::Desc(n_aabb)
3504             << "\n"
3505             ;
3506 
3507         if(tv && leaf && n_aabb && !is_degenerate && !n->is_complement_primitive())
3508         {
3509             if(contrib_bb) contrib_bb->push_back(n_bb0);
3510             if(contrib_bb) contrib_bb->push_back(n_bb);
3511             if(contrib_tr) contrib_tr->push_back(tv->t);
3512 
3513             if(out) *out << "stree::get_node_bb.sub_loop.i[" << i << "].n_bb0 " << s_bb::Desc(n_bb0.data()) << "\n" ;
3514             if(out) *out << "stree::get_node_bb.sub_loop.i[" << i << "].n_bb  " << s_bb::Desc(n_bb.data())  << "\n" ;
3515             s_bb::IncludeAABB( bb.data(), n_aabb, out );
3516         }
3517         // HMM does the complement message get thru to listnode subs ?
3518         if(out) *out << "stree::get_node_bb.sub_loop.TAIL i[" << i << "/" << num_sub_total <<  "]\n" ;
3519 
3520         if(isub_t_stack)
3521         {
3522             NP* a = NP::Make<double>( isub_t_stack->size(), 4, 4 );
3523             double* aa = a->values<double>();
3524             for(size_t i=0 ; i < isub_t_stack->size() ; i++)
3525             {
3526                 const TR& ctr = (*isub_t_stack)[i] ;
3527                 for(size_t j=0 ; j < 16 ; j++) aa[i*16+j] = glm::value_ptr(ctr)[j] ; ;
3528             }
3529             std::stringstream ss ;
3530             ss << stree__get_node_bb_LVID << "_" << lvid << "_" << i << "_isub_t_stack.npy" ;
3531             std::string name = ss.str();
3532             std::cout << "stree::_get_frame_global saving [" << name << "]\n" ;
3533             a->save( name.c_str() );
3534             delete isub_t_stack ;
3535         }
3536 
3537     }
3538 
3539     if(out)
3540     {
3541         std::stringstream* ss = dynamic_cast<std::stringstream*>(out) ;
3542         std::string msg = ss ? ss->str() : "-" ;
3543         std::cout
3544             << "stree::get_node_bb out\n[\n"
3545             << msg
3546             << "\n]\n"
3547             ;
3548     }
3549 
3550 
3551     return 0 ;
3552 }
3553 
3554 
3555 
3556 template<typename T>
3557 inline void stree::find_nodes_containing_point(std::vector<snode>& nodes, const T* xyz ) const
3558 {
3559     T x = xyz[0] ;
3560     T y = xyz[1] ;
3561     T z = xyz[2] ;
3562 
3563     int num_nd = nds.size();
3564     BB bb ;
3565     for(int i=0 ; i < num_nd ; i++)
3566     {
3567         int nidx = i ;
3568         const snode& node = nds[nidx];
3569         get_prim_aabb( bb.data(), node, nullptr, nullptr );
3570         bool in_bb = x >= bb[0] && x <= bb[3]  && y >= bb[1] && y <= bb[4] && z >= bb[2] && z <= bb[5] ;
3571         if( in_bb ) nodes.push_back(node);
3572     }
3573 }
3574 
3575 
3576 template<typename T>
3577 inline void stree::find_nodes_with_center_within_bb(std::vector<snode>& nodes, const T* qbb ) const
3578 {
3579     int num_nd = nds.size();
3580     std::array<double,4> ce = {} ;
3581     std::array<double,6> bb = {} ;
3582 
3583     for(int i=0 ; i < num_nd ; i++)
3584     {
3585         int nidx = i ;
3586         const snode& node = nds[nidx];
3587         get_node_ce_bb( ce , bb, node, nullptr, nullptr );
3588 
3589         bool in_bb = ce[0] >= qbb[0] && ce[0] <= qbb[3] &&
3590                      ce[1] >= qbb[1] && ce[1] <= qbb[4] &&
3591                      ce[2] >= qbb[2] && ce[2] <= qbb[5] ;
3592 
3593         if(in_bb) nodes.push_back(node);
3594     }
3595 }
3596 
3597 
3598 
3599 template<typename T>
3600 inline void stree::find_nodes_with_center_within_ce(std::vector<snode>& nodes, const T* ce ) const
3601 {
3602      std::array<T,6> bb ;
3603      bb[0] = ce[0] - ce[3] ;
3604      bb[1] = ce[1] - ce[3] ;
3605      bb[2] = ce[2] - ce[3] ;
3606      bb[3] = ce[0] + ce[3] ;
3607      bb[4] = ce[1] + ce[3] ;
3608      bb[5] = ce[2] + ce[3] ;
3609 
3610      find_nodes_with_center_within_bb<T>( nodes, bb.data() );
3611 }
3612 
3613 template<typename T>
3614 inline std::string stree::desc_nodes_with_center_within_ce( const T* qce ) const
3615 {
3616     std::vector<snode> nodes ;
3617     find_nodes_with_center_within_ce<T>( nodes, qce );
3618     int num_nodes = nodes.size();
3619 
3620     std::stringstream ss ;
3621     ss << "[stree::desc_nodes_with_center_within_ce\n"
3622        << " qce ["
3623        << std::setw(10) << std::fixed << std::setprecision(3) << qce[0]
3624        << std::setw(10) << std::fixed << std::setprecision(3) << qce[1]
3625        << std::setw(10) << std::fixed << std::setprecision(3) << qce[2]
3626        << std::setw(10) << std::fixed << std::setprecision(3) << qce[3]
3627        << "]\n"
3628        << " num_nodes " << num_nodes
3629        << "\n"
3630        ;
3631 
3632     for(int i=0 ; i < num_nodes ; i++)
3633     {
3634         const snode& nd = nodes[i];
3635         ss << nd.desc() << "\n" ;
3636     }
3637 
3638     std::string str = ss.str() ;
3639     return str ;
3640 }
3641 
3642 
3643 
3644 
3645 /**
3646 stree::get_sub_sonames
3647 -----------------------
3648 
3649 // TODO: should this be using sfactor ?
3650 
3651 **/
3652 inline void stree::get_sub_sonames( std::vector<std::string>& sonames ) const
3653 {
3654     std::vector<std::string> subs ;
3655     subs_freq->get_keys(subs, FREQ_CUT );
3656     for(unsigned i=0 ; i < subs.size() ; i++)
3657     {
3658         const char* sub = subs[i].c_str();
3659         const char* soname_ = get_sub_soname(sub);
3660         sonames.push_back(soname_);
3661     }
3662 }
3663 
3664 
3665 inline const char* stree::get_sub_soname(const char* sub) const
3666 {
3667     int first_nidx = get_first(sub);
3668     return first_nidx == -1 ? nullptr : get_soname(first_nidx ) ;
3669 }
3670 
3671 
3672 /**
3673 stree::Name
3674 ------------
3675 
3676 HMM: tail stripping now done at collection with sstr::StripTail_Unique
3677 
3678 **/
3679 inline std::string stree::Name( const std::string& name, bool strip_tail ) // static
3680 {
3681     return strip_tail ? sstr::StripTail(name, "0x") : name ;
3682 }
3683 inline std::string stree::get_lvid_soname(int lvid, bool strip) const
3684 {
3685     if(lvid < 0 || lvid >= int(soname.size())) return "bad_lvid" ;
3686     return Name(soname[lvid], strip) ;
3687 }
3688 
3689 inline const std::string& stree::get_lvid_soname_(int lvid) const
3690 {
3691     assert( lvid >= 0 && lvid < int(soname.size()) ) ;
3692     return soname[lvid] ;
3693 }
3694 
3695 
3696 /**
3697 stree::get_meshname
3698 ---------------------
3699 
3700 Same names everywhere::
3701 
3702      (ok) A[blyth@localhost CSGFoundry]$ diff meshname.txt SSim/stree/soname_names.txt
3703      (ok) A[blyth@localhost CSGFoundry]$ diff meshname.txt SSim/scene/soname_names.txt
3704 
3705 **/
3706 
3707 inline void stree::get_meshname( std::vector<std::string>& names) const
3708 {
3709     bool strip_tail = true ;    // suspect does nothing, as already done when this called
3710     assert( names.size() == 0 );
3711     for(unsigned i=0 ; i < soname.size() ; i++) names.push_back( Name(soname[i],strip_tail) );
3712 }
3713 
3714 inline void stree::get_mmlabel( std::vector<std::string>& names) const
3715 {
3716     assert( names.size() == 0 );
3717     int num_ridx = get_num_ridx();
3718 
3719     if(level > 1) std::cout
3720         << "stree::get_mmlabel"
3721         << " level " << level
3722         << " num_ridx " << num_ridx
3723         << "\n"
3724         ;
3725 
3726     for(int ridx=0 ; ridx < num_ridx ; ridx++)
3727     {
3728         int num_prim = get_ridx_subtree(ridx) ;
3729         int olvid    = get_ridx_olvid(ridx) ;
3730 
3731         assert( olvid < int(soname.size()) );
3732         std::string name = get_lvid_soname(olvid, true);
3733 
3734         std::stringstream ss ;
3735         ss << num_prim << ":" << name ;
3736         std::string mmlabel = ss.str();
3737 
3738         if(level > 1) std::cout
3739             << "stree::get_mmlabel"
3740             << " level " << level
3741             << " ridx " << ridx
3742             << " mmlabel " << mmlabel
3743             << "\n"
3744             ;
3745 
3746         names.push_back(mmlabel);
3747     }
3748 }
3749 
3750 
3751 inline int stree::get_num_nodes() const
3752 {
3753     return nds.size() ;
3754 }
3755 
3756 inline const char* stree::get_soname(int nidx) const
3757 {
3758     return nidx > -1 ? soname[nds[nidx].lvid].c_str() : "?" ;
3759 }
3760 inline const char* stree::get_sub(int nidx) const
3761 {
3762     return nidx > -1 ? subs[nidx].c_str() : nullptr ;
3763 }
3764 inline int stree::get_depth(int nidx) const
3765 {
3766     return nidx > -1 ? nds[nidx].depth : -1 ;
3767 }
3768 inline int stree::get_parent(int nidx) const
3769 {
3770     return nidx > -1 ? nds[nidx].parent : -1 ;
3771 }
3772 inline int stree::get_lvid(int nidx) const
3773 {
3774     return nidx > -1 ? nds[nidx].lvid : -1 ;
3775 }
3776 inline int stree::get_copyno(int nidx) const
3777 {
3778     return nidx > -1 ? nds[nidx].copyno : -1 ;
3779 }
3780 
3781 /**
3782 stree::get_top
3783 ---------------
3784 
3785 Returns root node, the first "World" node which has no parent node.
3786 
3787 **/
3788 
3789 inline const snode* stree::get_top() const
3790 {
3791     return get_node(0);
3792 }
3793 inline const snode* stree::get_node(int nidx) const
3794 {
3795     int num_nd = nds.size();
3796     return nidx > -1 && nidx < num_nd ? &nds[nidx] : nullptr ;
3797 }
3798 inline const snode* stree::get_parent_node(int nidx) const
3799 {
3800     const snode* n0 = &nds[nidx] ;
3801     const snode* nd = n0->parent > -1 ? &nds[n0->parent] : nullptr ;
3802     return nd ;
3803 }
3804 
3805 /**
3806 stree::is_outer_node
3807 ----------------------
3808 
3809 Used by::
3810 
3811    stree::get_ancestors
3812    stree::get_node_product
3813 
3814 An outer node is either the root node which has no parent
3815 or some other node with a parent from a different repeat_index.
3816 Typically the different repeat_index with be the global zero.
3817 
3818 The outer nodes correspond to base nodes of the instances,
3819 and similarly the root node is the base node of the remainder nodes.
3820 
3821 **/
3822 
3823 inline bool stree::is_outer_node(int nidx) const
3824 {
3825     const snode* n = get_node(nidx);
3826     assert(n);
3827     const snode* p = get_parent_node(nidx);
3828 
3829     return p == nullptr ?
3830                            true
3831                         :
3832                            p->repeat_index != n->repeat_index
3833                         ;
3834 
3835 }
3836 
3837 
3838 
3839 /**
3840 stree::get_ancestors
3841 ---------------------
3842 
3843 This should work even during node collection immediately
3844 after the parent link has been set and the snode pushed back.
3845 
3846 local:false
3847     Collects parent, then parent-of-parent and so on
3848     until reaching root (nidx:0) which has no parent.
3849     Then reverses the list to put into root first order.
3850 
3851     At first glance you might think this would miss root, but that
3852     is not the case as it collects parents and the node prior
3853     to the parent results in collecting root nidx:0.
3854 
3855 local:true
3856     Gets ancestors of *nidx* that have the same repeat_index as the *nidx* node.
3857     For *nidx* within the remainder nodes this is expected to start from root, nidx 0.
3858     For *nidx* within instanced nodes this will only include ancestor
3859     nodes within that same instance. Note also that the outer node of
3860     the instance is BY DESIGN : NOT INCLUDED.
3861 
3862 **/
3863 
3864 inline void stree::get_ancestors(
3865     std::vector<int>& ancestors,
3866     int nidx,
3867     bool local,
3868     std::ostream* out ) const
3869 {
3870     const snode* nd0 = &nds[nidx] ;
3871     const snode* nd = nd0->parent > -1 ? &nds[nd0->parent] : nullptr ;
3872 
3873     while(nd)
3874     {
3875         if(local == false)
3876         {
3877             ancestors.push_back(nd->index);
3878         }
3879         else if( !is_outer_node(nd->index) && nd->repeat_index == nd0->repeat_index )
3880         {
3881             ancestors.push_back(nd->index);
3882         }
3883 
3884         nd = nd->parent > -1 ? &nds[nd->parent] : nullptr ;
3885     }
3886     std::reverse( ancestors.begin(), ancestors.end() );
3887 
3888     if(out)
3889     {
3890         int num_ancestors = ancestors.size() ;
3891         *out
3892             << "stree::get_ancestors"
3893             << " nidx " << nidx
3894             << " local " << local
3895             << " nd0.repeat_index " << ( nd0 ? nd0->repeat_index : -10 )
3896             << " nd.repeat_index "  << ( nd  ? nd->repeat_index  : -10 )
3897             ;
3898 
3899         *out
3900             << " num_ancestors " << num_ancestors
3901             << " ["
3902             ;
3903         for(int i=0 ; i < num_ancestors ; i++) *out << ancestors[i] << " " ;
3904         *out << "]" << std::endl ;
3905 
3906         bool show_sub = true ;
3907         for(int i=0 ; i < num_ancestors ; i++) *out << desc_node(ancestors[i], show_sub) << std::endl ;
3908         *out << desc_node(nidx, show_sub) << " " << std::endl ;
3909     }
3910 
3911 
3912 }
3913 
3914 
3915 inline std::string stree::desc_ancestors(int nidx, bool local) const
3916 {
3917     std::stringstream ss ;
3918     std::vector<int> ancestors ;
3919     get_ancestors(ancestors, nidx, local, &ss );
3920 
3921     ss << "stree::desc_ancestors" << std::endl ;
3922     std::string str = ss.str();
3923     ss << str ;
3924     std::string s = ss.str();
3925     return s ;
3926 }
3927 
3928 
3929 /**
3930 stree::get_node_transform
3931 ---------------------------
3932 
3933 Returns local (relative to parent) transforms for the nidx snode
3934 
3935 **/
3936 
3937 inline void stree::get_node_transform( glm::tmat4x4<double>& m2w_, glm::tmat4x4<double>& w2m_, int nidx ) const
3938 {
3939     assert( w2m.size() == m2w.size() );
3940     assert( nidx > -1 && nidx < int(m2w.size()));
3941 
3942     m2w_ = m2w[nidx];
3943     w2m_ = w2m[nidx];
3944 }
3945 
3946 /**
3947 stree::get_node_product
3948 -------------------------
3949 
3950 local:true
3951    note that the get_ancestors does not include the outer node index,
3952    where the outer node is the one that has parent of different repeat_idx,
3953    or root with no parent.
3954 
3955    The reason to skip the outer node is because the transform for that node
3956    will differ for all instances whereas with local:true are operating
3957    within the frame of the instance such that the transform product will
3958    be the same for all instances.  Indeed that skipped transform
3959    will become part of the instance transforms.
3960 
3961 
3962 Q: What transforms are provided when called from the nidx of outer instanced nodes ?
3963 A: In that case num_nodes=0 so identity transforms are returned.
3964 
3965 **/
3966 
3967 inline void stree::get_node_product(
3968                       glm::tmat4x4<double>& m2w_,
3969                       glm::tmat4x4<double>& w2m_,
3970                       int nidx,
3971                       bool local,
3972                       bool reverse,
3973                       std::ostream* out,
3974                       VTR* t_stack ) const
3975 {
3976 
3977     if(out) *out << "stree::get_node_product.HEAD nidx " << nidx << " local " << local << " reverse " << reverse << "\n" ;
3978 
3979     std::vector<int> nodes ;
3980     get_ancestors(nodes, nidx, local, out);  // root-first-order (from collecting parent links then reversing the vector)
3981 
3982     bool is_local_outer = local && is_outer_node(nidx) ;
3983     if(is_local_outer == false ) nodes.push_back(nidx);
3984     // dont include the local_outer node, here either
3985 
3986 
3987     int num_nodes = nodes.size();
3988 
3989     if(out)
3990     {
3991         *out << "stree::get_node_product"
3992              << " nidx " << nidx
3993              << " local " << local
3994              << " reverse " << reverse
3995              << " is_local_outer " << is_local_outer
3996              << " num_nodes " << num_nodes
3997              << " ["
3998              ;
3999         for(int i=0 ; i < num_nodes ; i++ ) *out << " " << nodes[i] ;
4000         *out << "]" << std::endl ;
4001     }
4002 
4003 
4004     glm::tmat4x4<double> tp(1.);
4005     glm::tmat4x4<double> vp(1.);
4006 
4007     for(int i=0 ; i < num_nodes ; i++ )
4008     {
4009         int j = num_nodes - 1 - i ;
4010         int ii = nodes[reverse ? j : i] ;
4011         int jj = nodes[reverse ? i : j] ;
4012 
4013         if(out)
4014         {
4015             const char* s_ii = get_soname(ii) ;
4016             const char* s_jj = get_soname(jj) ;
4017 
4018             *out
4019                << std::endl
4020                << " i " << i
4021                << " j " << j
4022                << " ii " << ii
4023                << " jj " << jj
4024                << " s_ii " << s_ii
4025                << " s_jj " << s_jj
4026                << std::endl
4027                ;
4028         }
4029 
4030         glm::tmat4x4<double> it(1.);
4031         glm::tmat4x4<double> iv(1.);
4032         get_node_transform( it, iv, ii );   // m2w and w2m for nidx:ii
4033         if(out) *out << stra<double>::Desc(it, iv, "it", "iv" );
4034         if(t_stack) t_stack->push_back(it);
4035 
4036         glm::tmat4x4<double> jt(1.);
4037         glm::tmat4x4<double> jv(1.);
4038         get_node_transform( jt, jv, jj );  // m2w and w2m for nidx:jj
4039         if(out) *out << stra<double>::Desc(jt, jv, "jt", "jv" );
4040 
4041         tp *= it ;
4042         vp *= jv ; // inverse-transform product in opposite order
4043 
4044         //if(out) *out << stra<double>::Desc(tp, vp, "tp", "vp" );   // product not always identity
4045     }
4046 
4047     if(out) *out
4048          << "stree::get_node_product tp:product.it, vp:product.jv in opposite order\n"
4049          << stra<double>::Desc(tp, vp, "tp", "vp" )
4050          ;
4051 
4052     memcpy( glm::value_ptr(m2w_), glm::value_ptr(tp), sizeof(glm::tmat4x4<double>) );
4053     memcpy( glm::value_ptr(w2m_), glm::value_ptr(vp), sizeof(glm::tmat4x4<double>) );
4054 
4055     if(out) *out << "stree::get_node_product.TAIL nidx " << nidx << " local " << local << " reverse " << reverse << "\n" ;
4056 }
4057 
4058 
4059 inline std::string stree::desc_node_product( glm::tmat4x4<double>& m2w_, glm::tmat4x4<double>& w2m_, int nidx, bool local, bool reverse ) const
4060 {
4061     VTR* t_stack = nullptr ;
4062     std::stringstream ss ;
4063     ss << "stree::desc_node_product" ;
4064     get_node_product( m2w_, w2m_, nidx, local, reverse, &ss, t_stack );
4065     std::string s = ss.str();
4066     return s ;
4067 }
4068 
4069 /**
4070 stree::get_combined_transform : combining structural and CSG transforms
4071 ------------------------------------------------------------------------
4072 
4073 NB::
4074 
4075      local = node.repeat_index > 0
4076 
4077 **The local argument to get_node_product drastically changes the
4078 character of the returned transforms for the global ridx:0 and
4079 the "local" ridx>0 instances by changing which transforms
4080 are included in the product**
4081 
4082 * combines structural (volume level) and CSG (solid level) transforms
4083 * canonical usage from CSGImport::importNode
4084 
4085 The CSG constituent *snd/sn* lvid is required to directly match that of
4086 the structural *snode*, not just by containment but directly.
4087 
4088 
4089 modelFrame
4090    typically close to origin coordinates
4091 
4092 worldFrame
4093    typically far from origin coordinates
4094 
4095 m2w
4096    modelFrame to worldFrame
4097 w2m
4098    worldFrame to modelFrame
4099 
4100 get_m2w_product
4101     product of m2w transforms from root then down volume tree to the structural snode *index*
4102 
4103 get_w2m_product
4104     product of w2m transforms from snode *index* then up the structural tree
4105 
4106 
4107 GParts::applyPlacementTransform does::
4108 
4109     1243     for(unsigned i=0 ; i < ni ; i++)
4110     1244     {
4111     1245         nmat4triple* tvq = m_tran_buffer->getMat4TriplePtr(i) ;
4112     1247         bool match = true ;
4113     1248         const nmat4triple* ntvq = nmat4triple::make_transformed( tvq, placement, reversed, "GParts::applyPlacementTransform", match );
4114     1251         if(!match) mismatch.push_back(i);
4115     1253         m_tran_buffer->setMat4Triple( ntvq, i );
4116 
4117 
4118 
4119 sysrap/tests/stree_create_test.cc
4120    sets up a geometry with structural translations+rotations
4121    and csg translations+rotations+scales to test the get_transform product ordering
4122 
4123 
4124 local:true for instances node.repeat_index > 0
4125     get_node_product with local:true gives structural transform
4126     product within the instance, excluding the outer node transform.
4127     That transform product will often be identity.
4128 
4129 
4130 **/
4131 
4132 inline void stree::get_combined_transform(
4133     glm::tmat4x4<double>& t,
4134     glm::tmat4x4<double>& v,
4135     const snode& node,
4136     const sn* nd,
4137     std::ostream* out,
4138     VTR* t_stack ) const
4139 {
4140     bool local = node.repeat_index > 0 ;   // for instanced nodes restrict to same repeat_index excluding outer
4141     if(out) *out << "stree::get_combined_transform.HEAD local " << local << "\n" ;
4142 
4143     glm::tmat4x4<double> tt(1.) ;
4144     glm::tmat4x4<double> vv(1.) ;
4145     get_node_product( tt, vv, node.index, local, false, out, t_stack ); // reverse:false
4146     if(out) *out << "stree::get_combined_transform.nd (tt,vv)\n" << stra<double>::Desc( tt, vv, "(tt)", "(vv)" ) << "\n\n" ;
4147 
4148 
4149     glm::tmat4x4<double> tc(1.) ;
4150     glm::tmat4x4<double> vc(1.) ;
4151 
4152     if(nd)
4153     {
4154         assert( node.lvid == nd->lvid );
4155         sn::NodeTransformProduct(nd->idx(), tc, vc, false, out, t_stack );  // reverse:false
4156         if(out) *out << "stree::get_combined_transform.nd (tc,vc)\n" << stra<double>::Desc( tc, vc, "(tc)", "(vc)" ) << "\n\n" ;
4157     }
4158 
4159     // combine structural (volume level) and CSG (solid level) transforms
4160     t = tt * tc ;
4161     v = vc * vv ;
4162 
4163     if(out) *out << "stree::get_combined_transform.product (t,v)\n" << stra<double>::Desc( t, v, "(tt*tc)", "(vc*vv)" ) << "\n\n" ;
4164     if(out) *out << "stree::get_combined_transform.TAIL local " << local << "\n" ;
4165 }
4166 
4167 inline std::string stree::desc_combined_transform(
4168     glm::tmat4x4<double>& t,
4169     glm::tmat4x4<double>& v,
4170     const snode& node,
4171     const sn* nd ) const
4172 {
4173     VTR* t_stack = nullptr ;
4174     std::stringstream ss ;
4175     ss << "stree::desc_combined_transform" << std::endl;
4176     get_combined_transform(t, v, node, nd, &ss, t_stack );
4177     std::string str = ss.str();
4178     return str ;
4179 }
4180 
4181 /**
4182 stree::get_combined_tran_and_aabb
4183 --------------------------------------
4184 
4185 Critical usage of this from CSGImport::importNode
4186 
4187 0. early exits returning nullptr for non leaf nodes
4188 1. gets combined structural(snode.h) and CSG tree(sn.h) transform
4189 2. collects that combined transform and its inverse (t,v) into Tran instance
4190 3. copies leaf frame bbox values from the CSG nd into callers aabb array
4191 4. transforms the bbox of the callers aabb array using the combined structural node
4192    + tree node transform
4193 
4194 
4195 Note that sn::uncoincide needs CSG tree frame AABB but whereas this needs leaf
4196 frame AABB. These two demands are met by changing the AABB frame
4197 within sn::postconvert
4198 
4199 **/
4200 
4201 inline const Tran<double>* stree::get_combined_tran_and_aabb(
4202     double* aabb,
4203     const snode& node,
4204     const sn* nd,
4205     std::ostream* out,
4206     VTR* t_stack
4207     ) const
4208 {
4209     assert( nd );
4210     if(!CSG::IsLeaf(nd->typecode)) return nullptr ;
4211 
4212 
4213     glm::tmat4x4<double> t(1.) ;
4214     glm::tmat4x4<double> v(1.) ;
4215     get_combined_transform(t, v, node, nd, out, t_stack );
4216 
4217     // NB ridx:0 full stack of transforms from root down to CSG constituent nodes
4218     //    ridx>0 only within the instance and within constituent CSG tree
4219 
4220     const Tran<double>* tv = new Tran<double>(t, v);
4221 
4222     nd->copyBB_data( aabb );
4223     stra<double>::Transform_AABB_Inplace(aabb, t);
4224 
4225     return tv ;
4226 }
4227 
4228 
4229 /**
4230 stree::get_transformed_aabb
4231 ----------------------------
4232 
4233 snode.repeat_index:0
4234     full stack of transforms from root down into CSG constituent sn nodes
4235 snode.repeat_index>0
4236     only within the instance and down into constituent sn nodes
4237 
4238 **/
4239 
4240 inline void stree::get_transformed_aabb(
4241     double* aabb,
4242     const snode& node,
4243     const sn* nd,
4244     std::ostream* out,
4245     VTR* t_stack
4246     ) const
4247 {
4248     assert( nd );
4249     if(!CSG::IsLeaf(nd->typecode)) return ;
4250 
4251     glm::tmat4x4<double> t(1.) ;
4252     glm::tmat4x4<double> v(1.) ;
4253     get_combined_transform(t, v, node, nd, out, t_stack );
4254 
4255     nd->copyBB_data( aabb );
4256     stra<double>::Transform_AABB_Inplace(aabb, t);
4257 }
4258 
4259 
4260 /**
4261 stree::get_prim_aabb
4262 ---------------------
4263 
4264 Follow pattern of::
4265 
4266     CSGImport::importPrim_
4267     CSGImport::importNode
4268 
4269 1. gets CSG constituent nds for the node.lvid with sn::GetLVNodesComplete
4270 2. combines the transformed constituent bounding box
4271 
4272 HMM: THIS DOES NOT CONSIDER LISTNODE
4273 
4274 **/
4275 inline void stree::get_prim_aabb( double* aabb, const snode& node, std::ostream* out, VTR* t_stack ) const
4276 {
4277     std::vector<const sn*> nds ;
4278     sn::GetLVNodesComplete(nds, node.lvid); // many nullptr in unbalanced deep complete binary trees
4279     int numParts = nds.size();
4280 
4281     std::array<double,6> pbb = {} ;
4282 
4283     for(int i=0 ; i < numParts ; i++)
4284     {
4285         int partIdx = i ;
4286         const sn* nd = nds[partIdx];
4287 
4288         int  typecode = nd ? nd->typecode : CSG_ZERO ;
4289         bool leaf = CSG::IsLeaf(typecode) ;
4290         if(!leaf) continue ;
4291 
4292         bool is_complemented_primitive = nd->complement && CSG::IsPrimitive(typecode) ;
4293         if(is_complemented_primitive) continue ;
4294 
4295         bool external_bbox_is_expected = CSG::ExpectExternalBBox(typecode);
4296         bool expect = external_bbox_is_expected == false ;
4297         if(!expect) std::cerr << " NOT EXPECTING LEAF WITH EXTERNAL BBOX EXPECTED : DEFERRED UNTIL HAVE EXAMPLES\n" ;
4298         assert(expect);
4299         if(!expect) std::raise(SIGINT);
4300 
4301         std::array<double,6> nbb ;
4302         get_transformed_aabb( nbb.data(), node, nd, out, t_stack );
4303         s_bb::IncludeAABB( pbb.data(), nbb.data(), out );
4304     }
4305     for(int i=0 ; i < 6 ; i++) aabb[i] = pbb[i] ;
4306 }
4307 
4308 
4309 inline void stree::get_prim_aabb( std::vector<std::array<double,6>>& vbb, const std::vector<snode>& nodes) const
4310 {
4311     for( size_t i = 0 ; i < nodes.size() ; i++ )
4312     {
4313         const snode& node = nodes[i];
4314         BB bb ;
4315         get_prim_aabb( bb.data(), node, nullptr, nullptr );
4316         vbb.push_back(bb);
4317     }
4318 }
4319 
4320 
4321 
4322 /**
4323 stree::get_nodes : node indices of nodes with *sub* subtree digest
4324 ---------------------------------------------------------------------
4325 
4326 Collects node indices of all nodes with the subtree digest provided in the argument.
4327 This simply matches against the subs vector that contains subtree digest
4328 strings for all nodes in node order.
4329 
4330 Hence when providing the *sub* digest of factor subtrees this
4331 will return the node indices of all "outer volume" nodes
4332 of that factor.  This is done by stree::get_factor_nodes
4333 
4334 **/
4335 
4336 inline void stree::get_nodes(std::vector<int>& nodes, const char* sub) const
4337 {
4338     for(unsigned i=0 ; i < subs.size() ; i++)
4339         if(strcmp(subs[i].c_str(), sub)==0)
4340             nodes.push_back(int(i)) ;
4341 }
4342 
4343 inline void stree::get_depth_range(unsigned& mn, unsigned& mx, const char* sub) const
4344 {
4345     std::vector<int> nodes ;
4346     get_nodes(nodes, sub);
4347     mn = 100 ;
4348     mx = 0 ;
4349     for(unsigned i=0 ; i < nodes.size() ; i++)
4350     {
4351         unsigned nidx = nodes[i];
4352         const snode& sn = nds[nidx] ;
4353         if( unsigned(sn.depth) > mx ) mx = sn.depth ;
4354         if( unsigned(sn.depth) < mn ) mn = sn.depth ;
4355     }
4356 }
4357 
4358 
4359 inline int stree::get_first( const char* sub ) const
4360 {
4361     for(unsigned i=0 ; i < subs.size() ; i++) if(strcmp(subs[i].c_str(), sub)==0) return int(i) ;
4362     return -1 ;
4363 }
4364 
4365 
4366 /**
4367 stree::get_tree_digest
4368 ------------------------
4369 
4370 Returns subtree digest of *nidx* zero, the root node.
4371 The string returned can be accessed at bash level from a
4372 persisted geometry with eg::
4373 
4374     (ok) A[blyth@localhost InstallArea]$ head -1 .opticks/GEOM/$GEOM/CSGFoundry/SSim/stree/subs_names.txt
4375     f94d93c709d76d3f6c8cc0ad6c25e61a
4376 
4377 **/
4378 
4379 inline const char* stree::get_tree_digest() const
4380 {
4381     int num_sub = subs.size() ;
4382     return num_sub > 0 ? subs[0].c_str() : nullptr ;
4383 }
4384 
4385 inline std::string stree::make_tree_digest(const std::vector<unsigned char>& extra) const
4386 {
4387     int nidx = 0 ;
4388     return subtree_digest_plus(nidx, extra);
4389 }
4390 
4391 /**
4392 stree::subtree_digest
4393 -----------------------
4394 
4395 Returns digest string of the subtree of *nidx* node.  For root node
4396 the subtree is expected to cover all other nodes.
4397 
4398 Canonical usage from stree::classifySubtrees
4399 
4400 0. get progeny indices of *nidx* node
4401 1. form digest from lvid of *nidx* node and *digs* string from all the progeny nodes
4402 
4403 The *nidx* node transform is not included in the subtree digest as wish
4404 the sameness of subtrees that are placed differently to be reflected by them
4405 having the same subtree digest.
4406 
4407 The basis *digs* of each node are obtained and collected by the
4408 recursive U4Tree::initNodes_r
4409 
4410 
4411 Q: Is nidx 0 the root node ?
4412 A: YES, see U4Tree::initNodes, nidx 0 corresponds to the *top*
4413    Geant4 volume passed to U4Tree::Create which is typically
4414    the outermost "World" volume
4415 
4416 **/
4417 
4418 
4419 inline std::string stree::subtree_digest(int nidx) const
4420 {
4421     std::vector<int> progeny ;
4422     get_progeny(progeny, nidx);
4423 
4424     sdigest u ;
4425     u.add( nds[nidx].lvid );  // just lvid of subtree top, not the transform
4426     for(unsigned i=0 ; i < progeny.size() ; i++) u.add(digs[progeny[i]]) ;
4427     return u.finalize() ;
4428 }
4429 
4430 inline std::string stree::subtree_digest_plus(int nidx, const std::vector<unsigned char>& extra) const
4431 {
4432     std::vector<int> progeny ;
4433     get_progeny(progeny, nidx);
4434 
4435     sdigest u ;
4436     u.add( extra );
4437     u.add( nds[nidx].lvid );  // just lvid of subtree top, not the transform
4438     for(unsigned i=0 ; i < progeny.size() ; i++) u.add(digs[progeny[i]]) ;
4439     return u.finalize() ;
4440 }
4441 
4442 
4443 
4444 
4445 
4446 inline std::string stree::depth_spacer(int depth) // static
4447 {
4448     std::string spacer(MAXDEPTH, ' ');
4449     if(depth < MAXDEPTH) spacer[depth] = '+' ;
4450     return spacer ;
4451 }
4452 
4453 inline std::string stree::desc_node_(int nidx, const sfreq* sf) const
4454 {
4455     const snode& nd = nds[nidx];
4456     const char* sub = subs[nidx].c_str();
4457     assert( nd.index == nidx );
4458     bool is_outer = is_outer_node(nidx);
4459 
4460     std::stringstream ss ;
4461     ss << depth_spacer(nd.depth) ;
4462     ss << nd.desc() ;
4463     ss << " ou " << ( is_outer ? "Y" : "N" ) ;
4464     if(sf) ss << " " << sf->desc(sub) ;
4465     ss << " " << soname[nd.lvid]  ;
4466     std::string s = ss.str();
4467     return s ;
4468 }
4469 inline std::string stree::desc_node(int nidx, bool show_sub) const
4470 {
4471    const sfreq* sf = show_sub ? subs_freq : nullptr ;
4472    return desc_node_(nidx, sf );
4473 }
4474 
4475 
4476 
4477 inline std::string stree::desc_nodes(const std::vector<int>& nn, int edgeitems) const
4478 {
4479     int num = nn.size();
4480     std::stringstream ss ;
4481     ss << "stree::desc_nodes " << num << std::endl ;
4482     for(int i=0 ; i < num ; i++)
4483     {
4484         if( i < edgeitems || ( i > num - edgeitems ))
4485         {
4486             ss << desc_node(nn[i]) << std::endl ;
4487         }
4488         else if( i == edgeitems )
4489         {
4490             ss << " ... " << std::endl ;
4491         }
4492     }
4493     std::string s = ss.str();
4494     return s ;
4495 }
4496 
4497 // HMM: could use template specialization to avoid the duplication here
4498 inline std::string stree::desc_nodes_(const std::vector<snode>& nn, int edgeitems) const
4499 {
4500     int num = nn.size();
4501     std::stringstream ss ;
4502     ss << "stree::desc_nodes_ " << num << std::endl ;
4503     for(int i=0 ; i < num ; i++)
4504     {
4505         if( i < edgeitems || ( i > num - edgeitems ))
4506         {
4507             ss << desc_node(nn[i].index) << std::endl ;
4508         }
4509         else if( i == edgeitems )
4510         {
4511             ss << " ... " << std::endl ;
4512         }
4513     }
4514     std::string s = ss.str();
4515     return s ;
4516 }
4517 
4518 
4519 
4520 /**
4521 stree::desc_node_solids
4522 -------------------------
4523 
4524 HUH: this is horribly repetitive and long presentation of soname for every node
4525 
4526 **/
4527 
4528 
4529 inline std::string stree::desc_node_solids() const
4530 {
4531     int num_nodes = get_num_nodes();
4532     std::stringstream ss ;
4533     ss << "stree::desc_node_solids num_nodes " << num_nodes  << std::endl ;
4534     for(int nidx=0 ; nidx < num_nodes ; nidx++)
4535     {
4536         ss
4537             << " nidx " << std::setw(6) << nidx
4538             << " so " << get_soname(nidx)
4539             << std::endl
4540             ;
4541     }
4542     std::string str = ss.str();
4543     return str ;
4544 }
4545 
4546 
4547 
4548 /**
4549 stree::desc_solids_0
4550 ---------------------
4551 
4552 OBSERVE THAT THE stree::solids ARE NOT PERSISTED,
4553 SO THIS IS ONLY USEFUL DURING TRANSLATION AFTER U4Tree::initSolids
4554 
4555 TODO : FIND WAY TO RECOVER THE stree::solids vector using sn::Get
4556 methods to access the s_csg.h persisted sn.h
4557 **/
4558 
4559 inline std::string stree::desc_solids_0() const
4560 {
4561     int num_solids = solids.size() ;
4562     std::stringstream ss ;
4563     ss << "[stree::desc_solids_0 num_solids " << num_solids  << "\n" ;
4564     for(int i=0 ; i < num_solids ; i++)
4565     {
4566         const char* srn = soname_raw[i].c_str();
4567         const char* son = soname[i].c_str();
4568 
4569         const sn* root = solids[i] ;
4570 
4571         int lvid = i ;
4572         const sn* root2 = sn::GetLVRoot(lvid);
4573         bool root2_match = root == root2 ;
4574 
4575         ss
4576             << " i " << std::setw(3) << i
4577             << " (sn)root.lvid " << std::setw(3) << root->lvid
4578             << " root2_match " << ( root2_match ? "YES" : "NO " )
4579             << " soname_raw[i] " << std::setw(60) << ( srn ? srn : "-" )
4580             << " soname[i] " << std::setw(60) << ( son ? son : "-" )
4581             << "\n"
4582             ;
4583     }
4584     ss << "]stree::desc_solids_0 num_solids " << num_solids  << "\n" ;
4585     std::string str = ss.str();
4586     return str ;
4587 }
4588 
4589 inline std::string stree::desc_solids() const
4590 {
4591     int num_soname = soname.size() ;
4592     std::stringstream ss ;
4593     ss << "[stree::desc_solids num_soname " << num_soname  << "\n" ;
4594     for(int i=0 ; i < num_soname ; i++)
4595     {
4596         int lvid = i ;
4597 
4598         bool ift = is_force_triangulate(lvid) ;
4599         bool iat = is_auto_triangulate(lvid) ;
4600         bool it = is_triangulate(lvid) ;
4601 
4602         const char* son = lvid < int(soname.size())     ? soname[lvid].c_str() : nullptr ;
4603         const sn* root = sn::GetLVRoot(lvid);
4604         assert(root);
4605         assert( root->lvid == lvid );
4606         ss
4607             << " lvid " << std::setw(3) << lvid
4608             << " is_force_triangulate " << ( ift ? "YES" : "NO " )
4609             << " is_auto_triangulate " << ( iat ? "YES" : "NO " )
4610             << " is_triangulate " << ( it ? "YES" : "NO " )
4611             << " soname[lvid] " << std::setw(60) << ( son ? son : "-" )
4612             << " " << ( root ? root->rbrief() : "" )
4613             << "\n"
4614             ;
4615     }
4616     ss << "]stree::desc_solids num_soname " << num_soname  << "\n" ;
4617     std::string str = ss.str();
4618     return str ;
4619 }
4620 
4621 inline std::string stree::desc_triangulate() const
4622 {
4623     int num_soname = soname.size() ;
4624 
4625     int count_triangulate = 0 ;
4626     int count_force_triangulate = 0 ;
4627     int count_auto_triangulate = 0 ;
4628     int count_auto_triangulate_only = 0 ;
4629     int count_force_triangulate_only = 0 ;
4630 
4631     std::vector<std::string> names_auto_triangulate_only ;
4632     std::vector<std::string> names_force_triangulate_only ;
4633 
4634     std::stringstream ss ;
4635     ss << "[stree::desc_triangulate num_soname " << num_soname  << "\n" ;
4636     for(int i=0 ; i < num_soname ; i++)
4637     {
4638         int lvid = i ;
4639 
4640         bool ift = is_force_triangulate(lvid) ;
4641         bool iat = is_auto_triangulate(lvid) ;
4642         bool it = is_triangulate(lvid) ;
4643         bool fto = ift == true && iat == false ;
4644         bool ato = ift == false && iat == true ;
4645 
4646         if(it)  count_triangulate += 1 ;
4647         if(ift) count_force_triangulate += 1 ;
4648         if(iat) count_auto_triangulate += 1 ;
4649         if(fto) count_force_triangulate_only += 1 ;
4650         if(ato) count_auto_triangulate_only += 1 ;
4651 
4652         if(!it) continue ;
4653         //if(!fto) continue ;
4654 
4655         const char* son = lvid < int(soname.size())     ? soname[lvid].c_str() : nullptr ;
4656         assert(son);
4657 
4658         if(ato) names_auto_triangulate_only.push_back(son);
4659         if(fto) names_force_triangulate_only.push_back(son);
4660 
4661         const sn* root = sn::GetLVRoot(lvid);
4662         assert(root);
4663         assert( root->lvid == lvid );
4664         ss
4665             << " lvid " << std::setw(3) << lvid
4666             << " is_force_triangulate " << ( ift ? "YES" : "NO " )
4667             << " is_auto_triangulate " << ( iat ? "YES" : "NO " )
4668             << " is_triangulate " << ( it ? "YES" : "NO " )
4669             << " force_triangulate_only " << ( fto ? "YES" : "NO " )
4670             << " soname[lvid] " << std::setw(60) << ( son ? son : "-" )
4671             << " " << ( root ? root->rbrief() : "" )
4672             << "\n"
4673             ;
4674     }
4675 
4676     ss << "-stree::desc_triangulate.[names_auto_triangulate_only\n" ;
4677     for(int i=0 ; i < int(names_auto_triangulate_only.size()) ; i++ ) ss << names_auto_triangulate_only[i] << "\n" ;
4678     ss << "-stree::desc_triangulate.]names_auto_triangulate_only\n" ;
4679 
4680     ss << "-stree::desc_triangulate.[names_force_triangulate_only\n" ;
4681     for(int i=0 ; i < int(names_force_triangulate_only.size()) ; i++ ) ss << names_force_triangulate_only[i] << "\n" ;
4682     ss << "-stree::desc_triangulate.]names_force_triangulate_only\n" ;
4683 
4684 
4685     ss << "]stree::desc_triangulate\n"
4686        << " num_soname                   " << num_soname  << "\n"
4687        << " count_triangulate            " << count_triangulate << "\n"
4688        << " count_auto_triangulate       " << count_auto_triangulate << "\n"
4689        << " count_force_triangulate      " << count_force_triangulate << "\n"
4690        << " count_auto_triangulate_only  " << count_auto_triangulate_only << "\n"
4691        << " count_force_triangulate_only " << count_force_triangulate_only  << "\n"
4692        ;
4693 
4694 
4695     std::string str = ss.str();
4696     return str ;
4697 
4698 
4699 }
4700 
4701 
4702 
4703 
4704 inline std::string stree::desc_solid(int lvid) const
4705 {
4706     const sn* root = sn::GetLVRoot(lvid) ;
4707     const std::string& lvn = get_lvid_soname_(lvid) ;
4708     assert( root ) ;
4709 
4710     std::stringstream ss ;
4711     ss << "stree::desc_solid"
4712        << " lvid " << lvid
4713        << " lvn " << lvn
4714        << " root " << ( root ? "Y" : "N" )
4715        << " " << ( root ? root->rbrief() : "" )
4716        ;
4717     std::string str = ss.str();
4718     return str ;
4719 }
4720 
4721 
4722 
4723 /**
4724 stree::make_trs
4725 -----------------
4726 
4727 This is used from U4Tree::simtrace_scan as the basis for u4/tests/U4SimtraceTest.sh
4728  HMM: this is based on GTD: "GGeo Transform Debug" so it is not future safe
4729 
4730    * TODO: adopt the modern equivalent of GTD, or create one if non-existing
4731 
4732 2. saves solid names for every node of the geometry, so thats lots of
4733    repeated solid names in full geometries
4734 
4735 3. implication is that the number of nodes in the geometry
4736    matches the number of gtd and trs transforms (CHECK THAT)
4737 
4738 **/
4739 
4740 inline NP* stree::make_trs() const
4741 {
4742     NP* trs = NP::Make<double>( gtd.size(), 4, 4 );
4743     trs->read2<double>( (double*)gtd.data() ) ;
4744 
4745     std::vector<std::string> nd_soname ;
4746     int num_nodes = get_num_nodes();
4747     for(int nidx=0 ; nidx < num_nodes ; nidx++)
4748     {
4749         const char* so = get_soname(nidx);
4750         nd_soname.push_back(so);
4751     }
4752     trs->set_names(nd_soname);
4753 
4754     return trs ;
4755 }
4756 
4757 inline void stree::save_trs(const char* fold) const
4758 {
4759     NP* trs = make_trs();
4760     trs->save(fold, TRS );
4761 }
4762 
4763 
4764 
4765 
4766 
4767 
4768 
4769 
4770 
4771 inline void stree::save( const char* base) const
4772 {
4773     const char* dir = U::Resolve(base, RELDIR );
4774     save_(dir);
4775 }
4776 inline void stree::save_( const char* dir ) const
4777 {
4778     NPFold* fold = serialize() ;
4779     fold->save(dir) ;
4780     save_desc(dir);   // saves .txt for most desc methods into <base>/stree/desc
4781 }
4782 
4783 inline NPFold* stree::serialize() const
4784 {
4785     NPFold* fold = new NPFold ;
4786 
4787     NP* _nds = NPX::ArrayFromVec<int,snode>( nds, snode::NV ) ;
4788     NP* _rem = NPX::ArrayFromVec<int,snode>( rem, snode::NV ) ;
4789     NP* _tri = NPX::ArrayFromVec<int,snode>( tri, snode::NV ) ;
4790     NP* _m2w = NPX::ArrayFromVec<double,glm::tmat4x4<double>>( m2w, 4, 4 ) ;
4791     NP* _w2m = NPX::ArrayFromVec<double,glm::tmat4x4<double>>( w2m, 4, 4 ) ;
4792     NP* _gtd = NPX::ArrayFromVec<double,glm::tmat4x4<double>>( gtd, 4, 4 ) ;
4793 
4794     fold->add( NDS, _nds );
4795     fold->add( REM, _rem );
4796     fold->add( TRI, _tri );
4797     fold->add( M2W, _m2w );
4798     fold->add( W2M, _w2m );
4799     fold->add( GTD, _gtd );
4800 
4801     NP* _mtname = NPX::Holder(mtname) ;
4802     NP* _mtname_no_rindex = NPX::Holder(mtname_no_rindex) ;
4803     NP* _mtindex = NPX::ArrayFromVec<int,int>( mtindex );
4804     NP* _mtline = NPX::ArrayFromVec<int,int>( mtline );
4805     NP* _force_triangulate_lvid = NPX::ArrayFromVec<int,int>( force_triangulate_lvid );
4806 
4807 
4808     fold->add( MTNAME,  _mtname );
4809     fold->add( MTNAME_NO_RINDEX,  _mtname_no_rindex );
4810     fold->add( MTINDEX, _mtindex );
4811     fold->add( MTLINE , _mtline );
4812 
4813     fold->add( FORCE_TRIANGULATE_LVID, _force_triangulate_lvid ) ;
4814 
4815     if(material) fold->add_subfold( MATERIAL, material );
4816     if(surface)  fold->add_subfold( SURFACE,  surface );
4817     if(mesh)     fold->add_subfold( MESH,     mesh );
4818 
4819 
4820     fold->add( SUNAME,   NPX::Holder(suname) );
4821     fold->add( IMPLICIT, NPX::Holder(implicit) );
4822     //fold->add( SUINDEX,  NPX::ArrayFromVec<int,int>( suindex )  );
4823 
4824     NPFold* f_standard = standard->serialize() ;
4825     fold->add_subfold( STANDARD, f_standard );
4826     fold->add_subfold( _CSG, _csg->serialize() );
4827     fold->add( SONAME, NPX::Holder(soname) );
4828 
4829     fold->add( DIGS, NPX::Holder(digs) );
4830     fold->add( SUBS, NPX::Holder(subs) );
4831 
4832     NPFold* f_subs_freq = subs_freq->serialize() ;
4833     fold->add_subfold( SUBS_FREQ, f_subs_freq );
4834 
4835     fold->set_meta<int>("FREQ_CUT", FREQ_CUT);
4836     fold->set_meta<int>("FREQ_CUT_DEFAULT", FREQ_CUT_DEFAULT);
4837 
4838 
4839     NP* _factor = NPX::ArrayFromVec<int,sfactor>( factor, sfactor::NV );
4840 
4841     NP* _inst = NPX::ArrayFromVec<double, glm::tmat4x4<double>>( inst, 4, 4) ;
4842     NP* _iinst = NPX::ArrayFromVec<double, glm::tmat4x4<double>>( iinst, 4, 4) ;
4843 
4844     // inst_f4 crucially used from CSGImport::importInst
4845     NP* _inst_f4 = NPX::ArrayFromVec<float, glm::tmat4x4<float>>( inst_f4, 4, 4) ;
4846     NP* _iinst_f4 = NPX::ArrayFromVec<float, glm::tmat4x4<float>>( iinst_f4, 4, 4) ;
4847 
4848     NP* _inst_info = NPX::ArrayFromVec<int,int4>( inst_info, 4 ) ;
4849     NP* _inst_nidx = NPX::ArrayFromVec<int,int>( inst_nidx ) ;
4850     NP* _sensor_id = NPX::ArrayFromVec<int,int>( sensor_id ) ;
4851     NP* _sensor_name = NPX::Holder(sensor_name);
4852     NP* _mtindex_to_mtline = NPX::ArrayFromDiscoMap<int>( mtindex_to_mtline ) ;
4853 
4854 
4855 #ifdef STREE_CAREFUL
4856     if(ssys::getenvbool(stree__populate_prim_nidx))
4857     {
4858         NP* _prim_nidx = NPX::ArrayFromVec<int,int>( prim_nidx ) ;
4859         fold->add( PRIM_NIDX, _prim_nidx );
4860     }
4861     else if(ssys::getenvbool(stree__populate_nidx_prim))
4862     {
4863         NP* _prim_nidx = NPX::ArrayFromVec<int,int>( prim_nidx ) ;
4864         NP* _nidx_prim = NPX::ArrayFromVec<int,int>( nidx_prim ) ;
4865         NP* _prname = NPX::Holder(prname) ;
4866 
4867         fold->add( PRIM_NIDX, _prim_nidx );
4868         fold->add( NIDX_PRIM, _nidx_prim );
4869         fold->add( PRNAME,  _prname );
4870     }
4871 #else
4872     NP* _prim_nidx = NPX::ArrayFromVec<int,int>( prim_nidx ) ;
4873     NP* _nidx_prim = NPX::ArrayFromVec<int,int>( nidx_prim ) ;
4874     NP* _prname = NPX::Holder(prname) ;
4875 
4876     fold->add( PRIM_NIDX, _prim_nidx );
4877     fold->add( NIDX_PRIM, _nidx_prim );
4878     fold->add( PRNAME,  _prname );
4879 #endif
4880 
4881 
4882     fold->add( FACTOR, _factor );
4883     fold->add( INST,   _inst );
4884     fold->add( IINST,   _iinst );
4885     fold->add( INST_F4,   _inst_f4 );
4886     fold->add( IINST_F4,   _iinst_f4 );
4887     fold->add( INST_INFO,   _inst_info );
4888     fold->add( INST_NIDX,   _inst_nidx );
4889     fold->add( SENSOR_ID,   _sensor_id );
4890     fold->add( SENSOR_NAME, _sensor_name );
4891     fold->add( MTINDEX_TO_MTLINE, _mtindex_to_mtline  );
4892 
4893 
4894 
4895     return fold ;
4896 }
4897 
4898 
4899 
4900 
4901 template<typename S, typename T>
4902 inline void stree::ImportArray( std::vector<S>& vec, const NP* a, const char* label  )
4903 {
4904     if(a == nullptr)
4905     {
4906         std::cerr << "stree::ImportArray array is null, label[" << ( label ? label : "-" ) << "]\n" ;
4907         return ;
4908     }
4909     vec.resize(a->shape[0]);
4910     memcpy( (T*)vec.data(),    a->cvalues<T>() ,    a->arr_bytes() );
4911 }
4912 
4913 template void stree::ImportArray<snode, int>(std::vector<snode>& , const NP* , const char* label);
4914 template void stree::ImportArray<int  , int>(std::vector<int>&   , const NP* , const char* label);
4915 template void stree::ImportArray<int4 , int>(std::vector<int4>&  , const NP* , const char* label);
4916 template void stree::ImportArray<glm::tmat4x4<double>, double>(std::vector<glm::tmat4x4<double>>& , const NP*, const char* label );
4917 template void stree::ImportArray<glm::tmat4x4<float>, float>(std::vector<glm::tmat4x4<float>>& , const NP*, const char* label );
4918 template void stree::ImportArray<sfactor, int>(std::vector<sfactor>& , const NP*, const char* label );
4919 
4920 inline void stree::ImportNames( std::vector<std::string>& names, const NP* a, const char* label ) // static
4921 {
4922     if( a == nullptr )
4923     {
4924         std::cerr << "stree::ImportNames array is null, label[" << ( label ? label : "-" ) << "]\n" ;
4925         std::cerr << "(typically this means the stree.h serialization code has changed compared to the version used for saving the tree)\n" ;
4926         return ;
4927     }
4928     a->get_names(names);
4929 }
4930 
4931 
4932 inline stree* stree::Load(const char* _base, const char* reldir ) // static
4933 {
4934     const char* base = spath::ResolveTopLevel(_base) ;
4935     stree* st = nullptr ;
4936     if(base)
4937     {
4938         st = new stree ;
4939         st->load(base, reldir);
4940     }
4941     return st ;
4942 }
4943 inline int stree::load( const char* base, const char* reldir )
4944 {
4945     const char* dir = U::Resolve(base, reldir );
4946     int rc = load_(dir);
4947     return rc ;
4948 }
4949 
4950 inline int stree::load_( const char* dir )
4951 {
4952     loaddir = dir ? strdup(dir) : nullptr ;
4953     if(level > 0) std::cerr << "stree::load_ " << ( dir ? dir : "-" ) << std::endl ;
4954     NPFold* top = NPFold::Load(dir) ;
4955     import_(top);
4956     return 0 ;
4957 }
4958 
4959 /**
4960 stree::import_
4961 ----------------
4962 
4963 The stree is imported by SSim::load_ before CSGFoundry instanciation, example call stack::
4964 
4965     stree::import_
4966     SSim::load_
4967     SSim::load
4968     SSim::Load
4969     CSGFoundry::Load_   ## static loader invoked before CSGFoundry::CSGFoundry ctor which uses SSim::Get
4970     CSGFoundry::Load
4971     CSGOptiX::SimtraceMain
4972     main
4973 
4974 **/
4975 
4976 inline void stree::import_(const NPFold* fold)
4977 {
4978     if( fold == nullptr )
4979     {
4980         std::cerr
4981             << "stree::import_"
4982             << " : ERROR : null fold "
4983             << std::endl ;
4984         return ;
4985     }
4986 
4987     ImportArray<snode, int>( nds,                  fold->get(NDS), NDS );
4988     ImportArray<snode, int>( rem,                  fold->get(REM), REM );
4989     ImportArray<snode, int>( tri,                  fold->get(TRI), TRI );
4990     ImportArray<glm::tmat4x4<double>, double>(m2w, fold->get(M2W), M2W );
4991     ImportArray<glm::tmat4x4<double>, double>(w2m, fold->get(W2M), W2M );
4992     ImportArray<glm::tmat4x4<double>, double>(gtd, fold->get(GTD), GTD );
4993 
4994     ImportNames( soname,            fold->get(SONAME) , SONAME);
4995     ImportNames( mtname,            fold->get(MTNAME) , MTNAME);
4996     ImportNames( mtname_no_rindex,  fold->get(MTNAME_NO_RINDEX), MTNAME_NO_RINDEX );
4997     ImportNames( suname,            fold->get(SUNAME) ,  SUNAME );
4998     ImportNames( implicit,          fold->get(IMPLICIT), IMPLICIT);
4999 
5000     ImportArray<int, int>( force_triangulate_lvid, fold->get(FORCE_TRIANGULATE_LVID), FORCE_TRIANGULATE_LVID );
5001 
5002     ImportArray<int, int>( mtindex, fold->get(MTINDEX),  MTINDEX );
5003     //ImportArray<int, int>( suindex, fold->get(SUINDEX), SUINDEX );
5004 
5005     NPX::DiscoMapFromArray<int>( mtindex_to_mtline, fold->get(MTINDEX_TO_MTLINE) );
5006 
5007     NPFold* f_standard = fold->get_subfold(STANDARD) ;
5008 
5009     if(f_standard->is_empty())
5010     {
5011         std::cerr
5012             << "stree::import_ skip asserts for empty f_standard : assuming trivial test geometry "
5013             << std::endl
5014             ;
5015     }
5016     else
5017     {
5018         standard->import(f_standard);
5019 
5020         assert( standard->bd );
5021         NPX::VecFromArray<int4>( vbd, standard->bd );
5022         standard->bd->get_names( bdname );
5023 
5024         assert( standard->bnd );
5025         //import_bnd( standard->bnd );
5026     }
5027 
5028 
5029     NPFold* csg_f = fold->get_subfold(_CSG) ;
5030 
5031     if(csg_f == nullptr) std::cerr
5032         << "stree::import"
5033         << " FAILED : DUE TO LACK OF subfold _CSG : " << _CSG
5034         << std::endl
5035         ;
5036 
5037     _csg->import(csg_f);
5038 
5039 
5040     ImportNames( digs, fold->get(DIGS), DIGS );
5041     ImportNames( subs, fold->get(SUBS), SUBS );
5042 
5043     NPFold* f_subs_freq = fold->get_subfold(SUBS_FREQ) ;
5044     subs_freq->import(f_subs_freq);
5045 
5046     FREQ_CUT = fold->get_meta<int>("FREQ_CUT");
5047 
5048 
5049     ImportArray<sfactor, int>( factor, fold->get(FACTOR), FACTOR );
5050 
5051     material = fold->get_subfold( MATERIAL) ;
5052     surface  = fold->get_subfold( SURFACE ) ;
5053     mesh     = fold->get_subfold( MESH ) ;
5054 
5055     ImportArray<glm::tmat4x4<double>, double>(inst,   fold->get(INST), INST);
5056     ImportArray<glm::tmat4x4<double>, double>(iinst,  fold->get(IINST), IINST);
5057     ImportArray<glm::tmat4x4<float>, float>(inst_f4,  fold->get(INST_F4), INST_F4);
5058     ImportArray<glm::tmat4x4<float>, float>(iinst_f4, fold->get(IINST_F4), IINST_F4);
5059 
5060     ImportArray<int, int>( sensor_id, fold->get(SENSOR_ID), SENSOR_ID );
5061     sensor_count = sensor_id.size();
5062     ImportNames( sensor_name, fold->get(SENSOR_NAME), SENSOR_NAME );
5063 
5064     ImportArray<int4,int>( inst_info, fold->get(INST_INFO), INST_INFO );
5065     ImportArray<int, int>( inst_nidx, fold->get(INST_NIDX), INST_NIDX );
5066 
5067 
5068 #ifdef STREE_CAREFUL
5069     if(ssys::getenvbool(stree__populate_prim_nidx))
5070     {
5071         ImportArray<int, int>( prim_nidx, fold->get(PRIM_NIDX), PRIM_NIDX );
5072     }
5073     else if(ssys::getenvbool(stree__populate_nidx_prim))
5074     {
5075         ImportArray<int, int>( prim_nidx, fold->get(PRIM_NIDX), PRIM_NIDX );
5076         ImportArray<int, int>( nidx_prim, fold->get(NIDX_PRIM), NIDX_PRIM );
5077         ImportNames( prname, fold->get(PRNAME), PRNAME );
5078     }
5079 #else
5080     ImportArray<int, int>( prim_nidx, fold->get(PRIM_NIDX), PRIM_NIDX );
5081     ImportArray<int, int>( nidx_prim, fold->get(NIDX_PRIM), NIDX_PRIM );
5082     ImportNames( prname, fold->get(PRNAME), PRNAME );
5083 #endif
5084 }
5085 
5086 
5087 inline int stree::Compare( const std::vector<int>& a, const std::vector<int>& b ) // static
5088 {
5089     if( a.size() != b.size() ) return -1 ;
5090     int mismatch = 0 ;
5091     for(unsigned i=0 ; i < a.size() ; i++) if(a[i] != b[i]) mismatch += 1 ;
5092     return mismatch ;
5093 }
5094 
5095 inline std::string stree::Desc(const std::vector<int>& a, unsigned edgeitems ) // static
5096 {
5097     std::stringstream ss ;
5098     ss << "stree::Desc " << a.size() << " : " ;
5099     for(unsigned i=0 ; i < a.size() ; i++)
5100     {
5101         if(i < edgeitems || i > (a.size() - edgeitems) ) ss << a[i] << " " ;
5102         else if( i == edgeitems ) ss << "... " ;
5103     }
5104     std::string s = ss.str();
5105     return s ;
5106 }
5107 
5108 
5109 /**
5110 stree::FindForceTriangulateLVID
5111 --------------------------------
5112 
5113 Canonical usage from stree::findForceTriangulateLVID
5114 
5115 1. if _force_triangulate_solid is nullptr return doing nothing
5116 2. split _force_triangulate_solid delimited string into *force* vector of potential solid names
5117 
5118    * when have lots of solid names to force triangulate its easier to manage these in a file, see stree::findForceTriangulateLVID
5119    * when the string contains '\n' sstr::SplitTrimSuppress overrides delim to '\n'
5120 
5121 3. for each solid name found in the *_soname* vector collect corresponding indices into *lvid* vector
5122 
5123 **/
5124 
5125 
5126 inline void stree::FindForceTriangulateLVID(std::vector<int>& lvid, const std::vector<std::string>& _soname, const char* _force_triangulate_solid, char delim  )  // static
5127 {
5128     if(_force_triangulate_solid == nullptr) return ;
5129 
5130     std::vector<std::string> force ;
5131     sstr::SplitTrimSuppress( _force_triangulate_solid, delim, force );
5132     unsigned num_force = force.size();
5133 
5134     for(unsigned i=0 ; i < num_force ; i++)
5135     {
5136         const char* f = force[i].c_str() ;
5137         int lv = slist::FindIndex(_soname, f );
5138         if(lv == -1) std::cerr << "stree::FindForceTriangulateLVID name not found [" << ( f ? f : "-" ) << "]\n" ;
5139         if(lv > -1) lvid.push_back(lv);
5140     }
5141 }
5142 
5143 
5144 
5145 inline std::string stree::descForceTriangulateLVID() const
5146 {
5147     std::stringstream ss ;
5148     ss << "stree::descForceTriangulateLVID\n"
5149        << " force_triangulate_solid [" << ( force_triangulate_solid ? force_triangulate_solid : "-" ) << "]\n"
5150        << " force_triangulate_lvid.size  " << force_triangulate_lvid.size() << "\n"
5151        << " soname.size  " << soname.size() << "\n"
5152        ;
5153     std::string str = ss.str();
5154     return str ;
5155 }
5156 
5157 
5158 /**
5159 stree::is_force_triangulate
5160 ----------------------------
5161 **/
5162 
5163 
5164 inline bool stree::is_force_triangulate( int lvid ) const
5165 {
5166     return slist::Contains( force_triangulate_lvid, lvid );
5167 }
5168 
5169 /**
5170 stree::is_auto_triangulate
5171 ----------------------------
5172 
5173 WIP: to reproduce the manual list of lvid also need to judge
5174 based on the CSG complexity, depth etc.. even with supported
5175 nodes
5176 
5177 **/
5178 
5179 
5180 inline bool stree::is_auto_triangulate( int lvid ) const
5181 {
5182     const sn* root = sn::GetLVRoot(lvid);
5183     assert( root );
5184 
5185     const char* names = "torus,notsupported,cutcylinder,phicut,halfspace" ;
5186     const char* NAMES = ssys::getenvvar(stree__is_auto_triangulate_NAMES, names);
5187     std::vector<int> tcq ;
5188     CSG::TypeCodeVec(tcq, NAMES, ',');
5189     int minsubdepth = 0;
5190     int count = root->typecodes_count(tcq, minsubdepth );
5191     return count > 0 ;
5192 }
5193 
5194 
5195 /**
5196 stree::is_triangulate
5197 ------------------------
5198 
5199 How the result of stree::is_triangulate is used is spread over
5200 the geometry handling of sysrap, CSG and CSGOptiX packages.
5201 Relevant methods/fields include:
5202 
5203 stree::tri
5204     vector of snode : subset of nds which are triangulated (currently only global, non-instanced nodes)
5205     (tri vector is populated by stree::factorize/stree::collectGlobalNodes, based on stree::is_triangulate)
5206 
5207 stree::rem
5208     vector of snode : subset of nds which are remainder nodes : left following factorization
5209     (rem vector is populated by stree::factorize/stree::collectGlobalNodes, based on stree::is_triangulate)
5210 
5211 stree::get_ridx_type
5212     returns 'R' 'F' or 'T' (depending on get_num_remainder (always 1), get_num_factor, get_num_triangulated (0 or 1))
5213 
5214     'R' analytic global solid
5215     'T' triangulated global solid
5216     'F' analytic factor solid
5217     ## NB no 'Q' for triangulated factor solid : NOT YET IMPLEMENTED
5218 
5219 
5220 void CSGSolid::setIntent(char _intent)
5221    invoked by the below importers, where _intent is one of::
5222 
5223 CSGImport::importSolid
5224 CSGImport::importSolidGlobal
5225 CSGImport::importSolidFactor
5226 
5227 CSGFoundry::isSolidTrimesh
5228    returns true when CSGFoundry::getSolidIntent yields 'T' ... this is
5229    used extensively by the below CSGOptiX methods to setup the GPU geometry
5230 
5231 CSGOptiX/SBT::createGAS
5232    depending on CSGFoundry::isSolidTrimesh switches between the mesh and analytic buildInput
5233    passed to SOPTIX_Accel::Create
5234 
5235 CSGOptiX/SBT::createHitgroup
5236 
5237 
5238 In order to dump the triangulation status, need to dump the compound CSGSolid
5239 
5240 **/
5241 
5242 
5243 inline bool stree::is_triangulate( int lvid ) const
5244 {
5245     return is_force_triangulate(lvid) || is_auto_triangulate(lvid);
5246 }
5247 
5248 
5249 
5250 
5251 
5252 /**
5253 stree::classifySubtrees
5254 ------------------------
5255 
5256 This is invoked by stree::factorize
5257 
5258 Traverse all nodes, computing and collecting subtree digests and adding them to (sfreq*)subs_freq
5259 to find the top repeaters.
5260 
5261 **/
5262 
5263 inline void stree::classifySubtrees()
5264 {
5265     if(level>0) std::cout << "[ stree::classifySubtrees " << std::endl ;
5266     for(int nidx=0 ; nidx < int(nds.size()) ; nidx++)
5267     {
5268         std::string sub = subtree_digest(nidx) ;
5269         subs.push_back(sub) ;
5270         subs_freq->add(sub.c_str());
5271     }
5272     if(level>0) std::cout << "] stree::classifySubtrees " << std::endl ;
5273 }
5274 
5275 
5276 /**
5277 stree::is_contained_repeat
5278 ----------------------------
5279 
5280 The result of this is used to disqualify repeated subtrees
5281 that are contained within other repeated subtrees in order to find
5282 the largest subtrees of repeated nodes. In that case the parent node
5283 would be a candidate to be the outer factor node. But of course the
5284 parent could itself be a contained repeat.
5285 
5286 A contained repeat *sub* digest is defined as one where the subtree
5287 digest of the parent of the first node with *sub* digest passes "pfreq >= FREQ_CUT"
5288 
5289 Note that this definition assumes the first node of a *sub* is representative.
5290 That might not always be the case.
5291 
5292 Initially tried changing criteria for a contained repeat to be
5293 that the parent of the first node with the supplied subtree digest
5294 has a subtree digest frequency equal to that of the original first node,
5295 but that fails to match GGeo due to a repeats inside repeats recursively
5296 which do not have the same counts.
5297 
5298 Dump ancestry of first sBar::
5299 
5300     SO sBar lvs [8 9]
5301     lv:8 bb=st.find_lvid_nodes(lv)  bb:[   18    20    22    24    26 ... 65713 65715 65717 65719 65721] b:18
5302     b:18 anc=st.get_ancestors(b) anc:[0, 1, 5, 6, 12, 13, 14, 15, 16, 17]
5303     st.desc_nodes(anc, brief=True))
5304     +               snode ix:      0 dh: 0 nc:    2 lv:138. sf 138 :       1 : 8ab45. sWorld0x577e4d0
5305      +              snode ix:      1 dh: 1 nc:    2 lv: 17. sf 118 :       1 : 6eaf9. sTopRock0x578c0a0
5306       +             snode ix:      5 dh: 2 nc:    1 lv: 16. sf 120 :       1 : 6db9a. sExpRockBox0x578ce00
5307        +            snode ix:      6 dh: 3 nc:    3 lv: 15. sf 121 :       1 : 3736e. sExpHall0x578d4f0
5308         +           snode ix:     12 dh: 4 nc:   63 lv: 14. sf 127 :       1 : f6323. sAirTT0x71a76a0
5309          +          snode ix:     13 dh: 5 nc:    2 lv: 13. sf  36 :      63 : 66a4f. sWall0x71a8b30
5310           +         snode ix:     14 dh: 6 nc:    4 lv: 12. sf  35 :     126 : 09a56. sPlane0x71a8bb0
5311            +        snode ix:     15 dh: 7 nc:    1 lv: 11. sf  32 :     504 : 7d9a6. sPanel0x71a8d90
5312             +       snode ix:     16 dh: 8 nc:   64 lv: 10. sf  31 :     504 : a1a35. sPanelTape0x71a9090
5313              +      snode ix:     17 dh: 9 nc:    1 lv:  9. sf   1 :   32256 : 72cfb. sBar0x71a9200
5314     st.desc_nodes([b], brief=True))
5315               +     snode ix:     18 dh:10 nc:    0 lv:  8. sf   0 :   32256 : 34f45. sBar0x71a9370
5316 
5317 CAUTION : that the sf digest counts are for entire geometry, not the subtree of one node.
5318 
5319 The deepest sBar is part of 6 potential repeated instances::
5320 
5321     sWall,sPlane,sPanel,sPanelTape,sBar0x71a9200,sBar0x71a9370.
5322 
5323 GGeo picked sPanel (due to repeat candidate cut of 500).
5324 
5325 * For now I need to duplicate that here.
5326 
5327 **/
5328 
5329 inline bool stree::is_contained_repeat(const char* sub) const
5330 {
5331     int nidx = get_first(sub);            // first node with this subtree digest
5332     int parent = get_parent(nidx);        // parent of first node
5333     const char* psub = get_sub(parent) ;
5334     int p_freq = subs_freq->get_freq(psub) ;
5335     return p_freq >= FREQ_CUT ;
5336 }
5337 
5338 /**
5339 stree::disqualifyContainedRepeats
5340 ----------------------------------
5341 
5342 Disqualification is not applied during the loop
5343 as that would prevent some disqualifications
5344 from working as the parents will sometimes
5345 no longer be present.
5346 
5347 **/
5348 
5349 inline void stree::disqualifyContainedRepeats()
5350 {
5351     if(level>0) std::cout << "[ stree::disqualifyContainedRepeats " << std::endl ;
5352 
5353     unsigned num = subs_freq->get_num();
5354     std::vector<std::string> disqualify ;
5355 
5356     for(unsigned i=0 ; i < num ; i++)
5357     {
5358         const char* sub = subs_freq->get_key(i);
5359         int freq = subs_freq->get_freq(i);   // -ve freq means disqualified
5360         if(freq < FREQ_CUT) continue ;
5361         if(is_contained_repeat(sub)) disqualify.push_back(sub) ;
5362     }
5363 
5364     subs_freq->set_disqualify( disqualify );
5365 
5366     if(level > 0) std::cout
5367         << "] stree::disqualifyContainedRepeats "
5368         << " disqualify.size " << disqualify.size()
5369         << std::endl
5370         ;
5371 }
5372 
5373 
5374 /**
5375 stree_subs_freq_ordering
5376 ------------------------
5377 
5378 Used from stree::sortSubtrees ordering (sub, freq) based on:
5379 
5380 1. nidx obtained from stree::get_first(sub:subtree_digest)
5381 2. freq:frequency count
5382 
5383 It is necessary to use two level ordering
5384 to ensure that same order is achieved
5385 on different machines.
5386 
5387 **/
5388 
5389 struct stree_subs_freq_ordering
5390 {
5391     const stree* st ;
5392     stree_subs_freq_ordering( const stree* st_ ) : st(st_) {} ;
5393 
5394     bool operator()(
5395         const std::pair<std::string,int>& a,
5396         const std::pair<std::string,int>& b ) const
5397     {
5398         const char* a_sub = a.first.c_str();
5399         const char* b_sub = b.first.c_str();
5400         int a_nidx = st->get_first(a_sub);
5401         int b_nidx = st->get_first(b_sub);
5402         int a_freq = a.second ;
5403         int b_freq = b.second ;
5404 
5405         return a_freq == b_freq ?  b_nidx > a_nidx : a_freq > b_freq ;
5406     }
5407 };
5408 
5409 
5410 /**
5411 stree::sortSubtrees
5412 ---------------------
5413 
5414 Order the subs_freq (sub,freq) pairs within the vector
5415 The subtrees are sorted in order for the order of factors
5416 to be reproducible between different machines.
5417 
5418 **/
5419 
5420 inline void stree::sortSubtrees()  // hmm sortSubtreeDigestFreq would be more accurate
5421 {
5422     if(level > 0) std::cout << "[ stree::sortSubtrees " << std::endl ;
5423 
5424     stree_subs_freq_ordering ordering(this) ;
5425     sfreq::VSU& vsu = subs_freq->vsu ;
5426     std::sort( vsu.begin(), vsu.end(), ordering );
5427 
5428     if(level > 0) std::cout << "] stree::sortSubtrees " << std::endl ;
5429 }
5430 
5431 /**
5432 stree::enumerateFactors
5433 ------------------------
5434 
5435 For remaining subs that pass the "freq >= FREQ_CUT"
5436 create sfactor and collect into *factor* vector
5437 
5438 Q: What about ridx:0 is there a factor ?
5439 A: from the freq cut in stree::enumerateFactors and
5440    the offset by one in stree::labelFactorSubtrees it looks
5441    like the factor are really just for the instanced
5442 
5443 **/
5444 
5445 inline void stree::enumerateFactors()
5446 {
5447     if(level > 0) std::cout << "[ stree::enumerateFactors " << std::endl ;
5448     const sfreq* sf = subs_freq ;
5449     unsigned num = sf->get_num();
5450     for(unsigned i=0 ; i < num ; i++)
5451     {
5452         const char* sub = sf->get_key(i);
5453         int freq = sf->get_freq(i);
5454         if(freq < FREQ_CUT) continue ;
5455 
5456         sfactor fac ;
5457         fac.index = i ;
5458         fac.freq = freq ;
5459         fac.sensors = 0 ; // set later, from U4Tree::identifySensitiveInstances
5460         fac.subtree = 0 ; // set later, by stree::labelFactorSubtrees
5461         fac.set_sub(sub) ;
5462 
5463         factor.push_back( fac );
5464     }
5465     if(level > 0) std::cout << "] stree::enumerateFactors " << std::endl ;
5466 }
5467 
5468 
5469 /**
5470 stree::labelFactorSubtrees
5471 ------------------------------
5472 
5473 label all nodes of subtrees of all repeats with repeat_index,
5474 leaving remainder nodes at default of zero repeat_index
5475 
5476 **/
5477 
5478 inline void stree::labelFactorSubtrees()
5479 {
5480     int num_factor = factor.size();
5481     if(level>0) std::cout << "[ stree::labelFactorSubtrees num_factor " << num_factor << std::endl ;
5482 
5483     for(int f=0 ; f < num_factor ; f++)
5484     {
5485         int repeat_index = f + 1 ;   // leave repeat_index zero for the global remainder
5486         sfactor& fac = factor.at(repeat_index-1) ;  // .at is appropriate for small loops
5487         std::string sub = fac.get_sub() ;
5488         assert( fac.index == repeat_index - 1 );
5489 
5490         std::vector<int> outer_node ;
5491         get_nodes( outer_node, sub.c_str() );
5492         assert( int(outer_node.size()) ==  fac.freq );
5493 
5494         int fac_olvid = -1 ;
5495         int fac_subtree = -1 ;
5496 
5497         for(unsigned i=0 ; i < outer_node.size() ; i++)
5498         {
5499             int outer = outer_node[i] ;
5500 
5501             const snode& ond = nds[outer] ;
5502 
5503             if( fac_olvid == -1 )
5504             {
5505                 fac_olvid = ond.lvid ;
5506             }
5507             else
5508             {
5509                 // all the instances should have the same outer lvid
5510                 assert( ond.lvid == fac_olvid );
5511             }
5512 
5513             std::vector<int> subtree ;
5514             get_progeny(subtree, outer);
5515             subtree.push_back(outer);
5516 
5517             if(fac_subtree == -1)
5518             {
5519                 fac_subtree = subtree.size() ;
5520             }
5521             else
5522             {
5523                 // all the instances must have same number of nodes
5524                 assert( int(subtree.size()) == fac_subtree );
5525             }
5526 
5527 
5528             for(unsigned j=0 ; j < subtree.size() ; j++)
5529             {
5530                 int nidx = subtree[j] ;
5531                 snode& nd = nds[nidx] ;
5532                 assert( nd.index == nidx );
5533                 nd.repeat_index = repeat_index ;
5534                 nd.repeat_ordinal = i ;  // index over occurence of the subtree digest
5535             }
5536         }
5537         fac.subtree = fac_subtree ;  // notes on the subtree
5538         fac.olvid = fac_olvid ;      // outer node lvid
5539 
5540         if(level>0) std::cout
5541             << "stree::labelFactorSubtrees"
5542             << fac.desc()
5543             << " outer_node.size " << outer_node.size()
5544             << std::endl
5545             ;
5546     }
5547     if(level>0) std::cout << "] stree::labelFactorSubtrees " << std::endl ;
5548 }
5549 
5550 
5551 
5552 /**
5553 stree::findForceTriangulateLVID
5554 --------------------------------
5555 
5556 Canonically invoked during stree creation by U4Tree::Create/.../stree::factorize.
5557 Populates *force_triangulate_lvid* vector of indices, which is used by stree::is_force_triangulate.
5558 
5559 HMM: the envvar is an input to creation... how to persist that info ?
5560 To make is_force_triangulated work for a loaded stree ?
5561 Just need to persist the vector ?
5562 
5563 Uses the optional comma delimited stree__force_triangulate_solid envvar list of unique solid names
5564 together with the member variable vector of all solid names *soname* to form the indices.
5565 
5566 When there are many solids that need to be triangulated it is more
5567 convenient to adopt a config approach like the below example using
5568 a replacement path to load the solid names from a file::
5569 
5570     cd ~/.opticks/GEOM
5571 
5572     cp J_2024aug27/CSGFoundry/meshname.txt J_2024aug27_meshname_stree__force_triangulate_solid.txt
5573 
5574         ## copy meshname.txt with all the solid names to a suitable location
5575         ## "J_2024aug27" is an example GEOM identifier
5576 
5577     vi J_2024aug27_meshname_stree__force_triangulate_solid.txt
5578 
5579         ## edit the file leaving only global solids to be force triangulated
5580         ## note that as sstr::SplitTrimSuppress is used lines starting with '#' are skipped
5581 
5582     export GEOM=J_2024aug27
5583     export stree__force_triangulate_solid='filepath:$HOME/.opticks/GEOM/${GEOM}_meshname_stree__force_triangulate_solid.txt'
5584 
5585         ## configure envvar within runner script to load the filepath instead of directly using a delimited string
5586         ## NB the names and paths are just examples
5587 
5588 **/
5589 
5590 
5591 inline void stree::findForceTriangulateLVID()
5592 {
5593     FindForceTriangulateLVID(force_triangulate_lvid, soname, force_triangulate_solid, ',' );
5594 
5595     if(ssys::getenvbool(_findForceTriangulateLVID_DUMP)) std::cout
5596         << "stree::findForceTriangulateLVID\n"
5597         << " [" << _findForceTriangulateLVID_DUMP << "] "
5598         << descForceTriangulateLVID()
5599         ;
5600 }
5601 
5602 /**
5603 stree::collectGlobalNodes
5604 ---------------------------
5605 
5606 This is invoked from the tail of stree::factorize
5607 where subsets of the *nds* snode collected from Geant4 by *U4Tree::initNodes_r*
5608 are copied into the *rem* and *tri* vectors.
5609 
5610 Done by iterating over the *nds* vector of all nodes copying global non-instanced
5611 snode with repeat_index:0 into the *rem* and *tri* vectors
5612 depending on the "stree__force_triangulate_solid" envvar list of unique solid names.
5613 
5614 The default is to collect globals into the *rem* vector.
5615 
5616 NB simplifying assumption that all configured tri nodes are global (not instanced)
5617 
5618 NB as this action is pre-cache it means that currently must configure triangulation envvar
5619 when doing the from Geant4 to stree.h conversion with U4Tree.h, hence the triangulation
5620 setting configured is baked into the cache geometry
5621 
5622 could triangulation action be done post-cache ?
5623 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5624 
5625 * probably yes, but not easily : would need to defer the tri rem formation
5626 
5627 should triangulation action be done post-cache ?
5628 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5629 
5630 * probably no, flexibility would be convenient during testing
5631   but for actual use the definiteness of getting same geometry from cache has value
5632 
5633 
5634 
5635 Q: HOW TO EXTEND TO TRIANGULATED FACTOR NODES ?
5636 
5637 A: LOOKS LIKE NO FUNDAMENTAL THING STOPPING TRI FACTOR NODES IN CSGOptiX,
5638    PERHAPS JUST BOOKKEEPING CHANGE IN stree.h ?
5639 
5640    * ONE THING TO NOTE IS THAT THE ENTIRE factor subtree that becomes
5641      the compound CSGSolid will need to be all triangulated or all analytic
5642      ... cannot mix ana and tri in the same CSGSolid
5643 
5644    * HOW TO PROCEED : NEED EXAMPLE TO WORK WITH
5645 
5646 
5647 **/
5648 
5649 
5650 inline void stree::collectGlobalNodes()
5651 {
5652     assert( rem.size() == 0u );
5653     assert( tri.size() == 0u );
5654 
5655     for(int nidx=0 ; nidx < int(nds.size()) ; nidx++)
5656     {
5657         const snode& nd = nds[nidx] ;
5658         assert( nd.index == nidx );
5659         bool do_triangulate = is_triangulate(nd.lvid) ;
5660         bool do_triangulate_non_global = nd.repeat_index > 0 && do_triangulate ;
5661 
5662         if( do_triangulate_non_global ) std::cerr
5663             << "stree::collectGlobalNodes"
5664             << " nidx " << nidx
5665             << " nd.lvid " << nd.lvid
5666             << " soname[nd.lvid] " << soname[nd.lvid]
5667             << " do_triangulate " << ( do_triangulate ? "YES" : "NO " )
5668             << " do_triangulate_non_global " << ( do_triangulate_non_global ? "YES" : "NO " )
5669             << "\n"
5670             ;
5671 
5672 
5673         if( nd.repeat_index == 0 )
5674         {
5675             std::vector<snode>& dst = do_triangulate ? tri : rem  ;
5676             dst.push_back(nd) ;
5677         }
5678         else
5679         {
5680 
5681             assert( do_triangulate == false && "triangulate solid is currently only supported for remainder nodes" );
5682             // HMM: FOR TRI FACTOR NODES NEED TO OPERATE WITH THE SUBTREES : AS ALL OF THE FUTURE CSGSolid
5683             // HAS TO BE TRI TOGETHER : SO CAN DO NOTHING HERE
5684         }
5685     }
5686     if(level>0) std::cout
5687        << "stree::collectGlobalNodes "
5688        << descNodes()
5689        << descForceTriangulateLVID()
5690        << std::endl
5691        ;
5692 }
5693 
5694 
5695 inline std::string stree::descNodes() const
5696 {
5697     std::stringstream ss ;
5698     ss
5699        << "stree::descNodes "
5700        << " nds.size " << nds.size()
5701        << " rem.size " << rem.size()
5702        << " tri.size " << tri.size()
5703        << " (rem.size + tri.size) " << (rem.size() + tri.size() )
5704        << " (nds.size - rem.size - tri.size; inst nodes) " << (nds.size() - rem.size() - tri.size() )
5705        ;
5706     std::string str = ss.str();
5707     return str ;
5708 }
5709 
5710 
5711 
5712 /**
5713 stree::factorize
5714 -------------------
5715 
5716 Canonically invoked from U4Tree::Create
5717 
5718 classifySubtrees
5719    compute and store stree::subtree_digest for subtrees of all nodes using (sfreq*)subs_freq
5720 
5721 disqualifyContainedRepeats
5722    flip freq sign in (sfreq*)subs_freq to disqualify all contained repeats, as want factors
5723    to have the largest subtrees
5724 
5725 sortSubtrees
5726    order the subs_freq (sub,freq) pairs within the vector, for reproducible factors between different machines
5727 
5728 enumerateFactors
5729    create sfactor instances and collect into factor vector
5730 
5731 labelFactorSubtrees
5732    label all nodes of subtrees of all repeats with repeat_index,
5733    leaving remainder nodes at default of zero repeat_index
5734 
5735 findForceTriangulateLVID
5736    populates force_triangulate_lvid vector of lvid int based on force_triangulate
5737    envvar and solid names. The vector is used by stree::is_force_triangulate
5738 
5739 collectGlobalNodes
5740    collect global non-instanced nodes into *rem* vector and depending on envvars collect
5741    nodes to be force triangulated into *tri* vector
5742 
5743 
5744 **/
5745 
5746 inline void stree::factorize()
5747 {
5748     if(level>0) std::cout << "[ stree::factorize (" << level << ")" << std::endl ;
5749 
5750     classifySubtrees();
5751     disqualifyContainedRepeats();
5752     sortSubtrees();
5753     enumerateFactors();
5754     labelFactorSubtrees();
5755 
5756     findForceTriangulateLVID();
5757     collectGlobalNodes();
5758 
5759     if(level>0) std::cout << desc_factor() << std::endl ;
5760     if(level>0) std::cout << desc_lvid() << std::endl ;
5761 
5762 #ifdef STREE_CAREFUL
5763     if(ssys::getenvbool(stree__populate_prim_nidx))
5764     {
5765         populate_prim_nidx();
5766     }
5767     else if(ssys::getenvbool(stree__populate_nidx_prim))
5768     {
5769         populate_prim_nidx();
5770         populate_nidx_prim();
5771         check_nidx_prim();
5772     }
5773 #else
5774     populate_prim_nidx();
5775     populate_nidx_prim();
5776     check_nidx_prim();
5777 #endif
5778 
5779     if(level>0) std::cout << "] stree::factorize (" << level << ")" << std::endl ;
5780 }
5781 
5782 
5783 inline int stree::get_num_factor() const
5784 {
5785     return factor.size();
5786 }
5787 
5788 
5789 inline sfactor& stree::get_factor_(unsigned idx)
5790 {
5791     assert( idx < factor.size() );
5792     return factor[idx] ;
5793 }
5794 inline const sfactor& stree::get_factor(unsigned idx) const
5795 {
5796     assert( idx < factor.size() );
5797     return factor[idx] ;
5798 }
5799 
5800 inline int stree::get_factor_subtree(unsigned idx) const
5801 {
5802     const sfactor& fac = get_factor(idx);
5803     return fac.subtree ;
5804 }
5805 inline int stree::get_factor_olvid(unsigned idx) const
5806 {
5807     const sfactor& fac = get_factor(idx);
5808     return fac.olvid ;
5809 }
5810 
5811 
5812 
5813 inline int stree::get_remainder_subtree() const
5814 {
5815     return rem.size() ;
5816 }
5817 inline int stree::get_triangulate_subtree() const
5818 {
5819     return tri.size() ;
5820 }
5821 
5822 inline int stree::get_remainder_olvid() const
5823 {
5824     if(rem.size() == 0 ) return -1 ;
5825     const snode& out = rem[0] ;
5826     return out.lvid ;
5827 }
5828 inline int stree::get_triangulate_olvid() const
5829 {
5830     if(tri.size() == 0 ) return -1 ;
5831     const snode& out = tri[0] ;
5832     return out.lvid ;
5833 }
5834 
5835 
5836 inline int stree::get_ridx_subtree(unsigned ridx) const
5837 {
5838     char ridx_type = get_ridx_type(ridx) ;
5839     int num_rem = get_num_remainder();
5840     assert( num_rem == 1 );
5841 
5842     int _subtree = -1 ;
5843     switch(ridx_type)
5844     {
5845         case 'R': _subtree = get_remainder_subtree()             ; break ;
5846         case 'F': _subtree = get_factor_subtree(ridx - num_rem)  ; break ;
5847         case 'T': _subtree = get_triangulate_subtree()           ; break ;
5848     }
5849     return _subtree ;
5850 }
5851 inline int stree::get_ridx_olvid(unsigned ridx) const
5852 {
5853     char ridx_type = get_ridx_type(ridx) ;
5854     int num_rem = get_num_remainder();
5855     assert( num_rem == 1 );
5856 
5857     int _olvid = -1 ;
5858     switch(ridx_type)
5859     {
5860         case 'R': _olvid = get_remainder_olvid()             ; break ;
5861         case 'F': _olvid = get_factor_olvid(ridx - num_rem)  ; break ;
5862         case 'T': _olvid = get_triangulate_olvid()           ; break ;
5863     }
5864     return _olvid ;
5865 }
5866 
5867 inline int stree::get_num_ridx() const
5868 {
5869     return get_num_remainder() + get_num_factor() + get_num_triangulated() ;
5870 }
5871 
5872 /**
5873 stree::get_num_remainder
5874 -------------------------
5875 
5876 Currently always returns 1, as while OptiX geometry might be made to
5877 work with purely instanced compound solids such geometries
5878 are impossible from Geant4 conversions as the world volume would
5879 never be instanced.
5880 
5881 Also lots of other places assume always have this
5882 
5883 **/
5884 
5885 inline int stree::get_num_remainder() const   // to be more precise analytic_global_remainder
5886 {
5887     //return rem.size() > 0 ? 1 : 0 ;
5888     return 1 ;
5889 }
5890 inline int stree::get_num_triangulated() const  // to be more precise global_triangulated
5891 {
5892     return tri.size() > 0 ? 1 : 0 ;
5893 }
5894 
5895 inline int stree::get_num_ridx_(char ridx_type) const
5896 {
5897     int num = -1 ;
5898     switch(ridx_type)
5899     {
5900         case 'R': num = get_num_remainder()     ; break ;
5901         case 'F': num = get_num_factor()        ; break ;
5902         case 'T': num = get_num_triangulated()  ; break ;
5903     }
5904     return num ;
5905 }
5906 
5907 
5908 
5909 
5910 
5911 /**
5912 stree::get_ridx_type
5913 ---------------------
5914 
5915 The compound solids are assumed to be ordered in the below manner with
5916 the R solid(s) followed by the instanced F solids and then the T solid::
5917 
5918     RFFFFT
5919 
5920 
5921 Expectation for compound solids:
5922 
5923 +-----+-------------------------------+----------------------------------------------------------------------------+
5924 |     | solid type                    |  note                                                                      |
5925 +=====+===============================+============================================================================+
5926 |  R  |  global non-instanced         |   1 solid formed from the many *rem* nodes                                 |
5927 +-----+-------------------------------+----------------------------------------------------------------------------+
5928 |  F  |  factor/instanced             |  0...~10 solids with a few nodes each                                      |
5929 +-----+-------------------------------+----------------------------------------------------------------------------+
5930 |  T  |  triangulated non-instanced   |  0 or 1 solid formed from any *tri* nodes depending on stree envvar config |
5931 +-----+-------------------------------+----------------------------------------------------------------------------+
5932 |  Q  |  triangulated instanced       | NOT YET IMPLEMENTED  : MAY NEED FOR COMPLEX STRUTS                         |
5933 +-----+-------------------------------+----------------------------------------------------------------------------+
5934 
5935 
5936 Indices of ranges of the 3 types of compound solids:
5937 
5938 +---+--------------------------------------------+--------------------------------------------------------+
5939 |   |   lowest ridx                              |  highest ridx                                          |
5940 +===+============================================+========================================================+
5941 | R |   0                                        | num_remainder - 1                                      |
5942 +---+--------------------------------------------+--------------------------------------------------------+
5943 | F | num_remainder + 0                          | num_remainder + num_factor -1                          |
5944 +---+--------------------------------------------+--------------------------------------------------------+
5945 | T | num_remainder + num_factor + 0             | num_remainder + num_factor + num_triangulate - 1       |
5946 +---+--------------------------------------------+--------------------------------------------------------+
5947 
5948 
5949 **/
5950 
5951 
5952 inline char stree::get_ridx_type(int ridx) const
5953 {
5954     [[maybe_unused]] int num_ridx = get_num_ridx();
5955     int num_rem = get_num_remainder();
5956     int num_fac = get_num_factor();
5957     int num_tri = get_num_triangulated();
5958 
5959     assert( num_ridx == num_rem + num_fac + num_tri );
5960     assert( ridx < num_ridx );
5961 
5962     int R[2] = { 0,                     num_rem - 1 } ;
5963     int F[2] = { num_rem + 0,           num_rem + num_fac - 1 } ;
5964     int T[2] = { num_rem + num_fac + 0, num_rem + num_fac + num_tri - 1 };
5965 
5966     char type = '?' ;
5967     if(      ridx >= R[0] && ridx <= R[1] ) type = 'R' ;
5968     else if( ridx >= F[0] && ridx <= F[1] ) type = 'F' ;
5969     else if( ridx >= T[0] && ridx <= T[1] ) type = 'T' ;
5970     return type ;
5971 }
5972 
5973 
5974 inline void stree::get_global_nodes(std::vector<snode>& gn) const
5975 {
5976     size_t num_nd = nds.size();
5977     for(size_t i=0 ; i < num_nd ; i++)
5978     {
5979         const snode& node = nds[i];
5980         if(node.repeat_index == 0) gn.push_back(node);
5981     }
5982 }
5983 
5984 
5985 
5986 
5987 /**
5988 stree::get_factor_nodes : collect outer volume node indices of *idx* factor (0-based)
5989 --------------------------------------------------------------------------------------
5990 
5991 Q: Is factor idx 0 the global remainder OR is it the first instance factor ?
5992 A: First instance (judging by get_num_ridx, get_ridx_olvid) the remainder
5993    is treated separately from the factors.
5994 
5995 Used by U4Tree::identifySensitiveInstances
5996 
5997 1. lookup the subtree digest for factor idx
5998 2. get nodes that match that substree digest
5999 
6000 As the factor digests are from the outer volume nodes
6001 this provides node indices of the outer volumes of
6002 the idx factor.
6003 
6004 **/
6005 
6006 inline void stree::get_factor_nodes(std::vector<int>& nodes, unsigned idx) const
6007 {
6008     assert( idx < factor.size() );
6009     const sfactor& fac = factor[idx];
6010     std::string sub = fac.get_sub();
6011 
6012     get_nodes(nodes, sub.c_str() );
6013 
6014     bool consistent = int(nodes.size()) == fac.freq ;
6015     if(!consistent) std::cerr
6016         << "stree::get_factor_nodes INCONSISTENCY"
6017         << " nodes.size " << nodes.size()
6018         << " fac.freq " << fac.freq
6019         << std::endl
6020         ;
6021     assert(consistent );
6022 }
6023 
6024 inline std::string stree::desc_factor_nodes(unsigned idx) const
6025 {
6026     std::vector<int> nodes ;
6027     get_factor_nodes(nodes, idx);
6028 
6029     int num_nodes = nodes.size();
6030     std::stringstream ss ;
6031     ss << "stree::desc_factor_nodes idx " << idx << " num_nodes " << num_nodes << std::endl  ;
6032     std::string str = ss.str();
6033     return str ;
6034 }
6035 
6036 
6037 
6038 inline std::string stree::desc_factor() const
6039 {
6040     std::stringstream ss ;
6041     ss << "stree::desc_factor" << std::endl << sfactor::Desc(factor) ;
6042     std::string str = ss.str();
6043     return str ;
6044 }
6045 
6046 
6047 
6048 
6049 /**
6050 stree::get_repeat_field
6051 -------------------------
6052 
6053 ::
6054 
6055     In [9]: snode.Label(6,11), f.nds[f.nds[:,ri] == 1 ]
6056     Out[9]:
6057     ('           ix      dp      sx      pt      nc      fc      sx      lv      cp      se      sx      ri      bd',
6058      array([[194249,      6,  20675,  67846,      2, 194250, 194254,    122, 300000,     -1,     -1,      1,     26],
6059             [194250,      7,      0, 194249,      2, 194251, 194253,    120,      0,     -1,     -1,      1,     29],
6060             [194251,      8,      0, 194250,      0,     -1, 194252,    118,      0,     -1,     -1,      1,     36],
6061             [194252,      8,      1, 194250,      0,     -1,     -1,    119,      0,     -1,     -1,      1,     37],
6062             [194253,      7,      1, 194249,      0,     -1,     -1,    121,      0,     -1,     -1,      1,     24],
6063 
6064             [194254,      6,  20676,  67846,      2, 194255, 194259,    122, 300001,     -1,     -1,      1,     26],
6065             [194255,      7,      0, 194254,      2, 194256, 194258,    120,      0,     -1,     -1,      1,     29],
6066             [194256,      8,      0, 194255,      0,     -1, 194257,    118,      0,     -1,     -1,      1,     36],
6067             [194257,      8,      1, 194255,      0,     -1,     -1,    119,      0,     -1,     -1,      1,     37],
6068             [194258,      7,      1, 194254,      0,     -1,     -1,    121,      0,     -1,     -1,      1,     24],
6069 
6070             [194259,      6,  20677,  67846,      2, 194260, 194264,    122, 300002,     -1,     -1,      1,     26],
6071             [194260,      7,      0, 194259,      2, 194261, 194263,    120,      0,     -1,     -1,      1,     29],
6072             ...
6073 
6074 
6075 ADDED ro:repeat_ordinal to snode.h for convenient selecting of the repeats::
6076 
6077     In [27]: snode.Label(5,10), f.nds[np.logical_and(f.nds[:,ri] == 2,f.nds[:,ro] == 0)]
6078     Out[27]:
6079     ('          ix     dp     sx     pt     nc     fc     sx     lv     cp     se     sx     ri     ro     bd',
6080      array([[70979,     6,  3065, 67846,     3, 70980, 70986,   117,     2,    -1,    -1,     2,     0,    26],
6081             [70980,     7,     0, 70979,     0,    -1, 70981,   111,     0,    -1,    -1,     2,     0,    27],
6082             [70981,     7,     1, 70979,     0,    -1, 70982,   112,     0,    -1,    -1,     2,     0,    33],
6083             [70982,     7,     2, 70979,     1, 70983,    -1,   116,     0,    -1,    -1,     2,     0,    29],
6084             [70983,     8,     0, 70982,     2, 70984,    -1,   115,     0,    -1,    -1,     2,     0,    30],
6085             [70984,     9,     0, 70983,     0,    -1, 70985,   113,     0,    -1,    -1,     2,     0,    34],
6086             [70985,     9,     1, 70983,     0,    -1,    -1,   114,     0,    -1,    -1,     2,     0,    35]], dtype=int32))
6087 
6088 
6089 
6090 **/
6091 
6092 
6093 inline bool stree::SelectNode( const snode& nd, int q_repeat_index, int q_repeat_ordinal ) // static
6094 {
6095     bool all_ordinal = q_repeat_ordinal == -2 ;
6096     bool select = all_ordinal ?
6097                                 nd.repeat_index == q_repeat_index
6098                               :
6099                                 nd.repeat_index == q_repeat_index && nd.repeat_ordinal == q_repeat_ordinal
6100                               ;
6101 
6102     return select ;
6103 }
6104 
6105 
6106 /**
6107 stree::get_repeat_field
6108 -------------------------
6109 
6110 Iterates over all structural *snode* (aka physical volumes "PV"
6111 collecting field values for selected *snode* where the
6112 selection uses (q_repeat_index, q_repeat_ordinal).)
6113 
6114 Supported fields:
6115 
6116   +---------+--------------+-------------------+
6117   | q_field |  result      |                   |
6118   +=========+==============+===================+
6119   |  'I'    |  nd.index    |  get_repeat_nidx  |
6120   +---------+--------------+-------------------+
6121   |  'L'    |  nd.lvid     |  get_repeat_lvid  |
6122   +---------+--------------+-------------------+
6123 
6124 **/
6125 
6126 inline void stree::get_repeat_field(std::vector<int>& result, char q_field , int q_repeat_index, int q_repeat_ordinal ) const
6127 {
6128     int num_nd = nds.size() ;
6129 
6130     int nidx_mismatch = 0 ;
6131 
6132     for(int nidx=0 ; nidx < num_nd ; nidx++)
6133     {
6134         const snode& nd = nds[nidx] ;
6135 
6136         if(nd.index != nidx)
6137         {
6138             std::cerr
6139                << "stree::get_repeat_field"
6140                << " ERROR : NIDX MISMATCH : "
6141                << " nidx " << nidx
6142                << " nd.index " << nd.index
6143                << " num_nd " << num_nd
6144                << std::endl
6145                ;
6146             nidx_mismatch += 1 ;
6147         }
6148 
6149         if(SelectNode(nd, q_repeat_index, q_repeat_ordinal))
6150         {
6151             int field = -3 ;
6152             switch(q_field)
6153             {
6154                 case 'I': field = nd.index ; break ;
6155                 case 'L': field = nd.lvid  ; break ;
6156             }
6157             result.push_back(field) ;
6158         }
6159     }
6160 
6161     if( nidx_mismatch > 0 )
6162     {
6163         std::cerr
6164             << "stree::get_repeat_field"
6165             << " FATAL : NIDX MISMATCH : "
6166             << " nidx_mismatch " << nidx_mismatch
6167             << " num_nd " << num_nd
6168             << " q_field " << q_field
6169             << " q_repeat_index " << q_repeat_index
6170             << " q_repeat_ordinal " << q_repeat_ordinal
6171             << std::endl
6172             ;
6173     }
6174     assert( nidx_mismatch == 0 );
6175 }
6176 
6177 
6178 inline void stree::get_repeat_lvid(std::vector<int>& lvids, int q_repeat_index, int q_repeat_ordinal ) const
6179 {
6180     get_repeat_field(lvids, 'L', q_repeat_index, q_repeat_ordinal );
6181 }
6182 inline void stree::get_repeat_nidx(std::vector<int>& nidxs, int q_repeat_index, int q_repeat_ordinal ) const
6183 {
6184     get_repeat_field(nidxs, 'I', q_repeat_index, q_repeat_ordinal );
6185 }
6186 inline void stree::get_remainder_nidx(std::vector<int>& nodes ) const
6187 {
6188     int q_repeat_index = 0 ;
6189     int q_repeat_ordinal = -2 ;
6190     get_repeat_nidx(nodes, q_repeat_index, q_repeat_ordinal);
6191 }
6192 
6193 
6194 /**
6195 stree::get_repeat_node
6196 -----------------------
6197 
6198 Collect all snode (structual/volumes) selected by (q_repeat_index, q_repeat_ordinal)
6199 
6200 Observed this to give zero nodes for the first and last ridx, ie it does
6201 not handle the rem and tri nodes.
6202 
6203 ::
6204 
6205    TEST=get_repeat_node ~/o/sysrap/tests/stree_load_test.sh
6206 
6207    TEST=get_repeat_node RIDX=1 RORD=10 ~/o/sysrap/tests/stree_load_test.sh run
6208 
6209    TEST=get_repeat_node RIDX=9 RORD=0 ~/o/sysrap/tests/stree_load_test.sh run
6210 
6211 **/
6212 
6213 inline void stree::get_repeat_node(std::vector<snode>& nodes, int q_repeat_index, int q_repeat_ordinal ) const
6214 {
6215     for(int nidx=0 ; nidx < int(nds.size()) ; nidx++)
6216     {
6217         const snode& nd = nds[nidx] ;
6218         assert( nd.index == nidx );
6219         if(SelectNode(nd, q_repeat_index, q_repeat_ordinal)) nodes.push_back(nd);
6220     }
6221 }
6222 
6223 /**
6224 stree::desc_repeat_node
6225 ------------------------
6226 
6227 Dump structural volumes (snode) of the ordinal-th occurrence of q_repeat_index (aka ridx),
6228 collecting unique lvid from all nodes. Then for each unique lvid dump the csg nodes (sn).
6229 This enables for example the CSG shapes within a particular compound solid to be checked.::
6230 
6231     TEST=get_repeat_node RIDX=9 ~/o/sysrap/tests/stree_load_test.sh
6232 
6233 **/
6234 
6235 inline std::string stree::desc_repeat_node(int q_repeat_index, int q_repeat_ordinal) const
6236 {
6237     std::vector<snode> nodes ;
6238     get_repeat_node(nodes, q_repeat_index, q_repeat_ordinal);
6239     int num_node = nodes.size() ;
6240 
6241     std::stringstream ss ;
6242     ss << "stree::desc_repeat_node"
6243        << " q_repeat_index " << q_repeat_index
6244        << " q_repeat_ordinal " << q_repeat_ordinal
6245        << " num_node " << num_node
6246        << std::endl
6247        ;
6248 
6249 
6250     std::set<int> ulvid ;
6251 
6252     for(int i=0 ; i < num_node ; i++ )
6253     {
6254         const snode& n = nodes[i] ;
6255         const std::string& lvn = get_lvid_soname_( n.lvid ) ;
6256         ss << n.desc() << " " << lvn << "\n" ;
6257         ulvid.insert(n.lvid) ;
6258     }
6259 
6260     typedef std::set<int>::const_iterator IT ;
6261     ss << "ulvid {" ;
6262     for(IT it=ulvid.begin() ; it != ulvid.end() ; it++) ss << *it << "," ;
6263     ss << "}\n" ;
6264 
6265     for(IT it=ulvid.begin() ; it != ulvid.end() ; it++)
6266     {
6267         int lvid = *it ;
6268         ss << desc_solid(lvid) << "\n" ;
6269     }
6270 
6271     std::string str = ss.str();
6272     return str ;
6273 }
6274 
6275 
6276 inline std::string stree::desc_repeat_nodes() const
6277 {
6278     int num_factor = factor.size();
6279     std::stringstream ss ;
6280     ss << "stree::desc_repeat_nodes"
6281        << " num_factor " << num_factor
6282        << std::endl
6283        ;
6284 
6285     int total = 0 ;
6286     int remainder = 0 ;
6287 
6288     for(int i=0 ; i < num_factor + 1 ; i++)   // NB num_factor+1 : remainder in i=0
6289     {
6290         int q_ridx = i ;
6291         std::vector<int> nidxs ;
6292         get_repeat_nidx(nidxs, q_ridx );
6293 
6294         int num_nodes = nidxs.size() ;
6295         if(q_ridx == 0) remainder = num_nodes ;
6296 
6297         total += num_nodes ;
6298         ss
6299             << " q_ridx " << std::setw(3) << q_ridx
6300             << " num_nodes " << std::setw(8) << num_nodes
6301             << std::endl
6302             ;
6303     }
6304 
6305     ss
6306         << std::setw(30) << " total     "
6307         << std::setw(8)  << total
6308         << std::endl
6309         << std::setw(30) << " remainder "
6310         << std::setw(8)  << remainder
6311         << std::endl
6312         << std::setw(30) << " total - remainder "
6313         << std::setw(8)  << (total - remainder)
6314         << std::endl
6315         ;
6316 
6317     std::string str = ss.str();
6318     return str ;
6319 }
6320 
6321 
6322 /**
6323 desc_NRT
6324 ---------
6325 
6326 Provides intelligible dumping of the lvid solids from nds/rem/tri snode vectors
6327 in a way that is useful even with many thousands of nodes. This works by
6328 creating a presenting a unique lvid table using s_unique.h functionality.
6329 
6330 **/
6331 
6332 inline std::string stree::desc_NRT() const
6333 {
6334     std::stringstream ss ;
6335     ss << desc_nds();
6336     ss << desc_rem();
6337     ss << desc_tri();
6338     std::string str = ss.str();
6339     return str ;
6340 }
6341 
6342 inline std::string stree::desc_nds() const { return desc_NRT('N'); }
6343 inline std::string stree::desc_rem() const { return desc_NRT('R'); }
6344 inline std::string stree::desc_tri() const { return desc_NRT('T'); }
6345 
6346 inline std::string stree::desc_NRT(char NRT) const
6347 {
6348     const std::vector<snode>* vec = get_node_vector(NRT);
6349     const char* nam               = get_node_vector_name(NRT);
6350 
6351     // collect lvid from the snode vector
6352     std::vector<int> vec_lvid ;
6353     for(int i=0 ; i < int(vec->size()) ; i++) vec_lvid.push_back( (*vec)[i].lvid );
6354 
6355     std::stringstream ss ;
6356     ss
6357         << "[stree::desc_NRT." << NRT << " " << ( nam ? nam : "-" ) << "\n"
6358         << " vec.size " << vec->size() << "\n"
6359         << desc_lvid_unique(vec_lvid)
6360         << "]stree::desc_NRT." << NRT << " " << ( nam ? nam : "-" ) << "\n"
6361         ;
6362     std::string str = ss.str();
6363     return str ;
6364 }
6365 
6366 
6367 
6368 
6369 
6370 inline void stree::count_repeat_index( std::map<size_t,size_t>& m, char NRT ) const
6371 {
6372     const std::vector<snode>* vec = get_node_vector(NRT);
6373     for(size_t i=0 ; i < vec->size() ; i++)
6374     {
6375         const snode& node = (*vec)[i];
6376         m[node.repeat_index] += 1 ;
6377     }
6378 }
6379 
6380 inline std::string stree::desc_repeat_index() const
6381 {
6382     size_t N0_0 = get_repeat_index_zero_count();
6383 
6384     size_t N_tot(0);
6385     size_t R_tot(0);
6386     size_t T_tot(0);
6387 
6388     size_t N0(0);
6389     size_t R0(0);
6390     size_t T0(0);
6391 
6392     std::stringstream ss ;
6393     ss << "[stree::desc_repeat_index (NB total snode counts, often more than instance counts for each ridx)\n" ;
6394     ss << desc_repeat_index('N', &N_tot, &N0) ;
6395     ss << desc_repeat_index('R', &R_tot, &R0) ;
6396     ss << desc_repeat_index('T', &T_tot, &T0) ;
6397 
6398     bool R0_expect = R_tot == R0 ;
6399     bool T0_expect = T_tot == T0 ;
6400     bool N0_expect = R_tot + T_tot == N0 ;
6401     bool N0_0_expect = N0 == N0_0 ;
6402 
6403     ss
6404        << " N_tot " << std::setw(10) << N_tot
6405        << " N0 " << std::setw(10) << N0
6406        << " N0_0 " << std::setw(10) << N0_0
6407        << " (R_tot + T_tot) " << std::setw(10) << (R_tot + T_tot)
6408        << " N0_expect " << ( N0_expect ? "YES" : "NO " )
6409        << " N0_0_expect " << ( N0_0_expect ? "YES" : "NO " )
6410        << "\n"
6411        << " R_tot " << std::setw(10) << R_tot
6412        << " R0 " << std::setw(10) << R0
6413        << " R0_expect " << ( R0_expect ? "YES" : "NO " )
6414        << "\n"
6415        << " T_tot " << std::setw(10) << T_tot
6416        << " T0 " << std::setw(10) << T0
6417        << " T0_expect " << ( T0_expect ? "YES" : "NO " )
6418        << "\n"
6419        ;
6420 
6421     ss << "]stree::desc_repeat_index\n" ;
6422     std::string str = ss.str();
6423     return str ;
6424 }
6425 inline std::string stree::desc_repeat_index(char NRT, size_t* _tot, size_t* _num0 ) const
6426 {
6427     std::map<size_t,size_t> m = {} ;
6428     count_repeat_index(m, NRT);
6429     const char* nam  = get_node_vector_name(NRT);
6430 
6431     std::stringstream ss ;
6432     ss << "[stree::desc_repeat_index " << NRT << "[" << nam << "]\n" ;
6433     size_t tot = 0 ;
6434     for (auto const& [key, val] : m)
6435     {
6436         ss << std::setw(4) << key << " : "  << std::setw(10) << val << "\n" ;
6437         tot += val ;
6438         if(key == 0 && _num0 ) *_num0 = val ;
6439     }
6440     ss << std::setw(4) << "TOT:" << " : " << std::setw(10) <<  tot << "\n" ;
6441     ss << "]stree::desc_repeat_index " << NRT << "[" << nam << "]\n" ;
6442     std::string str = ss.str();
6443 
6444     if(_tot) *_tot = tot ;
6445     return str ;
6446 }
6447 
6448 
6449 inline size_t stree::get_repeat_index_zero_count() const
6450 {
6451     size_t count = 0 ;
6452     for(size_t i=0 ; i < nds.size() ; i++) if(nds[i].repeat_index == 0) count += 1 ;
6453     return count ;
6454 }
6455 
6456 inline NPFold* stree::get_global_aabb() const
6457 {
6458     std::vector<snode> gn ;
6459     get_global_nodes(gn);
6460 
6461     size_t num_gn = gn.size();
6462 
6463     enum { bb_nj = 6 };
6464     NP* bb = NP::Make<double>(num_gn,bb_nj) ;
6465     NP* ii = NP::Make<int>(   num_gn,snode::NV ) ;
6466 
6467     double* bb0 = bb->values<double>();
6468     int*    ii0 = ii->values<int>();
6469 
6470     for(size_t i=0 ; i < num_gn ; i++)
6471     {
6472         const snode& node = gn[i];
6473         const int* nn = node.cdata();
6474         get_prim_aabb( &bb0[bb_nj*i] , node, nullptr, nullptr );
6475         for(int k=0 ; k < snode::NV ; k++ ) ii0[snode::NV*i + k ] = nn[k] ;
6476     }
6477 
6478     NPFold* f = new NPFold ;
6479     f->add("bb", bb);
6480     f->add("ii", ii);
6481     return f ;
6482 }
6483 
6484 /**
6485 stree::get_global_aabb_sibling_overlaps
6486 -----------------------------------------
6487 
6488 TEST=get_global_aabb_sibling_overlaps ~/o/sysrap/tests/stree_load_test.sh
6489 TEST=get_global_aabb_sibling_overlaps ~/o/sysrap/tests/stree_load_test.sh pdb
6490 
6491 **/
6492 
6493 
6494 inline NPFold* stree::get_global_aabb_sibling_overlaps() const
6495 {
6496     VND gnd ;
6497     get_global_nodes(gnd);
6498     size_t num_gnd = gnd.size();
6499 
6500     VBB gbb ;
6501     get_prim_aabb(gbb, gnd);
6502     size_t num_gbb = gbb.size();
6503     assert( num_gbb == num_gnd );
6504 
6505     std::cout << "[stree::get_global_aabb_sibling_overlaps num_gnd " << num_gnd << "\n" ;
6506 
6507     VBB vbb ;
6508     VND vnd ;
6509 
6510     size_t check = 0 ;
6511 
6512     for( size_t i = 0 ; i < num_gnd ; i++ )
6513     {
6514         const snode& a = gnd[i];
6515         BB& _a = gbb[i] ;
6516 
6517         for( size_t j = 0 ; j < num_gnd ; j++ )
6518         {
6519             if( j >= i ) continue ;
6520 
6521             const snode& b = gnd[j];
6522 
6523             bool same_parent = a.parent == b.parent ;
6524             if(!same_parent) continue ;
6525 
6526             bool same_depth = a.depth == b.depth ;
6527             assert(same_depth);
6528 
6529             check += 1 ;
6530 
6531             BB& _b = gbb[j] ;
6532 
6533             bool has_overlap = s_bb::HasOverlap<double>( _a.data(), _b.data() );
6534 
6535             if(has_overlap)
6536             {
6537                 vnd.push_back(a);
6538                 vnd.push_back(b);
6539 
6540                 vbb.push_back(_a);
6541                 vbb.push_back(_b);
6542             }
6543         }
6544     }
6545 
6546     std::cout << " check " << check << "\n" ;
6547     std::cout << " vnd " << vnd.size() << "\n" ;
6548     std::cout << " vbb " << vbb.size() << "\n" ;
6549 
6550     NPFold* f = new NPFold ;
6551 
6552     f->add("bb", NPX::ArrayFromVec<double,BB>(vbb, 6 ) );
6553     f->add("nn", NPX::ArrayFromVec<int,snode>(vnd, snode::NV) );
6554 
6555     f->add("gbb", NPX::ArrayFromVec<double,BB>(gbb, 6 ) );
6556     f->add("gnd", NPX::ArrayFromVec<int,snode>(gnd, snode::NV) );
6557 
6558     std::cout << "]stree::get_global_aabb_sibling_overlaps num_gnd " << num_gnd << "\n" ;
6559 
6560     return f ;
6561 }
6562 
6563 
6564 
6565 /**
6566 stree::desc_node_ELVID
6567 ------------------------
6568 
6569 Dump snode that have lvid solids with index listed in the ELVID comma delimited envvar, eg::
6570 
6571     TEST=desc_node_elvid ELVID=43,44,45,46,47,48 ~/o/sysrap/tests/stree_load_test.sh
6572 
6573 **/
6574 
6575 inline std::string stree::desc_node_ELVID() const {   return desc_node_elist("ELVID", nullptr); }
6576 inline std::string stree::desc_node_ECOPYNO() const { return desc_node_elist("ECOPYNO", nullptr); }
6577 inline std::string stree::desc_node_EBOUNDARY() const { return desc_node_elist("EBOUNDARY", nullptr); }
6578 
6579 inline std::string stree::desc_node_elist(const char* etag, const char* fallback) const
6580 {
6581     std::vector<int64_t>* elist = ssys::getenv_ParseInt64SpecList(etag, fallback) ;
6582 
6583     char NRT = 'N' ;
6584     const std::vector<snode>* vec = get_node_vector(NRT);
6585     const char* nam               = get_node_vector_name(NRT);
6586 
6587     const char* tag = etag && etag[0] == 'E' ? etag + 1 : nullptr ; // eg ELVID -> LVID
6588     int field_idx = snode_field::Idx(tag) ;
6589 
6590     std::stringstream ss ;
6591 
6592     ss << "[stree::desc_node_elist\n" ;
6593     ss << " etag " << ( etag ? etag : "-" ) << "\n" ;
6594     ss << " tag " << ( tag ? tag : "-" ) << "\n" ;
6595     ss << " field_idx " << field_idx << "\n" ;
6596     ss << " nam " << ( nam ? nam : "-" ) << "\n" ;
6597     ss << " elist " << ( elist ? "YES" : "NO " ) << "\n" ;
6598 
6599     int count = 0 ;
6600     if(elist && field_idx > -1)
6601     {
6602 
6603         for(int i=0 ; i < int(vec->size()) ; i++)
6604         {
6605             const snode& n = (*vec)[i] ;
6606             int64_t attrib = n.get_attrib(field_idx);
6607             bool listed = std::find(elist->begin(), elist->end(), attrib ) != elist->end() ;
6608             if(!listed) continue ;
6609             count += 1 ;
6610             ss << std::setw(7) << i << " : " << n.desc() << " tag " << ( tag ? tag : "-" ) << " listed " << ( listed ? "YES" : "NO " ) << "\n" ;
6611         }
6612     }
6613     ss << " count " << count << "\n" ;
6614     ss << "]stree::desc_node_elist\n" ;
6615 
6616     std::string str = ss.str();
6617     return str ;
6618 }
6619 
6620 
6621 
6622 
6623 
6624 /**
6625 stree::add_inst
6626 ----------------
6627 
6628 Canonically invoked from U4Tree::Create
6629 
6630 1. lookup snode from nidx
6631 2. encode snode identity info into transform fourth columns
6632 3. collect transforms into inst, iinst vectors and nidx into inst_nidx
6633 
6634 * NB restrict to 32 bit of identity info, so it survives narrowing
6635 
6636 **/
6637 
6638 inline void stree::add_inst(
6639     glm::tmat4x4<double>& tr_m2w,
6640     glm::tmat4x4<double>& tr_w2m,
6641     int gas_idx,
6642     int nidx )
6643 {
6644     assert( nidx > -1 && nidx < int(nds.size()) );
6645     const snode& nd = nds[nidx];    // structural volume node
6646 
6647     int ins_idx = int(inst.size()); // 0-based index follow sqat4.h::setIdentity
6648 
6649     glm::tvec4<int64_t> col3 ;   // formerly uint64_t
6650 
6651     col3.x = ins_idx ;            // formerly  +1
6652     col3.y = gas_idx ;            // formerly  +1
6653     col3.z = nd.sensor_id ;       // formerly ias_idx + 1 (which was always 1)
6654     col3.w = nd.sensor_index ;
6655 
6656     strid::Encode(tr_m2w, col3 );
6657     strid::Encode(tr_w2m, col3 );
6658 
6659     inst.push_back(tr_m2w);
6660     iinst.push_back(tr_w2m);
6661 
6662     inst_nidx.push_back(nidx);
6663 }
6664 
6665 inline void stree::add_inst_identity( int gas_idx, int nidx )
6666 {
6667     glm::tmat4x4<double> tr_m2w(1.) ;
6668     glm::tmat4x4<double> tr_w2m(1.) ;
6669 
6670     add_inst(tr_m2w, tr_w2m, gas_idx, nidx );
6671 }
6672 
6673 
6674 /**
6675 stree::add_inst
6676 ------------------
6677 
6678 Canonically invoked from U4Tree::Create after stree::factorize
6679 
6680 
6681 ::
6682 
6683     In [7]: f.inst_f4[:,:,3].view(np.int32)
6684     Out[7]:
6685     array([[     0,      0,     -1,     -1],
6686            [     1,      1, 300000,  18116],
6687            [     2,      1, 300001,  18117],
6688            [     3,      1, 300002,  18118],
6689            [     4,      1, 300003,  18119],
6690            ...
6691            [ 48472,      9,      3,    499],
6692            [ 48473,      9,      0,    500],
6693            [ 48474,      9,      1,    501],
6694            [ 48475,      9,      2,    502],
6695            [ 48476,      9,      3,    503]], dtype=int32)
6696 
6697           # ins_idx, gas_idx,  sid    six
6698 
6699     In [8]: f.inst_f4.shape
6700     Out[8]: (48477, 4, 4)
6701 
6702     In [10]: f.inst_nidx
6703     Out[10]:
6704     array([     0, 279688, 279693, 279698, 279703, 279708, 279713, 279718, 279723, 279728, 279733, 279738, 279743, 279748, 279753, 279758, ...,  63638,  63768,  63898,  64028,  64159,  64289,  64419,
6705             64549,  64681,  64811,  64941,  65071,  65202,  65332,  65462,  65592], dtype=int32)
6706 
6707     In [11]: f.inst_nidx.shape
6708     Out[11]: (48477,)
6709 
6710 
6711 Note that the inst gas are gauranteed to be
6712 in contiguous tranches, so can just have instcount
6713 and offsets to reference to relevant transforms.
6714 
6715 ::
6716 
6717     g = i[:,1,3].view(np.int64)
6718 
6719     In [27]: np.where( g == 0 )[0]
6720     Out[27]: array([0])
6721 
6722     In [28]: np.where( g == 1 )[0]
6723     Out[28]: array([    1,     2,     3, ..., 25598, 25599, 25600])
6724 
6725     In [29]: np.where( g == 2 )[0]
6726     Out[29]: array([25601, 25602, 25603, ..., 38213, 38214, 38215])
6727 
6728     In [30]: np.where( g == 3 )[0]
6729     Out[30]: array([38216, 38217, 38218, ..., 43210, 43211, 43212])
6730 
6731     In [31]: np.where( g == 4 )[0]
6732     Out[31]: array([43213, 43214, 43215, ..., 45610, 45611, 45612])
6733 
6734 
6735 **/
6736 
6737 inline void stree::add_inst()
6738 {
6739     int ridx = 0 ;
6740     int nidx = 0 ;
6741     int num_inst = 1 ;
6742     int tot_inst = 0 ;
6743 
6744     add_inst_identity(ridx, nidx );   // global instance with identity transforms
6745 
6746     inst_info.push_back( {ridx,num_inst,tot_inst,0} );
6747     tot_inst += num_inst  ;
6748 
6749 
6750     glm::tmat4x4<double> tr_m2w(1.) ;
6751     glm::tmat4x4<double> tr_w2m(1.) ;
6752 
6753     unsigned num_factor = get_num_factor();
6754     for(int i=0 ; i < int(num_factor) ; i++)
6755     {
6756         std::vector<int> nodes ;
6757         get_factor_nodes(nodes, i);
6758 
6759         num_inst = nodes.size();
6760         ridx = i + 1 ;       // 0 is the global instance, so need this + 1
6761 
6762         if(level > 1) std::cout
6763             << "stree::add_inst.num_factor "
6764             << " i " << std::setw(3) << i
6765             << " ridx(gas_idx) " << std::setw(3) << ridx
6766             << " num_inst " << std::setw(7) << num_inst
6767             << std::endl
6768             ;
6769 
6770         inst_info.push_back( {ridx,num_inst,tot_inst,0} );
6771         tot_inst += num_inst ;
6772 
6773         for(int j=0 ; j < num_inst ; j++)
6774         {
6775             nidx = nodes[j];
6776 
6777             bool local = false ;
6778             bool reverse = false ;
6779             std::ostream* out = nullptr ;
6780             VTR* t_stack = nullptr ;
6781 
6782             get_node_product( tr_m2w, tr_w2m, nidx, local, reverse, out, t_stack  );
6783 
6784             add_inst(tr_m2w, tr_w2m, ridx, nidx );
6785         }
6786     }
6787 
6788 
6789 
6790     int num_tri = get_num_triangulated();
6791     assert( num_tri == 0 || num_tri == 1 );
6792 
6793     if(level > 1 ) std::cout
6794         << "stree::add_inst.num_tri "
6795         << " num_tri " << std::setw(7) << num_tri
6796         << std::endl
6797         ;
6798 
6799     if( num_tri == 1  )
6800     {
6801         const snode& tri0 = tri[0] ;
6802 
6803         ridx += 1 ;
6804         num_inst = 1 ;
6805         nidx = tri0.index  ; // ? node index of first of the triangulated volumes
6806         add_inst_identity( ridx, nidx );
6807 
6808         inst_info.push_back( {ridx,num_inst,tot_inst,0} );
6809         tot_inst += num_inst ;
6810     }
6811 
6812     narrow_inst();
6813 }
6814 
6815 
6816 
6817 
6818 
6819 inline void stree::narrow_inst()
6820 {
6821     strid::Narrow( inst_f4,   inst );
6822     strid::Narrow( iinst_f4, iinst );
6823 }
6824 
6825 inline void stree::clear_inst()
6826 {
6827     inst.clear();
6828     iinst.clear();
6829     inst_f4.clear();
6830     iinst_f4.clear();
6831 }
6832 
6833 inline std::string stree::desc_inst() const
6834 {
6835     std::stringstream ss ;
6836     ss << "stree::desc_inst"
6837        << " inst " << inst.size()
6838        << " iinst " << iinst.size()
6839        << " inst_f4 " << inst_f4.size()
6840        << " iinst_f4 " << iinst_f4.size()
6841        ;
6842     std::string s = ss.str();
6843     return s ;
6844 }
6845 
6846 
6847 inline std::string stree::desc_inst_info() const
6848 {
6849     std::stringstream ss ;
6850     ss << "[stree::desc_inst_info {ridx, inst_count, inst_offset, 0} " << std::endl ;
6851     int num_inst_info = inst_info.size();
6852 
6853     int tot_inst = 0 ;
6854     for(int i=0 ; i < num_inst_info ; i++)
6855     {
6856         const int4& info = inst_info[i] ;
6857         ss
6858            << "{"
6859            << std::setw(3) << info.x
6860            << ","
6861            << std::setw(7) << info.y
6862            << ","
6863            << std::setw(7) << info.z
6864            << ","
6865            << std::setw(3) << info.w
6866            << "}"
6867            << std::endl
6868            ;
6869         tot_inst += info.y ;
6870     }
6871     ss << "]stree::desc_inst_info tot_inst " <<  tot_inst << std::endl ;
6872     std::string str = ss.str();
6873     return str ;
6874 }
6875 
6876 inline std::string stree::desc_inst_info_check() const
6877 {
6878     int num_gas  = inst_info.size();
6879     [[maybe_unused]] int num_inst = inst.size();
6880     int tot = 0 ;
6881     int tot_count = 0 ;
6882     for(int i=0 ; i < num_gas ; i++)
6883     {
6884         const int4&  _inst_info = inst_info[i] ;
6885         [[maybe_unused]] int ridx = _inst_info.x ;
6886         int count = _inst_info.y ;
6887         int offset = _inst_info.z ;
6888 
6889         tot_count += count ;
6890 
6891         assert( ridx == i );
6892         for(int j=0 ; j < count ; j++)
6893         {
6894             [[maybe_unused]] int idx = offset + j ;
6895             assert( idx < num_inst );
6896             assert( idx == tot );
6897             tot += 1 ;
6898         }
6899     }
6900 
6901     std::stringstream ss ;
6902     ss << "stree::desc_inst_info_check"
6903        << " tot_count " <<  tot_count
6904        << std::endl
6905        << " tot " << tot
6906        ;
6907     std::string str = ss.str();
6908     return str ;
6909 }
6910 
6911 
6912 /**
6913 stree::find_inst_gas
6914 ----------------------
6915 
6916 Uses inst_info to provide the instance index of the ordinal-th
6917 instance for the gas_idx
6918 
6919 **/
6920 
6921 inline int stree::find_inst_gas( int q_gas_idx, int q_gas_ordinal ) const
6922 {
6923     int num_gas  = inst_info.size();
6924     bool valid = q_gas_idx < num_gas ;
6925     if(!valid) return -2 ;
6926 
6927     const int4& _inst_info = inst_info[q_gas_idx] ;
6928 
6929     [[maybe_unused]] int ridx = _inst_info.x ;
6930     int count = _inst_info.y ;
6931     int offset = _inst_info.z ;
6932 
6933     assert( ridx == q_gas_idx );
6934 
6935     if( q_gas_ordinal < 0 ) q_gas_ordinal += count ;
6936     int inst_idx = q_gas_ordinal < count ? offset + q_gas_ordinal : -1 ;
6937     int num_inst = inst.size();
6938     return inst_idx < num_inst ? inst_idx : -3  ;
6939 }
6940 
6941 inline int stree::find_inst_gas_slowly( int q_gas_idx, int q_gas_ordinal ) const
6942 {
6943     std::vector<int> v_inst_idx ;
6944     find_inst_gas_slowly_( v_inst_idx, q_gas_idx );
6945     int num = v_inst_idx.size() ;
6946     if( q_gas_ordinal < 0 ) q_gas_ordinal += num ;
6947     int inst_idx = q_gas_ordinal > -1 && q_gas_ordinal < num ? v_inst_idx[q_gas_ordinal] : -1 ;
6948 
6949     bool dump = false ;
6950     if(dump && q_gas_idx == 0 ) std::cout
6951         << "stree::find_inst_gas_slowly"
6952         << " q_gas_idx " << q_gas_idx
6953         << " q_gas_ordinal " << q_gas_ordinal
6954         << " v_inst_idx.size " << v_inst_idx.size()
6955         << " inst_idx " << inst_idx
6956         << std::endl
6957         ;
6958 
6959     return inst_idx ;
6960 }
6961 
6962 inline void stree::find_inst_gas_slowly_( std::vector<int>& v_inst_idx , int q_gas_idx ) const
6963 {
6964     int num_inst = inst.size();
6965     glm::tvec4<int64_t> col3 ;
6966     for(int i=0 ; i < num_inst ; i++)
6967     {
6968         const glm::tmat4x4<double>& tr_m2w = inst[i] ;
6969         strid::Decode(tr_m2w, col3 );
6970         int inst_idx = col3.x ;
6971         int gas_idx = col3.y ;
6972         assert( inst_idx == i );
6973         if( gas_idx == q_gas_idx ) v_inst_idx.push_back(inst_idx) ;
6974     }
6975 }
6976 
6977 
6978 
6979 
6980 inline int stree::get_inst_identity( glm::tvec4<int64_t>& col3, int ii ) const
6981 {
6982     const glm::tmat4x4<double>* tr = get_inst(ii) ;
6983     if(!tr) return 1 ;
6984     strid::Decode( *tr, col3 );
6985     return 0 ;
6986 }
6987 inline int stree::get_inst_identity( int& inst_idx, int& gas_idx, int& sensor_identifier, int& sensor_index, int ii ) const
6988 {
6989     glm::tvec4<int64_t> col3 = {} ;
6990     int rc = get_inst_identity( col3, ii );
6991     inst_idx = col3.x ;
6992     gas_idx = col3.y ;
6993     sensor_identifier = col3.z ;
6994     sensor_index = col3.w ;
6995     return rc ;
6996 }
6997 
6998 
6999 inline const glm::tmat4x4<double>* stree::get_inst(int idx) const
7000 {
7001     return idx > -1 && idx < int(inst.size()) ? &inst[idx] : nullptr ;
7002 }
7003 inline const glm::tmat4x4<double>* stree::get_iinst(int idx) const
7004 {
7005     return idx > -1 && idx < int(iinst.size()) ? &iinst[idx] : nullptr ;
7006 }
7007 
7008 inline const glm::tmat4x4<float>* stree::get_inst_f4(int idx) const
7009 {
7010     return idx > -1 && idx < int(inst_f4.size()) ? &inst_f4[idx] : nullptr ;
7011 }
7012 inline const glm::tmat4x4<float>* stree::get_iinst_f4(int idx) const
7013 {
7014     return idx > -1 && idx < int(iinst_f4.size()) ? &iinst_f4[idx] : nullptr ;
7015 }
7016 
7017 
7018 
7019 /**
7020 stree::get_mtindex_range
7021 --------------------------
7022 
7023 As the 0-based G4Material index is a creation index and not all
7024 created G4Material may be be in active use there can
7025 be gaps in the range of this origin material index.
7026 
7027 **/
7028 
7029 inline void stree::get_mtindex_range(int& mn, int& mx ) const
7030 {
7031     mn = std::numeric_limits<int>::max() ;
7032     mx = 0 ;
7033     for(unsigned i=0 ; i < mtindex.size() ; i++)
7034     {
7035         int mtidx = mtindex[i] ;
7036         if(mtidx > mx) mx = mtidx ;
7037         if(mtidx < mn) mn = mtidx ;
7038     }
7039 }
7040 
7041 inline std::string stree::desc_mt() const
7042 {
7043     int mn, mx ;
7044     get_mtindex_range( mn, mx);
7045     std::stringstream ss ;
7046     ss << "stree::desc_mt"
7047        << " mtname " << mtname.size()
7048        << " mtname_no_rindex " << mtname_no_rindex.size()
7049        << " mtindex " << mtindex.size()
7050        << " mtline " << mtline.size()
7051        << " mtindex.mn " << mn
7052        << " mtindex.mx " << mx
7053        << std::endl
7054        ;
7055 
7056     // populate mtline with SBnd::FillMaterialLine after have bndnames
7057 
7058     bool with_mtline = mtline.size() > 0 ;
7059     assert( mtname.size() == mtindex.size() );
7060     unsigned num_mt = mtname.size();
7061 
7062     if(with_mtline) assert( mtline.size() == num_mt );
7063 
7064     for(unsigned i=0 ; i < num_mt ; i++)
7065     {
7066         const char* mtn = mtname[i].c_str();
7067         int         mtidx = mtindex[i];
7068         int         mtlin = with_mtline ? mtline[i] : -1 ;
7069         ss
7070             << " i " << std::setw(3) << i
7071             << " mtindex " << std::setw(3) << mtidx
7072             << " mtline " << std::setw(3)  << mtlin
7073             << " mtname " << mtn
7074             << std::endl
7075             ;
7076     }
7077 
7078     std::string str = ss.str();
7079     return str ;
7080 }
7081 
7082 /**
7083 stree::desc_bd
7084 ----------------
7085 
7086 **/
7087 
7088 inline std::string stree::desc_bd() const
7089 {
7090     std::stringstream ss ;
7091     ss << "stree::desc_bd"
7092        << " vbd.size " << vbd.size()
7093        << " bdname.size " << bdname.size()
7094        << std::endl
7095        ;
7096 
7097     assert( vbd.size() == bdname.size() );
7098 
7099     int num_bd = vbd.size() ;
7100     for(int i=0 ; i < num_bd ; i++)
7101     {
7102         const std::string& bdn = bdname[i] ;
7103         const int4& bd_ = vbd[i] ;
7104         ss << std::setw(4) << i
7105            << "("
7106            << std::setw(3) << bd_.x
7107            << " "
7108            << std::setw(3) << bd_.y
7109            << " "
7110            << std::setw(3) << bd_.z
7111            << " "
7112            << std::setw(3) << bd_.w
7113            << ") "
7114            << bdn
7115            << std::endl
7116            ;
7117     }
7118     std::string str = ss.str();
7119     return str ;
7120 }
7121 
7122 
7123 
7124 
7125 
7126 /**
7127 stree::add_material
7128 ----------------------
7129 
7130 Canonically called from U4Tree::initMaterials_r/U4Tree::initMaterial
7131 
7132 g4index is the Geant4 creation index obtained from G4Material::GetIndex
7133 
7134 Note that not all G4Material are added, only G4Material that are
7135 referenced from G4LogicalVolume are added,
7136 so the g4index might not match the idx from mtname.
7137 
7138 **/
7139 
7140 inline int stree::add_material( const char* name, unsigned g4index )
7141 {
7142     int idx = mtname.size() ;
7143     mtname.push_back(name);
7144     mtindex.push_back(g4index);
7145     // assert( idx == g4index );   NOT FULFILLED
7146     return idx ;
7147 }
7148 inline int stree::num_material() const
7149 {
7150     return mtname.size();
7151 }
7152 
7153 /**
7154 stree::add_extra_surface
7155 -------------------------
7156 
7157 If the name is already present in the suname list
7158 just returns the 0-based index otherwise add to suname
7159 and return the new index.
7160 
7161 **/
7162 
7163 inline int stree::add_extra_surface( const char* name )
7164 {
7165     int idx = -1 ;
7166     int prior = stree::GetValueIndex<std::string>( suname, name ) ;
7167     if(prior > -1)
7168     {
7169         idx = prior ;
7170     }
7171     else   // new surface name
7172     {
7173         idx = suname.size() ;
7174         suname.push_back(name) ;
7175         //suindex.push_back(idx);
7176         int idx2 = stree::GetValueIndex<std::string>( suname, name ) ;
7177         bool idx_expect = idx2 == idx ;
7178         assert( idx_expect );
7179         if(!idx_expect) std::raise(SIGINT);
7180     }
7181     return idx ;
7182 }
7183 
7184 
7185 inline int stree::add_extra_surface(const std::vector<std::string>& names  )
7186 {
7187     int idx = -1 ;
7188     for(unsigned i=0 ; i < names.size() ; i++)
7189     {
7190         const char* surname = names[i].c_str() ;
7191         idx = add_extra_surface( surname );
7192     }
7193     return idx ;
7194 }
7195 
7196 
7197 inline int stree::get_surface( const char* name ) const
7198 {
7199     return stree::GetValueIndex<std::string>(suname, name ) ;
7200 }
7201 inline int stree::num_surface() const
7202 {
7203     return suname.size();
7204 }
7205 
7206 
7207 
7208 
7209 /**
7210 stree::add_surface_implicit
7211 ----------------------------
7212 
7213 Used from U4TreeBorder::get_implicit_idx
7214 
7215 THIS FORMERLY RETURNED THE IMPLICIT_IDX,
7216 NOW RETURNING THE STANDARD SURFACE IDX
7217 
7218 **/
7219 
7220 
7221 inline int stree::add_surface_implicit( const char* name )
7222 {
7223     int idx = add_extra_surface(name);
7224 
7225     int implicit_idx = stree::GetValueIndex<std::string>( implicit, name ) ;
7226     if(implicit_idx == -1)  // new implicit
7227     {
7228         implicit.push_back(name) ;
7229         implicit_idx = stree::GetValueIndex<std::string>( implicit, name ) ;
7230         assert( implicit_idx > -1 );
7231         assert( idx == num_surface_standard() + implicit_idx ) ;
7232     }
7233 
7234     return idx ;
7235 }
7236 
7237 
7238 inline int stree::get_surface_implicit( const char* name ) const
7239 {
7240     return stree::GetValueIndex<std::string>(implicit, name ) ;
7241 }
7242 inline int stree::num_surface_implicit() const
7243 {
7244     return implicit.size();
7245 }
7246 inline int stree::num_surface_standard() const
7247 {
7248     return num_surface() - num_surface_implicit() ;
7249 }
7250 
7251 
7252 
7253 
7254 
7255 
7256 
7257 inline int stree::add_boundary( const int4& bd_ )
7258 {
7259     int boundary = GetValueIndex<int4>( vbd, bd_ ) ;
7260     if(boundary == -1)  // new boundary
7261     {
7262         int num_bd_check = vbd.size();
7263         std::string bdn = get_boundary_name(bd_,'/') ;
7264         vbd.push_back(bd_) ;
7265         bdname.push_back(bdn.c_str()) ;
7266         boundary = GetValueIndex<int4>( vbd, bd_ ) ;
7267         bool boundary_expect = num_bd_check == boundary ;
7268         assert( boundary_expect );
7269         if(!boundary_expect) std::raise(SIGINT);
7270     }
7271     return boundary ;
7272 }
7273 
7274 
7275 inline const char* stree::get_material_name( int idx ) const
7276 {
7277     return snam::get(mtname, idx) ;
7278 }
7279 inline const char* stree::get_surface_name( int idx ) const
7280 {
7281     return snam::get(suname, idx) ;
7282 }
7283 inline std::string stree::get_boundary_name( const int4& bd_, char delim ) const
7284 {
7285     const char* omat = get_material_name( bd_.x );
7286     const char* osur = get_surface_name(  bd_.y );
7287     const char* isur = get_surface_name(  bd_.z );
7288     const char* imat = get_material_name( bd_.w );
7289 
7290     assert( omat );
7291     assert( imat );
7292 
7293     std::stringstream ss ;
7294     ss
7295        << omat
7296        << delim
7297        << ( osur ? osur : "" )
7298        << delim
7299        << ( isur ? isur : "" )
7300        << delim
7301        << imat
7302        ;
7303 
7304     std::string str = ss.str();
7305     return str ;
7306 }
7307 
7308 
7309 
7310 /**
7311 stree::get_surface_subfold
7312 ---------------------------
7313 
7314 Note that implicit and perfect names will be found
7315 in the suname vector but there is no corresponding
7316 subfold for them, hence no metadata.
7317 
7318 **/
7319 inline NPFold* stree::get_surface_subfold(int idx) const
7320 {
7321     const char* sn = get_surface_name(idx);
7322     assert(sn) ;
7323     NPFold* sub = surface->get_subfold(sn) ;
7324     return sub ;
7325 }
7326 
7327 /**
7328 stree::initStandard
7329 ----------------------
7330 
7331 Called from U4Tree::initStandard after most of
7332 the conversion is done with mat and sur arrays prepared.
7333 
7334 **/
7335 
7336 inline void stree::initStandard()
7337 {
7338     standard->deferred_init( vbd, bdname, suname, surface );
7339 
7340     init_material_mapping() ;
7341 }
7342 
7343 
7344 /**
7345 stree::init_material_mapping
7346 -------------------------------
7347 
7348 Formerly similar to this was done by stree::import_bnd
7349 but need the material map for live geometry running,
7350 so repositioned to being invoked from stree::initStandard
7351 
7352 **/
7353 
7354 
7355 inline void stree::init_material_mapping()
7356 {
7357     assert( mtline.size() == 0 );
7358     assert( mtname.size() == mtindex.size() );
7359 
7360     // for each mtname use bnd->names to fill the mtline vector
7361     SBnd::FillMaterialLine( mtline, mtindex, mtname, bdname );
7362 
7363     // fill (int,int) map from the mtline and mtindex vectors
7364     init_mtindex_to_mtline() ;
7365 
7366     if(ssys::getenvbool(_init_material_mapping_DUMP)) std::cerr
7367         << "stree::init_material_mapping"
7368         << " [" << _init_material_mapping_DUMP <<  "] "
7369         << " desc_mt "
7370         << std::endl
7371         << desc_mt()
7372         << std::endl
7373         ;
7374 }
7375 
7376 
7377 
7378 
7379 
7380 /**
7381 stree::import_bnd
7382 -------------------
7383 
7384 Moved from SSim::import_bnd
7385 
7386 The mtname and mtindex are populated by stree::add_material,
7387 beyond those just need the bnd names to determine the
7388 mtline and the map.
7389 
7390 
7391 inline void stree::import_bnd(const NP* bnd)
7392 {
7393     assert(bnd) ;
7394     const std::vector<std::string>& bnames = bnd->names ;
7395 
7396     assert( mtline.size() == 0 );
7397     assert( mtname.size() == mtindex.size() );
7398 
7399     // for each mtname use bnd->names to fill the mtline vector
7400     SBnd::FillMaterialLine( mtline, mtindex, mtname, bnames );
7401 
7402     // fill (int,int) map from the mtline and mtindex vectors
7403     init_mtindex_to_mtline() ;
7404 
7405     if( level > 1 ) std::cerr
7406         << "stree::import_bnd"
7407         << " level > 1 [" << level << "]"
7408         << " bnd " << bnd->sstr()
7409         << " desc_mt "
7410         << std::endl
7411         << desc_mt()
7412         << std::endl
7413         ;
7414 }
7415 
7416 **/
7417 
7418 
7419 
7420 
7421 /**
7422 stree::init_mtindex_to_mtline
7423 ------------------------------
7424 
7425 SUSPECT THIS IS ONLY CALLED ON LOADING : NOT
7426 ON CREATION : CAUSING CERENKOV MTLINE ISSUE
7427 
7428 Canonically invoked from SSim::import_bnd/SBnd::FillMaterialLine following
7429 live creation or from stree::load_ when loading a persisted stree.
7430 
7431 TODO: suspect this complication can be avoided using NPX.h load/save of maps ?
7432 **/
7433 
7434 inline void stree::init_mtindex_to_mtline()
7435 {
7436     bool consistent = mtindex.size() == mtline.size() ;
7437     if(!consistent) std::cerr
7438         << "stree::init_mtindex_to_mtline"
7439         << " mtindex.size " << mtindex.size()
7440         << " mtline.size " << mtline.size()
7441         << " : must use SBnd::FillMaterialLine once have bnd specs"
7442         << std::endl
7443         ;
7444 
7445     assert(consistent);
7446     for(unsigned i=0 ; i < mtindex.size() ; i++) mtindex_to_mtline[mtindex[i]] = mtline[i] ;
7447 }
7448 
7449 inline int stree::lookup_mtline( int mtindex ) const
7450 {
7451     return mtindex_to_mtline.count(mtindex) == 0 ? -1 :  mtindex_to_mtline.at(mtindex) ;
7452 }
7453 
7454 
7455 /**
7456 stree::populate_prim_nidx
7457 ----------------------------
7458 
7459 nidx
7460    index into the full flattened tree of volumes, for JUNO 0->~400k
7461 
7462 prim
7463    index into CSGPrim array (aka globalPrimIdx), for JUNO 0->~4k
7464    which is greatly reduced compared to the full tree due to the factorization
7465    that avoids repetition of same shape volumes
7466 
7467 prim_nidx
7468    array of *nidx* indices for each implicit *prim* index of that array.
7469    This *prim* indices to be converted into *nidx*.
7470    For instanced volumes the first *nidx* is returned, for global volumes
7471    the relationship is 1:1 making prim_nidx more useful
7472 
7473 
7474 How are stree.h/nidx related to CSGFoundry/globalPrimIdx
7475 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7476 
7477 This method used a "faux_import" technique to arrive at the "globalPrimIdx" using
7478 a stripped down form of CSGImport::importSolid within stree that establish
7479 the correspondence between nidx and globalPrimIdx
7480 Every nidx will have a single globalPrimIdx but the relationship is not 1:1
7481 multiple nidx will have the same globalPrimIdx due to instancing.
7482 
7483 HMM this approach provides way to go from globalPrimIdx to nidx,
7484 but not the general case of the reverse for instanced nodes.
7485 That might not matter as the main usefulness of globalPrimIdx
7486 is for the global geometry. Instanced geometry is best
7487 identified with the instance index.
7488 
7489 
7490 globalPrimIdx
7491 ~~~~~~~~~~~~~~~
7492 
7493 cx/cxt_min.py simtrace intersect point clouds are labelled with globalPrimIdx::
7494 
7495     277     _ii = ust[:,3,3].view(np.int32)   ## instanceIndex
7496     278     _gp_bn = ust[:,2,3].view(np.int32)    ## simtrace intersect boundary indices
7497     279     _gp = _gp_bn >> 16      ## globalPrimIdx
7498     280     _bn = _gp_bn & 0xffff   ## boundary
7499 
7500 that ust[:,2,3] is written GPU side by sevent::add_simtrace pulling from (quad2)prd::
7501 
7502      a.q2.u.w = prd->globalPrimIdx_boundary() ;
7503 
7504 The globalPrimIdx is pulled from the geometry on intersection CSGOptiX7.cu::
7505 
7506     882     HitGroupData* hg  = (HitGroupData*)optixGetSbtDataPointer();
7507     883     int nodeOffset = hg->prim.nodeOffset ;
7508     884     int globalPrimIdx = hg->prim.globalPrimIdx ;
7509 
7510 Geometry is labelled with the globalPrimIdx by SBT from the CSGPrim::
7511 
7512     1198 void SBT::setPrimData( CustomPrim& cp, const CSGPrim* prim )
7513     1199 {
7514     1200     cp.numNode = prim->numNode();
7515     1201     cp.nodeOffset = prim->nodeOffset();
7516     1202     cp.globalPrimIdx = prim->globalPrimIdx();
7517     1203 }
7518 
7519 The globalPrimIdx is minted by the addition of CSGPrim to the geometry with the below making it a
7520 0-based idx of CSGPrim creation done compound-solid by compound solid::
7521 
7522     2220 CSGPrim* CSGFoundry::addPrim(int num_node, int nodeOffset_ )
7523     2221 {
7524     2222     LOG_IF(fatal, !last_added_solid) << "must addSolid prior to addPrim" ;
7525     2223     assert( last_added_solid );
7526     2224
7527     2225     unsigned primOffset = last_added_solid->primOffset ;
7528     2226     unsigned numPrim    = last_added_solid->numPrim ;
7529     2227
7530     2228     unsigned globalPrimIdx = prim.size();
7531 
7532 
7533 **/
7534 
7535 inline void stree::populate_prim_nidx()
7536 {
7537     if(level > 0) std::cout << "[stree::populate_prim_nidx\n" ;
7538     faux_importSolid();
7539     if(level > 0) std::cout << "]stree::populate_prim_nidx\n" ;
7540 }
7541 
7542 inline void stree::faux_importSolid()
7543 {
7544     // follow the pattern of CSGImport::importSolid
7545     int num_ridx = get_num_ridx() ;
7546     for(int ridx=0 ; ridx < num_ridx ; ridx++)
7547     {
7548         char ridx_type = get_ridx_type(ridx) ;
7549         switch(ridx_type)
7550         {
7551             case 'R': faux_importSolidGlobal( ridx, ridx_type ) ; break ;   // remainder
7552             case 'T': faux_importSolidGlobal( ridx, ridx_type ) ; break ;   // triangulate
7553             case 'F': faux_importSolidFactor( ridx, ridx_type ) ; break ;   // factor
7554         }
7555     }
7556 }
7557 inline void stree::faux_importSolidGlobal(int ridx, char ridx_type )
7558 {
7559     assert( ridx_type == 'R' || ridx_type == 'T' );  // remainder or triangulate
7560     const std::vector<snode>* src = get_node_vector(ridx_type) ;
7561     assert( src );
7562     int num_src = src->size() ;
7563 
7564     for(int i=0 ; i < num_src ; i++)
7565     {
7566         int primIdx = i ;  // primIdx within the CSGSolid
7567         const snode& node = (*src)[primIdx] ;
7568         int globalPrimIdx = faux_importPrim( primIdx, node ) ;
7569         assert( globalPrimIdx >= 0 );
7570     }
7571 }
7572 
7573 
7574 inline void stree::faux_importSolidFactor(int ridx, char ridx_type )
7575 {
7576     assert( ridx > 0 );
7577     assert( ridx_type == 'F' );
7578 
7579     int  num_rem = get_num_remainder() ;
7580     assert( num_rem == 1 ) ;  // YEP: always one
7581 
7582     int num_factor = factor.size() ;
7583     assert( ridx - num_rem < num_factor );
7584 
7585     const sfactor& sf = factor[ridx-num_rem] ;
7586     int subtree = sf.subtree ;  // number of prim within the compound solid
7587 
7588 
7589     int q_repeat_index = ridx ;
7590     int q_repeat_ordinal = 0 ;   // just first repeat
7591 
7592     std::vector<snode> nodes ;
7593     get_repeat_node(nodes, q_repeat_index, q_repeat_ordinal) ;
7594 
7595     if(level > 1) std::cout
7596         << " stree::faux_importSolidFactor "
7597         << " ridx " << ridx
7598         << " ridx_type " << ridx_type
7599         << " num_rem " << num_rem
7600         << " num_factor " << num_factor
7601         << " nodes.size " << nodes.size()
7602         << " subtree " << subtree
7603         << "\n"
7604         ;
7605 
7606     assert( subtree == int(nodes.size()) );
7607 
7608     for(int i=0 ; i < subtree ; i++)
7609     {
7610         int primIdx = i ;  // primIdx within the CSGSolid
7611         const snode& node = nodes[primIdx] ;   // structural node
7612         int globalPrimIdx = faux_importPrim( primIdx, node );
7613         assert( globalPrimIdx >= 0 );
7614     }
7615 }
7616 
7617 inline int stree::faux_importPrim(int primIdx, const snode& node )
7618 {
7619     int globalPrimIdx = prim_nidx.size();
7620     int nidx = node.index ;
7621     const char* prn = soname[node.lvid].c_str();
7622 
7623     if(level > 2) std::cout
7624         << "stree::faux_importPrim"
7625         << " primIdx " << std::setw(4) << primIdx
7626         << " globalPrimIdx " << std::setw(6) << globalPrimIdx
7627         << " nidx " << std::setw(8) << nidx
7628         << " prn(=soname[node.lvid]) " << prn
7629         << "\n"
7630         ;
7631 
7632     prim_nidx.push_back(nidx);
7633     prname.push_back(prn);
7634     return globalPrimIdx ;
7635 }
7636 
7637 
7638 inline const char* stree::get_prname(int globalPrimIdx) const
7639 {
7640     return globalPrimIdx < int(prname.size()) ? prname[globalPrimIdx].c_str() : nullptr ;
7641 }
7642 
7643 
7644 /**
7645 stree::search_prim_for_nidx_first
7646 ---------------------------------
7647 
7648 Only expected to give globalPrimIdx for global or first instance nidx,
7649 for the rest of the repeated nidx will give -1
7650 
7651 **/
7652 
7653 inline int stree::search_prim_for_nidx_first(int nidx) const
7654 {
7655     size_t gpi = std::distance( prim_nidx.begin(), std::find(prim_nidx.begin(), prim_nidx.end(), nidx ));
7656     return gpi < prim_nidx.size() ? int(gpi) : -1  ;
7657 }
7658 
7659 
7660 /**
7661 stree::populate_nidx_prim  WIP : needs shakedown on full geometry
7662 -------------------------------------------------------------------
7663 
7664 Needs to be called after populate_prim_nidx
7665 
7666 * For every tree node *nidx* find the prim idx
7667 * implicit assumption that the same prim type does not
7668   occur across multiple ridx : that allows simple modulo approach
7669 
7670 
7671 For each ridx:
7672 
7673 1. get first nodes with q_repeat_index
7674 2. get all nodes with q_repeat_index
7675 
7676 
7677 ::
7678 
7679     In [4]: np.where( f.nidx_prim == -1 )
7680     Out[4]: (array([], dtype=int64), array([], dtype=int64))
7681 
7682     In [5]: f.nidx_prim
7683     Out[5]:
7684     array([[   0],
7685            [   1],
7686            [   2],
7687            [   3],
7688            [   4],
7689            ...,
7690            [3103],
7691            [3100],
7692            [3101],
7693            [3102],
7694            [3103]], shape=(386577, 1), dtype=int32)
7695 
7696     In [7]: tab = np.c_[np.unique(f.nidx_prim, return_counts=True)]
7697 
7698     In [9]: tab[tab[:,1]>500]
7699     Out[9]:
7700     array([[ 3070, 25600],
7701            [ 3071, 25600],
7702            [ 3072, 25600],
7703            [ 3073, 25600],
7704            [ 3074, 25600],
7705            [ 3075, 12657],
7706 
7707 **/
7708 
7709 
7710 inline void stree::populate_nidx_prim()
7711 {
7712     if(level > 0) std::cout << "[stree::populate_nidx_prim\n" ;
7713 
7714     int num_nd = nds.size();
7715     nidx_prim.resize(num_nd);
7716     std::fill( nidx_prim.begin(), nidx_prim.end(), -1 );
7717 
7718     int num_ridx = get_num_ridx() ;
7719     for(int ridx=0 ; ridx < num_ridx ; ridx++)
7720     {
7721         //char ridx_type = get_ridx_type(ridx) ;
7722         int q_repeat_index = ridx ;
7723 
7724         std::vector<snode> nodes_first ;
7725         std::vector<snode> nodes_all ;
7726 
7727         get_repeat_node(nodes_first, q_repeat_index,  0) ; //  0:first
7728         get_repeat_node(nodes_all,   q_repeat_index, -2) ; // -2:all
7729 
7730         int num_nodes_first = nodes_first.size();
7731         int num_nodes_all = nodes_all.size();
7732 
7733         // first repeat nodes should all have corresponding prim
7734         std::vector<int> gpi_first(num_nodes_first);
7735         for(int i=0 ; i < num_nodes_first ; i++)
7736         {
7737             int gpi = search_prim_for_nidx_first(nodes_first[i].index);
7738             assert( gpi > -1 );
7739             gpi_first[i] = gpi ;
7740         }
7741 
7742         int num_repeat = num_nodes_first > 0 ? num_nodes_all/num_nodes_first : 0  ;
7743         int all_modulo_first =  num_nodes_first > 0 ? num_nodes_all % num_nodes_first : -1 ;
7744 
7745         // hmm can getting the nodes in regular pattern be relied on
7746         // if so can use that pattern to pass the prim
7747         // from the first to all the repeats
7748 
7749         if(level > 1) std::cout
7750             << "-stree::populate_nidx_prim"
7751             << " ridx " << std::setw(2) << ridx
7752             << " num_nodes_first " << std::setw(8) << num_nodes_first
7753             << " num_nodes_all " << std::setw(8) << num_nodes_all
7754             << " num_repeat " << std::setw(6) << num_repeat
7755             << " all_modulo_first " << std::setw(5) << all_modulo_first
7756             << "\n"
7757             ;
7758 
7759         if(num_nodes_first == 0)
7760         {
7761             for(int i=0 ; i < num_nodes_all ; i++)
7762             {
7763                 int nidx = nodes_all[i].index ;
7764                 int gpi = search_prim_for_nidx_first(nidx);
7765                 nidx_prim[nidx] = gpi ;
7766             }
7767         }
7768         else if(num_nodes_first > 0)
7769         {
7770             for(int j=0 ; j < num_nodes_all ; j++)
7771             {
7772                 int i = j % num_nodes_first ;  // maybe ?
7773                 int gpi0 = gpi_first[i] ;
7774                 int nidx = nodes_all[j].index ;
7775                 nidx_prim[nidx] = gpi0 ;
7776             }
7777         }
7778     }
7779     if(level > 0) std::cout << "]stree::populate_nidx_prim\n" ;
7780 }
7781 
7782 
7783 inline void stree::check_nidx_prim() const
7784 {
7785     if(level > 0) std::cout << "[stree::check_nidx_prim\n" ;
7786     int num_nd = nds.size();
7787     int num_nidx_prim = nidx_prim.size();
7788     assert( num_nd == num_nidx_prim );
7789 
7790     for(int nidx=0 ; nidx < num_nd ; nidx++)
7791     {
7792         int gpi = nidx_prim[nidx] ;
7793         bool dump = level > 2 || gpi < 0 ;
7794         if(dump) std::cout
7795             << "-stree::check_nidx_prim"
7796             << " nidx " << std::setw(8) << nidx
7797             << " gpi(=nidx_prim[nidx]) " << std::setw(5) << gpi
7798             << "\n"
7799            ;
7800         assert(gpi > -1);
7801     }
7802     if(level > 0) std::cout << "]stree::check_nidx_prim\n" ;
7803 }
7804 
7805 
7806 /**
7807 stree::get_prim_for_nidx
7808 ---------------------------------
7809 
7810 Expected to give globalPrimIdx for all nidx,
7811 never giving -1 for valid nidx.
7812 
7813 **/
7814 
7815 inline int stree::get_prim_for_nidx(int nidx) const
7816 {
7817     return nidx < int(nidx_prim.size()) ? nidx_prim[nidx] : -1 ;
7818 }
7819 
7820 /**
7821 stree::get_nidx_for_prim
7822 -------------------------
7823 
7824 Expected to give nidx for all prim (aka globalPrimIdx)
7825 never giving -1 for valid prim.
7826 
7827 BUT: for nodes that are instanced many nidx share the same prim
7828 so this will provide the first nidx with the query prim.
7829 
7830 For global volumes the relationship between nidx and prim is 1:1
7831 for some prim and not for others.
7832 
7833 
7834 **/
7835 
7836 inline int stree::get_nidx_for_prim(int prim) const
7837 {
7838     return prim < int(prim_nidx.size()) ? prim_nidx[prim] : -1 ;
7839 }
7840 
7841 
7842 
7843 
7844 
7845 
7846 inline std::string stree::desc_prim() const
7847 {
7848     int num_pr = prim_nidx.size();
7849     std::stringstream ss ;
7850     ss << "[stree::desc_prim num_pr " << num_pr << "\n" ;
7851     for(int prim=0 ; prim < num_pr ; prim++) ss << desc_prim(prim) << "\n" ;
7852     ss << "]stree::desc_prim num_pr " << num_pr << "\n" ;
7853     std::string str = ss.str() ;
7854     return str ;
7855 }
7856 
7857 inline std::string stree::desc_prim(int prim) const
7858 {
7859     int nidx = get_nidx_for_prim(prim);
7860     const snode* nd = get_node( nidx );
7861     assert( nd );
7862 
7863     std::stringstream ss ;
7864     ss
7865          << " pr " << std::setw(5) << prim
7866          << " nx " << std::setw(7) << nidx
7867          << " nd [" << nd->desc() << "]"
7868          ;
7869 
7870     std::string str = ss.str() ;
7871     return str ;
7872 }
7873 
7874 
7875 
7876 
7877 /**
7878 stree::localize_photon_inplace
7879 --------------------------------
7880 
7881 Argument photon is assumed to be a copy of a global frame photon.
7882 This method transforms pos, mom, pol according to the transform
7883 looked up from the iindex.
7884 
7885 similar to SEvt::getLocalHit
7886 
7887 **/
7888 
7889 
7890 inline void stree::localize_photon_inplace( sphoton& p ) const
7891 {
7892     unsigned iindex   = p.iindex() ;
7893     assert( iindex != 0xffffffffu );
7894     const glm::tmat4x4<double>* tr = get_iinst(iindex) ;
7895     assert( tr );
7896 
7897     bool normalize = true ;
7898     p.transform( *tr, normalize );   // inplace transforms l (pos, mom, pol) into local frame
7899 
7900 #ifdef NDEBUG
7901 #else
7902     unsigned sensor_identifier = p.pmtid() ;
7903 
7904     glm::tvec4<int64_t> col3 = {} ;
7905     strid::Decode( *tr, col3 );
7906 
7907     sphit ht = {};
7908     ht.iindex            = col3[0] ;
7909     ht.sensor_identifier = col3[2] ;
7910     ht.sensor_index      = col3[3] ;
7911 
7912     assert( ht.iindex == iindex );
7913     assert( ht.sensor_identifier == sensor_identifier );
7914 #endif
7915 
7916 }
7917 
7918 
7919 
7920 
7921 
7922 
7923 /**
7924 stree::localize_photon  (formerly localize_hit, but its more general than that)
7925 ---------------------------------------------------------------------------------
7926 
7927 Canonically invoked from SEvt::save only when "hitlocal" save component is
7928 configured via see SEventConfig::HitComp
7929 
7930 Using the sphoton::iindex which should always be present for any real
7931 simulation (missed intersects have no iindex, but that should not happen
7932 with real geometry) lookup the transform allowing global coordinate pos, mom, pol
7933 to be inplace transformed into the local frame of the geometry
7934 of the part of the gometry that the photon last intersected.
7935 
7936 Note that the corresponding strid::Encode into the transform
7937 is done by the above stree::add_inst
7938 
7939 
7940 TODO : multiple-photon localization optimization
7941 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7942 
7943 GPU hit merging increases the usefulness of grouped localization
7944 as the hits will arrive sorted by (pmtid, timebucket) so in big events
7945 there will be many contiguous hits that all need the same transform
7946 
7947 sphoton::transform uses  Tran<double>::ApplyToFloat with count = 1, stride = 4*4
7948 arguments suggesting it may be quite straightforward to implement more efficient
7949 transformation just by finding the indices of transitions between pmtid
7950 
7951 **/
7952 
7953 inline NP* stree::localize_photon(const NP* photon, bool consistency_check ) const
7954 {
7955     if(!photon) return nullptr ;
7956     assert( photon->has_shape( -1, 4, 4 ) );
7957     size_t num = photon->shape[0] ;
7958 
7959     NP* local_photon = photon->copy();
7960     sphoton* ll = (sphoton*)local_photon->bytes() ;
7961 
7962     for(size_t i=0 ; i < num ; i++)
7963     {
7964         sphoton& l = ll[i] ; // start with global frame fields
7965         localize_photon_inplace(l);
7966 #ifdef NDEBUG
7967 #else
7968         if(consistency_check) transform_consistency_check(l);
7969 #endif
7970     }
7971     return local_photon ;
7972 }
7973 
7974 inline void stree::transform_consistency_check( const sphoton& l ) const
7975 {
7976     unsigned iindex   = l.iindex() ;
7977     const glm::tmat4x4<double>* tr = get_iinst(iindex) ;
7978     unsigned identity = l.get_identity();
7979     bool not_a_sensor = identity == 0 ;
7980     unsigned sensor_identifier = identity - 1 ;
7981 
7982     glm::tvec4<int64_t> col3 = {} ;
7983     strid::Decode( *tr, col3 );
7984 
7985     sphit ht = {} ;
7986     ht.iindex            = col3[0] ;
7987     ht.sensor_identifier = col3[2] ;
7988     ht.sensor_index      = col3[3] ;
7989 
7990     bool match_iindex = ht.iindex == iindex ;
7991     bool match_ident = ht.sensor_identifier == sensor_identifier ;
7992 
7993     if(!match_iindex) std::cerr
7994         << "stree::transform_consistency_check"
7995         << " ht.iindex " << ht.iindex
7996         << " iindex " << iindex
7997         << " match_iindex " << ( match_iindex ? "YES" : "NO " )
7998         << "\n"
7999         ;
8000 
8001     if(!match_ident) std::cerr
8002         << "stree::transform_consistency_check"
8003         << " ht.sensor_identifier " << ht.sensor_identifier
8004         << " sensor_identifier " << sensor_identifier
8005         << " match_ident " << ( match_ident ? "YES" : "NO " )
8006         << " identity " << identity
8007         << " not_a_sensor " << ( not_a_sensor ? "YES" : "NO " )
8008         << "\n"
8009         ;
8010 
8011     assert( match_iindex );
8012     assert( match_ident  );
8013 }
8014 
8015 
8016 
8017 inline void stree::create_photonlite_from_photon( sphotonlite& lite, const sphoton& p ) const
8018 {
8019     sphoton locp = p ;             // copy global frame input photon
8020     localize_photon_inplace(locp); // transform it to local frame using iindex
8021 
8022     lite.set_lpos( locp.get_cost(), locp.get_fphi() );
8023     lite.time = p.time ;
8024     lite.flagmask = p.flagmask ;
8025     lite.set_hitcount_identity( p.hitcount(), p.get_identity() );
8026 }
8027 
8028 inline NP* stree::create_photonlite_from_photon( const NP* photon ) const
8029 {
8030     if(photon == nullptr) return nullptr ;
8031 
8032     size_t ni = photon->num_items();
8033     assert( photon->has_shape( ni, 4, 4));
8034     sphoton* pp = (sphoton*)photon->bytes();
8035 
8036     NP* photonlite = sphotonlite::zeros(ni);
8037     assert( photonlite->has_shape(ni, 4) );
8038     sphotonlite* ll = (sphotonlite*)photonlite->bytes();
8039 
8040     for(size_t i=0 ; i < ni ; i++)
8041     {
8042        const sphoton& p = pp[i];
8043        sphotonlite& l = ll[i];
8044        create_photonlite_from_photon( l, p );
8045     }
8046     return photonlite ;
8047 }
8048 
8049 
8050