Project
Loading...
Searching...
No Matches
O2Tessellated.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12// Sandro Wenzel 2026
13
14// An implementation of TGeoTessellated augmented with efficient navigation functions.
15// Asked for integration into ROOT here https://github.com/root-project/root/pull/21045
16// Will be deleted once we get this from ROOT.
17
18#include <iostream>
19#include <sstream>
20
21#include "TGeoManager.h"
22#include "TGeoMatrix.h"
23#include "TGeoVolume.h"
24#include "TVirtualGeoPainter.h"
26#include "TBuffer3D.h"
27#include "TBuffer3DTypes.h"
28#include "TMath.h"
29#include "TBuffer.h"
30
31#include <array>
32#include <vector>
33
34// THIS IS THIRD PARTY CODE (TO BE PUT IN ROOT) WHICH DOES NOT NEED TO ADHERE TO OUR LINTING
35// NOLINTBEGIN
36
37// include the Third-party BVH headers
38#include "bvh2_third_party.h"
39// some kernels on top of BVH
40#include "bvh2_extra_kernels.h"
41
42#include <cmath>
43#include <limits>
44
45using namespace o2::base;
47
48using Vertex_t = Tessellated::Vertex_t;
49
52
53int TGeoFacet::CompactFacet(Vertex_t* vert, int nvertices)
54{
55 // Compact the common vertices and return new facet
56 if (nvertices < 2)
57 return nvertices;
58 int nvert = nvertices;
59 int i = 0;
60 while (i < nvert) {
61 if (vert[(i + 1) % nvert] == vert[i]) {
62 // shift last vertices left by one element
63 for (int j = i + 2; j < nvert; ++j)
64 vert[j - 1] = vert[j];
65 nvert--;
66 }
67 i++;
68 }
69 return nvert;
70}
71
74
75bool TGeoFacet::IsNeighbour(const TGeoFacet& other, bool& flip) const
76{
77
78 // Find a connecting segment
79 bool neighbour = false;
80 int line1[2], line2[2];
81 int npoints = 0;
82 for (int i = 0; i < fNvert; ++i) {
83 auto ivert = fIvert[i];
84 // Check if the other facet has the same vertex
85 for (int j = 0; j < other.GetNvert(); ++j) {
86 if (ivert == other[j]) {
87 line1[npoints] = i;
88 line2[npoints] = j;
89 if (++npoints == 2) {
90 neighbour = true;
91 bool order1 = line1[1] == line1[0] + 1;
92 bool order2 = line2[1] == (line2[0] + 1) % other.GetNvert();
93 flip = (order1 == order2);
94 return neighbour;
95 }
96 }
97 }
98 }
99 return neighbour;
100}
101
105
106O2Tessellated::O2Tessellated(const char* name, int nfacets) : TGeoBBox(name, 0, 0, 0)
107{
108 fNfacets = nfacets;
109 if (nfacets)
110 fFacets.reserve(nfacets);
111}
112
116
117O2Tessellated::O2Tessellated(const char* name, const std::vector<Vertex_t>& vertices) : TGeoBBox(name, 0, 0, 0)
118{
119 fVertices = vertices;
120 fNvert = fVertices.size();
121}
122
125
126O2Tessellated::O2Tessellated(TGeoTessellated const& tsl, bool check) : TGeoBBox(tsl.GetName(), 0, 0, 0)
127{
128 fNfacets = tsl.GetNfacets();
129 fNvert = tsl.GetNvertices();
130 fNseg = tsl.GetNsegments();
131
132 // copy facet and vertex done
133 fVertices.reserve(fNvert);
134 fFacets.reserve(fNfacets);
135 for (int i = 0; i < fNfacets; ++i) {
136 fFacets.push_back(tsl.GetFacet(i));
137 }
138 for (int i = 0; i < fNvert; ++i) {
139 fVertices.push_back(tsl.GetVertex(i));
140 }
141 // finish remaining structures
143}
144
147
149{
150 constexpr double tolerance = 1.e-10;
151 auto vertexHash = [&](Vertex_t const& vertex) {
152 // Compute hash for the vertex
153 long hash = 0;
154 // helper function to generate hash from integer numbers
155 auto hash_combine = [](long seed, const long value) {
156 return seed ^ (std::hash<long>{}(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2));
157 };
158 for (int i = 0; i < 3; i++) {
159 // use tolerance to generate int with the desired precision from a real number for hashing
160 hash = hash_combine(hash, std::roundl(vertex[i] / tolerance));
161 }
162 return hash;
163 };
164
165 auto hash = vertexHash(vert);
166 bool isAdded = false;
167 int ivert = -1;
168 // Get the compatible vertices
169 auto range = fVerticesMap.equal_range(hash);
170 for (auto it = range.first; it != range.second; ++it) {
171 ivert = it->second;
172 if (fVertices[ivert] == vert) {
173 isAdded = true;
174 break;
175 }
176 }
177 if (!isAdded) {
178 ivert = fVertices.size();
179 fVertices.push_back(vert);
180 fVerticesMap.insert(std::make_pair(hash, ivert));
181 }
182 return ivert;
183}
184
187
188bool O2Tessellated::AddFacet(const Vertex_t& pt0, const Vertex_t& pt1, const Vertex_t& pt2)
189{
190 if (fDefined) {
191 Error("AddFacet", "Shape %s already fully defined. Not adding", GetName());
192 return false;
193 }
194
195 Vertex_t vert[3];
196 vert[0] = pt0;
197 vert[1] = pt1;
198 vert[2] = pt2;
199 int nvert = TGeoFacet::CompactFacet(vert, 3);
200 if (nvert < 3) {
201 Error("AddFacet", "Triangular facet at index %d degenerated. Not adding.", GetNfacets());
202 return false;
203 }
204 int ind[3];
205 for (auto i = 0; i < 3; ++i)
206 ind[i] = AddVertex(vert[i]);
207 fNseg += 3;
208 fFacets.emplace_back(ind[0], ind[1], ind[2]);
209
210 return true;
211}
212
215
216bool O2Tessellated::AddFacet(int i0, int i1, int i2)
217{
218 if (fDefined) {
219 Error("AddFacet", "Shape %s already fully defined. Not adding", GetName());
220 return false;
221 }
222 if (fVertices.empty()) {
223 Error("AddFacet", "Shape %s Cannot add facets by indices without vertices. Not adding", GetName());
224 return false;
225 }
226
227 fNseg += 3;
228 fFacets.emplace_back(i0, i1, i2);
229 return true;
230}
231
234
235bool O2Tessellated::AddFacet(const Vertex_t& pt0, const Vertex_t& pt1, const Vertex_t& pt2, const Vertex_t& pt3)
236{
237 if (fDefined) {
238 Error("AddFacet", "Shape %s already fully defined. Not adding", GetName());
239 return false;
240 }
241 Vertex_t vert[4];
242 vert[0] = pt0;
243 vert[1] = pt1;
244 vert[2] = pt2;
245 vert[3] = pt3;
246 int nvert = TGeoFacet::CompactFacet(vert, 4);
247 if (nvert < 3) {
248 Error("AddFacet", "Quadrilateral facet at index %d degenerated. Not adding.", GetNfacets());
249 return false;
250 }
251
252 int ind[4];
253 for (auto i = 0; i < nvert; ++i)
254 ind[i] = AddVertex(vert[i]);
255 fNseg += nvert;
256 if (nvert == 3)
257 fFacets.emplace_back(ind[0], ind[1], ind[2]);
258 else
259 fFacets.emplace_back(ind[0], ind[1], ind[2], ind[3]);
260
261 if (fNfacets > 0 && GetNfacets() == fNfacets)
262 CloseShape(false);
263 return true;
264}
265
268
269bool O2Tessellated::AddFacet(int i0, int i1, int i2, int i3)
270{
271 if (fDefined) {
272 Error("AddFacet", "Shape %s already fully defined. Not adding", GetName());
273 return false;
274 }
275 if (fVertices.empty()) {
276 Error("AddFacet", "Shape %s Cannot add facets by indices without vertices. Not adding", GetName());
277 return false;
278 }
279
280 fNseg += 4;
281 fFacets.emplace_back(i0, i1, i2, i3);
282 return true;
283}
284
287
288Vertex_t O2Tessellated::FacetComputeNormal(int ifacet, bool& degenerated) const
289{
290 // Compute normal using non-zero segments
291 constexpr double kTolerance = 1.e-20;
292 auto const& facet = fFacets[ifacet];
293 int nvert = facet.GetNvert();
294 degenerated = true;
295 Vertex_t normal;
296 for (int i = 0; i < nvert - 1; ++i) {
297 Vertex_t e1 = fVertices[facet[i + 1]] - fVertices[facet[i]];
298 if (e1.Mag2() < kTolerance)
299 continue;
300 for (int j = i + 1; j < nvert; ++j) {
301 Vertex_t e2 = fVertices[facet[(j + 1) % nvert]] - fVertices[facet[j]];
302 if (e2.Mag2() < kTolerance)
303 continue;
304 normal = Vertex_t::Cross(e1, e2);
305 // e1 and e2 may be colinear
306 if (normal.Mag2() < kTolerance)
307 continue;
308 normal.Normalize();
309 degenerated = false;
310 break;
311 }
312 if (!degenerated)
313 break;
314 }
315 return normal;
316}
317
320
321bool O2Tessellated::FacetCheck(int ifacet) const
322{
323 constexpr double kTolerance = 1.e-10;
324 auto const& facet = fFacets[ifacet];
325 int nvert = facet.GetNvert();
326 bool degenerated = true;
327 FacetComputeNormal(ifacet, degenerated);
328 if (degenerated) {
329 std::cout << "Facet: " << ifacet << " is degenerated\n";
330 return false;
331 }
332
333 // Compute surface area
334 double surfaceArea = 0.;
335 for (int i = 1; i < nvert - 1; ++i) {
336 Vertex_t e1 = fVertices[facet[i]] - fVertices[facet[0]];
337 Vertex_t e2 = fVertices[facet[i + 1]] - fVertices[facet[0]];
338 surfaceArea += 0.5 * Vertex_t::Cross(e1, e2).Mag();
339 }
340 if (surfaceArea < kTolerance) {
341 std::cout << "Facet: " << ifacet << " has zero surface area\n";
342 return false;
343 }
344
345 return true;
346}
347
350
351void O2Tessellated::CloseShape(bool check, bool fixFlipped, bool verbose)
352{
353 if (fIsClosed && fBVH) {
354 return;
355 }
356 // Compute bounding box
357 fDefined = true;
358 fNvert = fVertices.size();
359 fNfacets = fFacets.size();
360 ComputeBBox();
361
362 BuildBVH();
363 if (fOutwardNormals.size() == 0) {
364 CalculateNormals();
365 } else {
366 // short check if the normal container is of correct size
367 if (fOutwardNormals.size() != fFacets.size()) {
368 std::cerr << "Inconsistency in normal container";
369 }
370 }
371 fIsClosed = true;
372
373 // Cleanup the vertex map
374 std::multimap<long, int>().swap(fVerticesMap);
375
376 if (fVertices.size() > 0) {
377 if (!check)
378 return;
379
380 // Check facets
381 for (auto i = 0; i < fNfacets; ++i)
382 FacetCheck(i);
383
384 fClosedBody = CheckClosure(fixFlipped, verbose);
385 }
386}
387
390
391bool O2Tessellated::CheckClosure(bool fixFlipped, bool verbose)
392{
393 int* nn = new int[fNfacets];
394 bool* flipped = new bool[fNfacets];
395 bool hasorphans = false;
396 bool hasflipped = false;
397 for (int i = 0; i < fNfacets; ++i) {
398 nn[i] = 0;
399 flipped[i] = false;
400 }
401
402 for (int icrt = 0; icrt < fNfacets; ++icrt) {
403 // all neighbours checked?
404 if (nn[icrt] >= fFacets[icrt].GetNvert())
405 continue;
406 for (int i = icrt + 1; i < fNfacets; ++i) {
407 bool isneighbour = fFacets[icrt].IsNeighbour(fFacets[i], flipped[i]);
408 if (isneighbour) {
409 if (flipped[icrt])
410 flipped[i] = !flipped[i];
411 if (flipped[i])
412 hasflipped = true;
413 nn[icrt]++;
414 nn[i]++;
415 if (nn[icrt] == fFacets[icrt].GetNvert())
416 break;
417 }
418 }
419 if (nn[icrt] < fFacets[icrt].GetNvert())
420 hasorphans = true;
421 }
422
423 if (hasorphans && verbose) {
424 Error("Check", "Tessellated solid %s has following not fully connected facets:", GetName());
425 for (int icrt = 0; icrt < fNfacets; ++icrt) {
426 if (nn[icrt] < fFacets[icrt].GetNvert())
427 std::cout << icrt << " (" << fFacets[icrt].GetNvert() << " edges, " << nn[icrt] << " neighbours)\n";
428 }
429 }
430 fClosedBody = !hasorphans;
431 int nfixed = 0;
432 if (hasflipped) {
433 if (verbose)
434 Warning("Check", "Tessellated solid %s has following facets with flipped normals:", GetName());
435 for (int icrt = 0; icrt < fNfacets; ++icrt) {
436 if (flipped[icrt]) {
437 if (verbose)
438 std::cout << icrt << "\n";
439 if (fixFlipped) {
440 fFacets[icrt].Flip();
441 nfixed++;
442 }
443 }
444 }
445 if (nfixed && verbose)
446 Info("Check", "Automatically flipped %d facets to match first defined facet", nfixed);
447 }
448 delete[] nn;
449 delete[] flipped;
450
451 return !hasorphans;
452}
453
456
458{
459 const double kBig = TGeoShape::Big();
460 double vmin[3] = {kBig, kBig, kBig};
461 double vmax[3] = {-kBig, -kBig, -kBig};
462 for (const auto& facet : fFacets) {
463 for (int i = 0; i < facet.GetNvert(); ++i) {
464 for (int j = 0; j < 3; ++j) {
465 vmin[j] = TMath::Min(vmin[j], fVertices[facet[i]].operator[](j));
466 vmax[j] = TMath::Max(vmax[j], fVertices[facet[i]].operator[](j));
467 }
468 }
469 }
470 fDX = 0.5 * (vmax[0] - vmin[0]);
471 fDY = 0.5 * (vmax[1] - vmin[1]);
472 fDZ = 0.5 * (vmax[2] - vmin[2]);
473 for (int i = 0; i < 3; ++i)
474 fOrigin[i] = 0.5 * (vmax[i] + vmin[i]);
475}
476
479
480void O2Tessellated::GetMeshNumbers(int& nvert, int& nsegs, int& npols) const
481{
482 nvert = fNvert;
483 nsegs = fNseg;
484 npols = GetNfacets();
485}
486
489
490Bool_t O2Tessellated::GetPointsOnSegments(Int_t npoints, Double_t* array) const
491{
492 if (array == nullptr || npoints <= 0 || fVertices.empty()) {
493 return kFALSE;
494 }
495 const int vertexCount = static_cast<int>(fVertices.size());
496 if (npoints < vertexCount) {
497 // Hand the caller back to SetPoints(), which gives it every vertex -- more points than asked
498 // for, all of them exactly on the shape.
499 return kFALSE;
500 }
501 for (int vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) {
502 fVertices[vertexIndex].CopyTo(&array[3 * vertexIndex]);
503 }
504
505 const int extraCount = npoints - vertexCount;
506 const int facetCount = static_cast<int>(fFacets.size());
507 if (extraCount == 0) {
508 return kTRUE;
509 }
510 if (facetCount == 0) {
511 for (int extraIndex = 0; extraIndex < extraCount; ++extraIndex) {
512 fVertices[extraIndex % vertexCount].CopyTo(&array[3 * (vertexCount + extraIndex)]);
513 }
514 return kTRUE;
515 }
516
517 // The same deterministic R2 low-discrepancy pair O2BVHSurfaceSolid::GetPointsOnSegments uses:
518 // what a shape hands out must depend on the shape and on nothing else.
519 constexpr double kAlpha1 = 0.7548776662466927;
520 constexpr double kAlpha2 = 0.5698402909980532;
521 for (int extraIndex = 0; extraIndex < extraCount; ++extraIndex) {
522 const int facetIndex =
523 static_cast<int>((static_cast<long long>(extraIndex) * facetCount) / extraCount) % facetCount;
524 const TGeoFacet& facet = fFacets[facetIndex];
525 const int facetVertices = facet.GetNvert();
526 double first = std::fmod(0.5 + kAlpha1 * (extraIndex + 1), 1.);
527 double second = std::fmod(0.5 + kAlpha2 * (extraIndex + 1), 1.);
528 if (first + second > 1.) {
529 first = 1. - first;
530 second = 1. - second;
531 }
532 // A quad facet is two triangles sharing vertex 0; pick one by the parity of the sample index
533 // so both halves are covered.
534 const int cornerB = (facetVertices > 3 && (extraIndex & 1)) ? 2 : 1;
535 const int cornerC = (facetVertices > 3 && (extraIndex & 1)) ? 3 : ((facetVertices > 2) ? 2 : 1);
536 const Vertex_t& vertexA = fVertices[facet[0]];
537 const Vertex_t& vertexB = fVertices[facet[cornerB]];
538 const Vertex_t& vertexC = fVertices[facet[cornerC]];
539 const double weightA = 1. - first - second;
540 double* slot = &array[3 * (vertexCount + extraIndex)];
541 slot[0] = weightA * vertexA.x() + first * vertexB.x() + second * vertexC.x();
542 slot[1] = weightA * vertexA.y() + first * vertexB.y() + second * vertexC.y();
543 slot[2] = weightA * vertexA.z() + first * vertexB.z() + second * vertexC.z();
544 }
545 return kTRUE;
546}
547
551
553{
554 const int nvert = fNvert;
555 const int nsegs = fNseg;
556 const int npols = GetNfacets();
557 auto buff = new TBuffer3D(TBuffer3DTypes::kGeneric, nvert, 3 * nvert, nsegs, 3 * nsegs, npols, 6 * npols);
558 if (buff) {
559 SetPoints(buff->fPnts);
560 SetSegsAndPols(*buff);
561 }
562 return buff;
563}
564
567
568void O2Tessellated::Print(Option_t*) const
569{
570 std::cout << "=== Tessellated shape " << GetName() << " having " << GetNvertices() << " vertices and "
571 << GetNfacets() << " facets\n";
572}
573
576
577void O2Tessellated::SetSegsAndPols(TBuffer3D& buff) const
578{
579 const int c = GetBasicColor();
580 int* segs = buff.fSegs;
581 int* pols = buff.fPols;
582
583 int indseg = 0; // segment internal data index
584 int indpol = 0; // polygon internal data index
585 int sind = 0; // segment index
586 for (const auto& facet : fFacets) {
587 auto nvert = facet.GetNvert();
588 pols[indpol++] = c;
589 pols[indpol++] = nvert;
590 for (auto j = 0; j < nvert; ++j) {
591 int k = (j + 1) % nvert;
592 // segment made by next consecutive points
593 segs[indseg++] = c;
594 segs[indseg++] = facet[j];
595 segs[indseg++] = facet[k];
596 // add segment to current polygon and increment segment index
597 pols[indpol + nvert - j - 1] = sind++;
598 }
599 indpol += nvert;
600 }
601}
602
605
606void O2Tessellated::SetPoints(double* points) const
607{
608 int ind = 0;
609 for (const auto& vertex : fVertices) {
610 vertex.CopyTo(&points[ind]);
611 ind += 3;
612 }
613}
614
617
619{
620 int ind = 0;
621 for (const auto& vertex : fVertices) {
622 points[ind++] = vertex.x();
623 points[ind++] = vertex.y();
624 points[ind++] = vertex.z();
625 }
626}
627
630
631void O2Tessellated::ResizeCenter(double maxsize)
632{
633 using Vector3_t = Vertex_t;
634
635 if (!fDefined) {
636 Error("ResizeCenter", "Not all faces are defined");
637 return;
638 }
639 Vector3_t origin(fOrigin[0], fOrigin[1], fOrigin[2]);
640 double maxedge = TMath::Max(TMath::Max(fDX, fDY), fDZ);
641 double scale = maxsize / maxedge;
642 for (size_t i = 0; i < fVertices.size(); ++i) {
643 fVertices[i] = scale * (fVertices[i] - origin);
644 }
645 fOrigin[0] = fOrigin[1] = fOrigin[2] = 0;
646 fDX *= scale;
647 fDY *= scale;
648 fDZ *= scale;
649}
650
653
654const TBuffer3D& O2Tessellated::GetBuffer3D(int reqSections, Bool_t localFrame) const
655{
656 static TBuffer3D buffer(TBuffer3DTypes::kGeneric);
657
658 FillBuffer3D(buffer, reqSections, localFrame);
659
660 const int nvert = fNvert;
661 const int nsegs = fNseg;
662 const int npols = GetNfacets();
663
664 if (reqSections & TBuffer3D::kRawSizes) {
665 if (buffer.SetRawSizes(nvert, 3 * nvert, nsegs, 3 * nsegs, npols, 6 * npols)) {
666 buffer.SetSectionsValid(TBuffer3D::kRawSizes);
667 }
668 }
669 if ((reqSections & TBuffer3D::kRaw) && buffer.SectionsValid(TBuffer3D::kRawSizes)) {
670 SetPoints(buffer.fPnts);
671 if (!buffer.fLocalFrame) {
672 TransformPoints(buffer.fPnts, buffer.NbPnts());
673 }
674
676 buffer.SetSectionsValid(TBuffer3D::kRaw);
677 }
678
679 return buffer;
680}
681
684
685O2Tessellated* O2Tessellated::ImportFromObjFormat(const char* objfile, bool check, bool verbose)
686{
687 using std::vector, std::string, std::ifstream, std::stringstream, std::endl;
688
689 vector<Vertex_t> vertices;
690 vector<string> sfacets;
691
692 struct FacetInd_t {
693 int i0 = -1;
694 int i1 = -1;
695 int i2 = -1;
696 int i3 = -1;
697 int nvert = 0;
698 FacetInd_t(int a, int b, int c)
699 {
700 i0 = a;
701 i1 = b;
702 i2 = c;
703 nvert = 3;
704 };
705 FacetInd_t(int a, int b, int c, int d)
706 {
707 i0 = a;
708 i1 = b;
709 i2 = c;
710 i3 = d;
711 nvert = 4;
712 };
713 };
714
715 vector<FacetInd_t> facets;
716 // List of geometric vertices, with (x, y, z [,w]) coordinates, w is optional and defaults to 1.0.
717 // struct vtx_t { double x = 0; double y = 0; double z = 0; double w = 1; };
718
719 // Texture coordinates in u, [,v ,w]) coordinates, these will vary between 0 and 1. v, w are optional and default to
720 // 0.
721 // struct tex_t { double u; double v; double w; };
722
723 // List of vertex normals in (x,y,z) form; normals might not be unit vectors.
724 // struct vn_t { double x; double y; double z; };
725
726 // Parameter space vertices in ( u [,v] [,w] ) form; free form geometry statement
727 // struct vp_t { double u; double v; double w; };
728
729 // Faces are defined using lists of vertex, texture and normal indices which start at 1.
730 // Polygons such as quadrilaterals can be defined by using more than three vertex/texture/normal indices.
731 // f v1//vn1 v2//vn2 v3//vn3 ...
732
733 // Records starting with the letter "l" specify the order of the vertices which build a polyline.
734 // l v1 v2 v3 v4 v5 v6 ...
735
736 string line;
737 int ind[4] = {0};
738 ifstream file(objfile);
739 if (!file.is_open()) {
740 ::Error("O2Tessellated::ImportFromObjFormat", "Unable to open %s", objfile);
741 return nullptr;
742 }
743
744 while (getline(file, line)) {
745 stringstream ss(line);
746 string tag;
747
748 // We ignore everything which is not a vertex or a face
749 if (line.rfind('v', 0) == 0 && line.rfind("vt", 0) != 0 && line.rfind("vn", 0) != 0 && line.rfind("vn", 0) != 0) {
750 // Decode the vertex
751 double pos[4] = {0, 0, 0, 1};
752 ss >> tag >> pos[0] >> pos[1] >> pos[2] >> pos[3];
753 vertices.emplace_back(pos[0] * pos[3], pos[1] * pos[3], pos[2] * pos[3]);
754 }
755
756 else if (line.rfind('f', 0) == 0) {
757 // Decode the face
758 ss >> tag;
759 string word;
760 sfacets.clear();
761 while (ss >> word)
762 sfacets.push_back(word);
763 if (sfacets.size() > 4 || sfacets.size() < 3) {
764 ::Error("O2Tessellated::ImportFromObjFormat", "Detected face having unsupported %zu vertices",
765 sfacets.size());
766 return nullptr;
767 }
768 int nvert = 0;
769 for (auto& sword : sfacets) {
770 stringstream ssword(sword);
771 string token;
772 getline(ssword, token, '/'); // just need the vertex index, which is the first token
773 // Convert string token to integer
774
775 ind[nvert++] = stoi(token) - 1;
776 if (ind[nvert - 1] < 0) {
777 ::Error("O2Tessellated::ImportFromObjFormat", "Unsupported relative vertex index definition in %s",
778 objfile);
779 return nullptr;
780 }
781 }
782 if (nvert == 3)
783 facets.emplace_back(ind[0], ind[1], ind[2]);
784 else
785 facets.emplace_back(ind[0], ind[1], ind[2], ind[3]);
786 }
787 }
788
789 int nvertices = (int)vertices.size();
790 int nfacets = (int)facets.size();
791 if (nfacets < 3) {
792 ::Error("O2Tessellated::ImportFromObjFormat", "Not enough faces detected in %s", objfile);
793 return nullptr;
794 }
795
796 string sobjfile(objfile);
797 if (verbose)
798 std::cout << "Read " << nvertices << " vertices and " << nfacets << " facets from " << sobjfile << endl;
799
800 auto tsl = new O2Tessellated(sobjfile.erase(sobjfile.find_last_of('.')).c_str(), vertices);
801
802 for (int i = 0; i < nfacets; ++i) {
803 auto facet = facets[i];
804 if (facet.nvert == 3)
805 tsl->AddFacet(facet.i0, facet.i1, facet.i2);
806 else
807 tsl->AddFacet(facet.i0, facet.i1, facet.i2, facet.i3);
808 }
809 tsl->CloseShape(check, true, verbose);
810 tsl->Print();
811 return tsl;
812}
813
814// implementation of some geometry helper functions in anonymous namespace
815namespace
816{
817
818using Vertex_t = Tessellated::Vertex_t;
819// The classic Moeller-Trumbore ray triangle-intersection kernel:
820// - Compute triangle edges e1, e2
821// - Compute determinant det
822// - Reject parallel rays
823// - Compute barycentric coordinates u, v
824// - Compute ray parameter t
825double rayTriangle(const Vertex_t& orig, const Vertex_t& dir, const Vertex_t& v0, const Vertex_t& v1,
826 const Vertex_t& v2, double rayEPS = 1e-8)
827{
828 constexpr double EPS = 1e-8;
829 const double INF = std::numeric_limits<double>::infinity();
830 Vertex_t e1{v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]};
831 Vertex_t e2{v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]};
832 auto p = Vertex_t::Cross(dir, e2);
833 auto det = e1.Dot(p);
834 if (std::abs(det) <= EPS) {
835 return INF;
836 }
837
838 Vertex_t tvec{orig[0] - v0[0], orig[1] - v0[1], orig[2] - v0[2]};
839 auto invDet = 1.0 / det;
840 auto u = tvec.Dot(p) * invDet;
841 if (u < 0.0 || u > 1.0) {
842 return INF;
843 }
844 auto q = Vertex_t::Cross(tvec, e1);
845 auto v = dir.Dot(q) * invDet;
846 if (v < 0.0 || u + v > 1.0) {
847 return INF;
848 }
849 auto t = e2.Dot(q) * invDet;
850 return (t > rayEPS) ? t : INF;
851}
852
853template <typename T = float>
854struct Vec3f {
855 T x, y, z;
856};
857
858template <typename T>
859inline Vec3f<T> operator-(const Vec3f<T>& a, const Vec3f<T>& b)
860{
861 return {a.x - b.x, a.y - b.y, a.z - b.z};
862}
863
864template <typename T>
865inline Vec3f<T> cross(const Vec3f<T>& a, const Vec3f<T>& b)
866{
867 return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
868}
869
870template <typename T>
871inline T dot(const Vec3f<T>& a, const Vec3f<T>& b)
872{
873 return a.x * b.x + a.y * b.y + a.z * b.z;
874}
875
876// Kernel to get closest/shortest distance between a point and a triangl (a,b,c).
877// Performed by default in float since Safety can be approximate.
878// Project point onto triangle plane
879// If projection lies inside → distance to plane
880// Otherwise compute min distance to the three edges
881// Return squared distance
882template <typename T = float>
883T pointTriangleDistSq(const Vec3f<T>& p, const Vec3f<T>& a, const Vec3f<T>& b, const Vec3f<T>& c)
884{
885 // Edges
886 Vec3f<T> ab = b - a;
887 Vec3f<T> ac = c - a;
888 Vec3f<T> ap = p - a;
889
890 auto d1 = dot(ab, ap);
891 auto d2 = dot(ac, ap);
892 if (d1 <= T(0.0) && d2 <= T(0.0)) {
893 return dot(ap, ap); // barycentric (1,0,0)
894 }
895
896 Vec3f<T> bp = p - b;
897 auto d3 = dot(ab, bp);
898 auto d4 = dot(ac, bp);
899 if (d3 >= T(0.0) && d4 <= d3) {
900 return dot(bp, bp); // (0,1,0)
901 }
902
903 T vc = d1 * d4 - d3 * d2;
904 if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) {
905 T v = d1 / (d1 - d3);
906 Vec3f<T> proj = {a.x + v * ab.x, a.y + v * ab.y, a.z + v * ab.z};
907 Vec3f<T> d = p - proj;
908 return dot(d, d); // edge AB
909 }
910
911 Vec3f<T> cp = p - c;
912 T d5 = dot(ab, cp);
913 T d6 = dot(ac, cp);
914 if (d6 >= T(0.0f) && d5 <= d6) {
915 return dot(cp, cp); // (0,0,1)
916 }
917
918 T vb = d5 * d2 - d1 * d6;
919 if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) {
920 T w = d2 / (d2 - d6);
921 Vec3f<T> proj = {a.x + w * ac.x, a.y + w * ac.y, a.z + w * ac.z};
922 Vec3f<T> d = p - proj;
923 return dot(d, d); // edge AC
924 }
925
926 T va = d3 * d6 - d5 * d4;
927 if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) {
928 T w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
929 Vec3f<T> proj = {b.x + w * (c.x - b.x), b.y + w * (c.y - b.y), b.z + w * (c.z - b.z)};
930 Vec3f<T> d = p - proj;
931 return dot(d, d); // edge BC
932 }
933
934 // Inside face region
935 T denom = T(1.0f) / (va + vb + vc);
936 T v = vb * denom;
937 T w = vc * denom;
938
939 Vec3f<T> proj = {a.x + ab.x * v + ac.x * w, a.y + ab.y * v + ac.y * w, a.z + ab.z * v + ac.z * w};
940
941 Vec3f<T> d = p - proj;
942 return dot(d, d);
943}
944
945template <typename T>
946inline Vec3f<T> normalize(const Vec3f<T>& v)
947{
948 T len2 = dot(v, v);
949 if (len2 == T(0.0f)) {
950 std::cerr << "Degnerate triangle. Cannot determine normal";
951 return {0, 0, 0};
952 }
953 T invLen = T(1.0f) / std::sqrt(len2);
954 return {v.x * invLen, v.y * invLen, v.z * invLen};
955}
956
957template <typename T>
958inline Vec3f<T> triangleNormal(const Vec3f<T>& a, const Vec3f<T>& b, const Vec3f<T>& c)
959{
960 const Vec3f<T> e1 = b - a;
961 const Vec3f<T> e2 = c - a;
962 return normalize(cross(e1, e2));
963}
964
966constexpr float kFacetBoxPad = 0.001f;
967
970constexpr double kMaxPruneScale = kFacetBoxPad * (1 << 24) / 8.;
971
973template <typename BBox>
974double pruneLimit(const BBox& bbox, const double* point)
975{
976 double origin = 0.;
977 double box = 0.;
978 for (int index = 0; index < 3; ++index) {
979 origin = std::max(origin, std::abs(point[index]));
980 box = std::max({box, std::abs(static_cast<double>(bbox.min[index])),
981 std::abs(static_cast<double>(bbox.max[index]))});
982 }
983 return kMaxPruneScale - origin - box;
984}
985
986} // end anonymous namespace
987
990
991Double_t O2Tessellated::DistFromOutside(const Double_t* point, const Double_t* dir, Int_t /*iact*/, Double_t stepmax,
992 Double_t* /*safe*/) const
993{
994 // use the BVH intersector in combination with leaf ray-triangle testing
995 double local_step = Big(); // we need this otherwise the lambda get's confused
996
997 using Scalar = float;
998 using Vec3 = bvh::v2::Vec<Scalar, 3>;
999 using Node = bvh::v2::Node<Scalar, 3>;
1000 using Bvh = bvh::v2::Bvh<Node>;
1001 using Ray = bvh::v2::Ray<Scalar, 3>;
1002
1003 // let's fetch the bvh
1004 auto mybvh = (Bvh*)fBVH;
1005 if (!mybvh) {
1006 assert(false);
1007 return -1.;
1008 }
1009
1010 auto truncate_roundup = [](double orig) {
1011 float epsilon = std::numeric_limits<float>::epsilon() * std::fabs(orig);
1012 // Add the bias to x before assigning it to y
1013 return static_cast<float>(orig + epsilon);
1014 };
1015
1016 // let's do very quick checks against the top node
1017 const auto topnode_bbox = mybvh->get_root().get_bbox();
1018 if ((-point[0] + topnode_bbox.min[0]) > stepmax) {
1019 return Big();
1020 }
1021 if ((-point[1] + topnode_bbox.min[1]) > stepmax) {
1022 return Big();
1023 }
1024 if ((-point[2] + topnode_bbox.min[2]) > stepmax) {
1025 return Big();
1026 }
1027 if ((point[0] - topnode_bbox.max[0]) > stepmax) {
1028 return Big();
1029 }
1030 if ((point[1] - topnode_bbox.max[1]) > stepmax) {
1031 return Big();
1032 }
1033 if ((point[2] - topnode_bbox.max[2]) > stepmax) {
1034 return Big();
1035 }
1036
1037 // the ray used for bvh interaction
1038 Ray ray(Vec3(point[0], point[1], point[2]), // origin
1039 Vec3(dir[0], dir[1], dir[2]), // direction
1040 0.0f, // minimum distance (could give stepmax ?)
1041 truncate_roundup(local_step));
1042
1043 static constexpr bool use_robust_traversal = true;
1044
1045 // the ray object is ours and mutable: bvh2 re-reads tmax at every box test, so lowering it on a
1046 // hit prunes the rest of the traversal
1047 const double prune_limit = pruneLimit(topnode_bbox, point);
1048
1049 Vertex_t dir_v{dir[0], dir[1], dir[2]};
1050 // Traverse the BVH and apply concrete object intersection in BVH leafs
1051 bvh::v2::GrowingStack<Bvh::Index> stack;
1052 mybvh->intersect<false, use_robust_traversal>(ray, mybvh->get_root().index, stack, [&](size_t begin, size_t end) {
1053 for (size_t prim_id = begin; prim_id < end; ++prim_id) {
1054 auto objectid = mybvh->prim_ids[prim_id];
1055 const auto& facet = fFacets[objectid];
1056 const auto& n = fOutwardNormals[objectid];
1057
1058 // quick normal test. Coming from outside, the dot product must be negative
1059 if (n.Dot(dir_v) > 0.) {
1060 continue;
1061 }
1062
1063 auto thisdist = rayTriangle(Vertex_t(point[0], point[1], point[2]), dir_v,
1064 fVertices[facet[0]], fVertices[facet[1]], fVertices[facet[2]], 0.);
1065
1066 if (thisdist < local_step) {
1067 local_step = thisdist;
1068 if (local_step <= prune_limit) {
1069 ray.tmax = truncate_roundup(local_step);
1070 }
1071 }
1072 }
1073 return false; // go on after this
1074 });
1075
1076 return local_step;
1077}
1078
1081
1082Double_t O2Tessellated::DistFromInside(const Double_t* point, const Double_t* dir, Int_t /*iact*/, Double_t /*stepmax*/,
1083 Double_t* /*safe*/) const
1084{
1085 // use the BVH intersector in combination with leaf ray-triangle testing
1086 double local_step = Big(); // we need this otherwise the lambda get's confused
1087
1088 using Scalar = float;
1089 using Vec3 = bvh::v2::Vec<Scalar, 3>;
1090 using Node = bvh::v2::Node<Scalar, 3>;
1091 using Bvh = bvh::v2::Bvh<Node>;
1092 using Ray = bvh::v2::Ray<Scalar, 3>;
1093
1094 // let's fetch the bvh
1095 auto mybvh = (Bvh*)fBVH;
1096 if (!mybvh) {
1097 assert(false);
1098 return -1.;
1099 }
1100
1101 auto truncate_roundup = [](double orig) {
1102 float epsilon = std::numeric_limits<float>::epsilon() * std::fabs(orig);
1103 // Add the bias to x before assigning it to y
1104 return static_cast<float>(orig + epsilon);
1105 };
1106
1107 // the ray used for bvh interaction
1108 Ray ray(Vec3(point[0], point[1], point[2]), // origin
1109 Vec3(dir[0], dir[1], dir[2]), // direction
1110 0., // minimum distance (could give stepmax ?)
1111 truncate_roundup(local_step));
1112
1113 static constexpr bool use_robust_traversal = true;
1114
1115 // as in DistFromOutside: lowering the ray's own tmax on a hit prunes the rest of the traversal
1116 const auto rootbox = mybvh->get_root().get_bbox();
1117 const double prune_limit = pruneLimit(rootbox, point);
1118
1119 Vertex_t dir_v{dir[0], dir[1], dir[2]};
1120 // Traverse the BVH and apply concrete object intersection in BVH leafs
1121 bvh::v2::GrowingStack<Bvh::Index> stack;
1122 mybvh->intersect<false, use_robust_traversal>(ray, mybvh->get_root().index, stack, [&](size_t begin, size_t end) {
1123 for (size_t prim_id = begin; prim_id < end; ++prim_id) {
1124 auto objectid = mybvh->prim_ids[prim_id];
1125 auto facet = fFacets[objectid];
1126 const auto& n = fOutwardNormals[objectid];
1127
1128 // Only exiting surfaces are relevant (from inside--> dot product must be positive)
1129 if (n.Dot(dir_v) <= 0.) {
1130 continue;
1131 }
1132
1133 const auto& v0 = fVertices[facet[0]];
1134 const auto& v1 = fVertices[facet[1]];
1135 const auto& v2 = fVertices[facet[2]];
1136
1137 const double t =
1138 rayTriangle(Vertex_t{point[0], point[1], point[2]}, dir_v, v0, v1, v2, 0.);
1139 if (t < local_step) {
1140 local_step = t;
1141 if (local_step <= prune_limit) {
1142 ray.tmax = truncate_roundup(local_step);
1143 }
1144 }
1145 }
1146 return false; // go on after this
1147 });
1148
1149 return local_step;
1150}
1151
1154
1156{
1157 // For explanation of the following algorithm see:
1158 // https://en.wikipedia.org/wiki/Polyhedron#Volume
1159 // http://wwwf.imperial.ac.uk/~rn/centroid.pdf
1160
1161 double vol = 0.0;
1162 for (size_t i = 0; i < fFacets.size(); ++i) {
1163 auto& facet = fFacets[i];
1164 auto a = fVertices[facet[0]];
1165 auto b = fVertices[facet[1]];
1166 auto c = fVertices[facet[2]];
1167 vol +=
1168 a[0] * (b[1] * c[2] - b[2] * c[1]) + b[0] * (c[1] * a[2] - c[2] * a[1]) + c[0] * (a[1] * b[2] - a[2] * b[1]);
1169 }
1170 return vol / 6.0;
1171}
1172
1175
1176void O2Tessellated::BuildBVH()
1177{
1178 using Scalar = float;
1179 using BBox = bvh::v2::BBox<Scalar, 3>;
1180 using Vec3 = bvh::v2::Vec<Scalar, 3>;
1181 using Node = bvh::v2::Node<Scalar, 3>;
1182 using Bvh = bvh::v2::Bvh<Node>;
1183
1184 // helper determining axis aligned bounding box from a facet;
1185 auto GetBoundingBox = [this](TGeoFacet const& facet) {
1186#ifndef NDEBUG
1187 const auto nvertices = facet.GetNvert();
1188 assert(nvertices == 3); // for now only triangles
1189#endif
1190 const auto& v1 = fVertices[facet[0]];
1191 const auto& v2 = fVertices[facet[1]];
1192 const auto& v3 = fVertices[facet[2]];
1193 BBox bbox;
1194 bbox.min[0] = std::min(std::min(v1[0], v2[0]), v3[0]) - kFacetBoxPad;
1195 bbox.min[1] = std::min(std::min(v1[1], v2[1]), v3[1]) - kFacetBoxPad;
1196 bbox.min[2] = std::min(std::min(v1[2], v2[2]), v3[2]) - kFacetBoxPad;
1197 bbox.max[0] = std::max(std::max(v1[0], v2[0]), v3[0]) + kFacetBoxPad;
1198 bbox.max[1] = std::max(std::max(v1[1], v2[1]), v3[1]) + kFacetBoxPad;
1199 bbox.max[2] = std::max(std::max(v1[2], v2[2]), v3[2]) + kFacetBoxPad;
1200 return bbox;
1201 };
1202
1203 // we need bounding boxes enclosing the primitives and centers of primitives
1204 // (replaced here by centers of bounding boxes) to build the bvh
1205 std::vector<BBox> bboxes;
1206 std::vector<Vec3> centers;
1207
1208 // loop over all the triangles/Facets;
1209 int nd = fFacets.size();
1210 for (int i = 0; i < nd; ++i) {
1211 auto& facet = fFacets[i];
1212
1213 // fetch the bounding box of this node and add to the vector of bounding boxes
1214 (bboxes).push_back(GetBoundingBox(facet));
1215 centers.emplace_back((bboxes).back().get_center());
1216 }
1217
1218 // check if some previous object is registered and delete if necessary
1219 if (fBVH) {
1220 delete (Bvh*)fBVH;
1221 fBVH = nullptr;
1222 }
1223
1224 // create the bvh
1225 typename bvh::v2::DefaultBuilder<Node>::Config config;
1226 config.quality = bvh::v2::DefaultBuilder<Node>::Quality::High;
1227 auto bvh = bvh::v2::DefaultBuilder<Node>::build(bboxes, centers, config);
1228 auto bvhptr = new Bvh;
1229 *bvhptr = std::move(bvh); // copy structure
1230 fBVH = (void*)(bvhptr);
1231
1232 return;
1233}
1234
1237
1238bool O2Tessellated::Contains(Double_t const* point) const
1239{
1240 // we do the parity test
1241 using Scalar = float;
1242 using Vec3 = bvh::v2::Vec<Scalar, 3>;
1243 using Node = bvh::v2::Node<Scalar, 3>;
1244 using Bvh = bvh::v2::Bvh<Node>;
1245 using Ray = bvh::v2::Ray<Scalar, 3>;
1246
1247 // let's fetch the bvh
1248 auto mybvh = (Bvh*)fBVH;
1249 if (!mybvh) {
1250 assert(false);
1251 return false;
1252 }
1253
1254 auto truncate_roundup = [](double orig) {
1255 float epsilon = std::numeric_limits<float>::epsilon() * std::fabs(orig);
1256 // Add the bias to x before assigning it to y
1257 return static_cast<float>(orig + epsilon);
1258 };
1259
1260 // let's do very quick checks against the top node
1261 if (!TGeoBBox::Contains(point)) {
1262 return false;
1263 }
1264
1265 // An arbitrary test direction.
1266 // Doesn't need to be normalized and probes all normals. Also ensuring to be skewed somewhat
1267 // without evident symmetries.
1268 Vertex_t test_dir{1.0, 1.41421356237, 1.73205080757};
1269
1270 double local_step = Big();
1271 // the ray used for bvh interaction
1272 Ray ray(Vec3(point[0], point[1], point[2]), // origin
1273 Vec3(test_dir[0], test_dir[1], test_dir[2]), // direction
1274 0.0f, // minimum distance (could give stepmax ?)
1275 truncate_roundup(local_step));
1276
1277 static constexpr bool use_robust_traversal = true;
1278
1279 // Traverse the BVH and apply concrete object intersection in BVH leafs
1280 bvh::v2::GrowingStack<Bvh::Index> stack;
1281 size_t crossings = 0;
1282 mybvh->intersect<false, use_robust_traversal>(ray, mybvh->get_root().index, stack, [&](size_t begin, size_t end) {
1283 for (size_t prim_id = begin; prim_id < end; ++prim_id) {
1284 auto objectid = mybvh->prim_ids[prim_id];
1285 auto& facet = fFacets[objectid];
1286
1287 // for the parity test, we probe all crossing surfaces
1288 const auto& v0 = fVertices[facet[0]];
1289 const auto& v1 = fVertices[facet[1]];
1290 const auto& v2 = fVertices[facet[2]];
1291
1292 const double t = rayTriangle(Vertex_t(point[0], point[1], point[2]),
1293 test_dir, v0, v1, v2, 0.);
1294
1295 if (t != std::numeric_limits<double>::infinity()) {
1296 ++crossings;
1297 }
1298 }
1299 return false;
1300 });
1301
1302 return crossings & 1;
1303}
1304
1305namespace
1306{
1307
1308// Helper classes/structs used for priority queue - BVH traversal
1309// structure keeping cost (value) for a BVH index
1310struct BVHPrioElement {
1311 size_t bvh_node_id;
1312 float value;
1313};
1314
1315// A priority queue for BVHPrioElement with an additional clear method
1316// for quick reset. We intentionally derive from std::priority_queue here to expose a
1317// clear() convenience method via access to the protected container `c`.
1318// This is internal, non-polymorphic code and relies on standard-library
1319// implementation details that are stable across supported platforms.
1320template <typename Comparator>
1321class BVHPrioQueue : public std::priority_queue<BVHPrioElement, std::vector<BVHPrioElement>, Comparator>
1322{
1323 public:
1324 using std::priority_queue<BVHPrioElement, std::vector<BVHPrioElement>,
1325 Comparator>::priority_queue; // constructor inclusion
1326
1327 // convenience method to quickly clear/reset the queue (instead of having to pop one by one)
1328 void clear() { this->c.clear(); }
1329};
1330
1331} // namespace
1332
1334template <bool returnFace>
1335inline Double_t O2Tessellated::SafetyKernel(const Double_t* point, bool in, int* closest_facet_id) const
1336{
1337 // This is the classic traversal/pruning of a BVH based on priority queue search
1338
1339 float smallest_safety_sq = TGeoShape::Big();
1340
1341 using Scalar = float;
1342 using Vec3 = bvh::v2::Vec<Scalar, 3>;
1343 using Node = bvh::v2::Node<Scalar, 3>;
1344 using Bvh = bvh::v2::Bvh<Node>;
1345
1346 // let's fetch the bvh
1347 auto mybvh = (Bvh*)fBVH;
1348
1349 // testpoint object in float for quick BVH interaction
1350 Vec3 testpoint(point[0], point[1], point[2]);
1351
1352 auto currnode = mybvh->nodes[0]; // we start from the top BVH node
1353 // we do a quick check on the top node (in case we are outside shape)
1354 bool outside_top = false;
1355 if (!in) {
1356 outside_top = !bvh::v2::extra::contains(currnode.get_bbox(), testpoint);
1357 if (outside_top) {
1358 const auto safety_sq_to_top = bvh::v2::extra::SafetySqToNode(currnode.get_bbox(), testpoint);
1359 // we simply return safety to the outer bounding box as an estimate
1360 return std::sqrt(safety_sq_to_top);
1361 }
1362 }
1363
1364 // comparator bringing out "smallest" value on top
1365 auto cmp = [](BVHPrioElement a, BVHPrioElement b) { return a.value > b.value; };
1366 static thread_local BVHPrioQueue<decltype(cmp)> queue(cmp);
1367 queue.clear();
1368
1369 // algorithm is based on standard iterative tree traversal with priority queues
1370 float current_safety_to_node_sq = 0.f;
1371
1372 if (returnFace) {
1373 *closest_facet_id = -1;
1374 }
1375
1376 do {
1377 if (currnode.is_leaf()) {
1378 // we are in a leaf node and actually talk to a face/triangular primitive
1379 const auto begin_prim_id = currnode.index.first_id();
1380 const auto end_prim_id = begin_prim_id + currnode.index.prim_count();
1381
1382 for (auto p_id = begin_prim_id; p_id < end_prim_id; p_id++) {
1383 const auto object_id = mybvh->prim_ids[p_id];
1384
1385 const auto& facet = fFacets[object_id];
1386 const auto& v1 = fVertices[facet[0]];
1387 const auto& v2 = fVertices[facet[1]];
1388 const auto& v3 = fVertices[facet[2]];
1389
1390 auto thissafetySQ = pointTriangleDistSq(Vec3f{point[0], point[1], point[2]}, Vec3f{v1[0], v1[1], v1[2]},
1391 Vec3f{v2[0], v2[1], v2[2]}, Vec3f{v3[0], v3[1], v3[2]});
1392
1393 if (thissafetySQ < smallest_safety_sq) {
1394 smallest_safety_sq = thissafetySQ;
1395 if (returnFace) {
1396 *closest_facet_id = object_id;
1397 }
1398 }
1399 }
1400 } else {
1401 // not a leave node ... for further traversal,
1402 // we inject the children into priority queue based on distance to it's bounding box
1403 const auto leftchild_id = currnode.index.first_id();
1404 const auto rightchild_id = leftchild_id + 1;
1405
1406 for (size_t childid : {leftchild_id, rightchild_id}) {
1407 if (childid >= mybvh->nodes.size()) {
1408 continue;
1409 }
1410
1411 const auto& node = mybvh->nodes[childid];
1412 const auto inside = bvh::v2::extra::contains(node.get_bbox(), testpoint);
1413
1414 if (inside) {
1415 // this must be further considered because we are inside the bounding box
1416 queue.push(BVHPrioElement{childid, -1.});
1417 } else {
1418 auto safety_to_node_square = bvh::v2::extra::SafetySqToNode(node.get_bbox(), testpoint);
1419 if (safety_to_node_square <= smallest_safety_sq) {
1420 // this should be further considered
1421 queue.push(BVHPrioElement{childid, safety_to_node_square});
1422 }
1423 }
1424 }
1425 }
1426
1427 if (queue.size() > 0) {
1428 auto currElement = queue.top();
1429 currnode = mybvh->nodes[currElement.bvh_node_id];
1430 current_safety_to_node_sq = currElement.value;
1431 queue.pop();
1432 } else {
1433 break;
1434 }
1435 } while (current_safety_to_node_sq <= smallest_safety_sq);
1436
1437 return std::nextafter(std::sqrt(smallest_safety_sq), 0.0f);
1438}
1439
1442
1443Double_t O2Tessellated::Safety(const Double_t* point, Bool_t in) const
1444{
1445 // we could use some caching here (in future) since queries to the solid will likely
1446 // be made with some locality
1447
1448 if (in) {
1449 call_counter++;
1450 // distance to last known evaluation
1451 const auto xd = float(point[0]) - mLast_x;
1452 const auto yd = float(point[1]) - mLast_y;
1453 const auto zd = float(point[2]) - mLast_z;
1454 const auto d2 = xd * xd + yd * yd + zd * zd;
1455
1456 if (d2 < mCachedSafety * mCachedSafety) {
1457 // we moved less than known safety
1458 cached_counter++;
1459 return mCachedSafety - std::sqrt(d2);
1460 }
1461 }
1462
1463 // fall-back to precise safety kernel
1464 const auto safety = SafetyKernel<false>(point, in);
1465 if (in) {
1466 mLast_x = point[0];
1467 mLast_y = point[1];
1468 mLast_z = point[2];
1469 mCachedSafety = safety;
1470 }
1471 return safety;
1472}
1473
1476
1477void O2Tessellated::ComputeNormal(const Double_t* point, const Double_t* dir, Double_t* norm) const
1478{
1479 // We take the approach to identify closest facet to the point via safety
1480 // and returning the normal from this face.
1481
1482 // TODO: Before doing that we could check for cached points from other queries
1483
1484 // use safety kernel
1485 int closest_face_id = -1;
1486 SafetyKernel<true>(point, true, &closest_face_id);
1487
1488 if (closest_face_id < 0) {
1489 norm[0] = 1.;
1490 norm[1] = 0.;
1491 norm[2] = 0.;
1492 return;
1493 }
1494
1495 const auto& n = fOutwardNormals[closest_face_id];
1496 norm[0] = n[0];
1497 norm[1] = n[1];
1498 norm[2] = n[2];
1499
1500 // change sign depending on dir
1501 if (norm[0] * dir[0] + norm[1] * dir[1] + norm[2] * dir[2] < 0) {
1502 norm[0] = -norm[0];
1503 norm[1] = -norm[1];
1504 norm[2] = -norm[2];
1505 }
1506 return;
1507}
1508
1511
1512Double_t O2Tessellated::DistFromInside_Loop(const Double_t* point, const Double_t* dir) const
1513{
1514 Vertex_t p(point[0], point[1], point[2]);
1515 Vertex_t d(dir[0], dir[1], dir[2]);
1516
1517 double dist = Big();
1518 for (size_t i = 0; i < fFacets.size(); ++i) {
1519 const auto& facet = fFacets[i];
1520 const auto& n = fOutwardNormals[i];
1521
1522 // Only exiting surfaces are relevant (from inside--> dot product must be positive)
1523 if (n.Dot(d) <= 0.0) {
1524 continue;
1525 }
1526
1527 const auto& v0 = fVertices[facet[0]];
1528 const auto& v1 = fVertices[facet[1]];
1529 const auto& v2 = fVertices[facet[2]];
1530
1531 const double t = rayTriangle(p, d, v0, v1, v2, 0.);
1532
1533 if (t < dist) {
1534 dist = t;
1535 }
1536 }
1537 return dist;
1538}
1539
1542
1543Double_t O2Tessellated::DistFromOutside_Loop(const Double_t* point, const Double_t* dir) const
1544{
1545 Vertex_t p(point[0], point[1], point[2]);
1546 Vertex_t d(dir[0], dir[1], dir[2]);
1547
1548 double dist = Big();
1549 for (size_t i = 0; i < fFacets.size(); ++i) {
1550 const auto& facet = fFacets[i];
1551 const auto& n = fOutwardNormals[i];
1552
1553 // Only exiting surfaces are relevant (from outside, the dot product must be negative)
1554 if (n.Dot(d) > 0.0) {
1555 continue;
1556 }
1557
1558 const auto& v0 = fVertices[facet[0]];
1559 const auto& v1 = fVertices[facet[1]];
1560 const auto& v2 = fVertices[facet[2]];
1561
1562 const double t = rayTriangle(p, d, v0, v1, v2, 0.);
1563
1564 if (t < dist) {
1565 dist = t;
1566 }
1567 }
1568 return dist;
1569}
1570
1573
1574bool O2Tessellated::Contains_Loop(const Double_t* point) const
1575{
1576 // Fixed ray direction
1577 const Vertex_t test_dir{1.0, 1.41421356237, 1.73205080757};
1578
1579 Vertex_t p(point[0], point[1], point[2]);
1580
1581 int crossings = 0;
1582 for (size_t i = 0; i < fFacets.size(); ++i) {
1583 const auto& facet = fFacets[i];
1584
1585 const auto& v0 = fVertices[facet[0]];
1586 const auto& v1 = fVertices[facet[1]];
1587 const auto& v2 = fVertices[facet[2]];
1588
1589 const double t = rayTriangle(p, test_dir, v0, v1, v2, 0.);
1590 if (t != std::numeric_limits<double>::infinity()) {
1591 ++crossings;
1592 }
1593 }
1594 return (crossings & 1);
1595}
1596
1600
1601void O2Tessellated::Streamer(TBuffer& b)
1602{
1603 if (b.IsReading()) {
1604 b.ReadClassBuffer(O2Tessellated::Class(), this);
1605 CloseShape(false); // close shape but do not re-perform checks
1606 } else {
1607 b.WriteClassBuffer(O2Tessellated::Class(), this);
1608 }
1609}
1610
1613
1614void O2Tessellated::CalculateNormals()
1615{
1616 fOutwardNormals.clear();
1617 for (auto& facet : fFacets) {
1618 auto& v1 = fVertices[facet[0]];
1619 auto& v2 = fVertices[facet[1]];
1620 auto& v3 = fVertices[facet[2]];
1621 using Vec3d = Vec3f<double>;
1622 auto norm = triangleNormal(Vec3d{v1[0], v1[1], v1[2]}, Vec3d{v2[0], v2[1], v2[2]}, Vec3d{v3[0], v3[1], v3[2]});
1623 fOutwardNormals.emplace_back(Vertex_t{norm.x, norm.y, norm.z});
1624 }
1625}
1626
1627// NOLINTEND
header::DataOrigin origin
std::function< int(const o2::mch::mapping::CathodeSegmentation &, int, rapidjson::Value &)> Comparator
uint32_t hash
std::unique_ptr< expressions::Node > node
uint64_t vertex
Definition RawEventData.h:9
int32_t i
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual Show related Highlight grid Show only individual collision Color clusters belonging to track objects Set line Enable drawing TPC data Enable drawing TOF data Enable drawing magnetic field Set field line step count Invert colors configStandalone Heavy OpenGL display display_heavy Show tracks with ITS contribution Show tracks with TOF contribution Enable propagation of loopers Show clusters at nominal x of pad row for early transformed data Split CE tracks when they cross the central Mark clusters Mark adjacent clusters Hide rejected tracks Apply filter on tracks to be displayed Separate increase X h configStandalone Camera window renderer OpenGL display display_camera Orient the camera such that the y axis is always upwards Full Screen MultiSample Anti Downsampling Anti Enable Vertical Sync Target frame or if the backend cannot limit to the refresh Use OpenGL indirect draws to reduce number of draw calls Display h configStandalone Vulkan display display_vulkan Have uniform buffers in host accessible device memory h configStandalone Internal display display_internal Number of updates the Qt GUI may send ahead of the last one the display has before it waits for the display Opacity of the console background Number of console lines the history is shortened to this when it reaches twice the size Time in seconds an info message fades out after setInfoTime h OpenGL display display Font size Do not use Freetype for font Renderer for event Frontend to use for event ROOT macros used as track filter Console script file
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char Directory with events to Events directory is and not inside events d
configStandalone Reconstruction rec_tpc multiplier for the combined cluster track error during track following area in cm for the search of for z only used if decrease removal by when above this local occupancy rowx Shift of TPC revert cluster flag edge bit distance to edge exceeds this ZOffset for secondary multiple of radius max P of electron K BB bands crossing max P of electron p BB bands crossing max P of electron d BB bands crossing max P of electron t BB bands crossing min accepted dEdxMaxTPC of the track Additive extra cluster error for Y2 if edge flag set Additive extra cluster error for Y2 if single set Additive extra cluster error for Y2 if or single set Additive extra cluster error for Z2 if or single set Extra errors to add to track when crossing depending on addErrorsCECrossing Inner portion of y range in sector that is not used in searching for extrapolated track candidates factor2YS for track merging factor2K for track merging Margin in cm of Y position when rejecting edge clusters based on uncorrected track Y Max fraction of shared clusters for tracklet Except pad from cluster finding if total number of charges in a fragment is above this Except pad from cluster finding if number of consecutive charges in a fragment is above this Threshold where a timebin is considered disabling the noisy pad check for that pad Treat signal above this minimum as saturated Smoothing factor for the exponential Highly Ionising Particle tail filter Number of timebins to use for the averaging Add additional custom track errors when crossing relative as fraction of Do N fit passes in final fit of< 0:reject at max -n hits") AddOptionRTC(rejectIFCLowRadiusCluster, uint8_t, 1, "", 0, "Reject clusters that get the IFC mask error during refit") AddOptionRTC(dEdxTruncLow, uint8_t, 2, "", 0, "Low truncation threshold, fraction of 128") AddOptionRTC(dEdxTruncHigh, uint8_t, 77, "", 0, "High truncation threshold, fraction of 128") AddOptionRTC(extrapolationTracking, int8_t, 1, "", 0, "Enable Extrapolation Tracking(prolong tracks to adjacent sectors to find short segments)") AddOptionRTC(disableRefitAttachment, uint8_t, 0, "", 0, "Bitmask to disable certain attachment steps during refit(1:attachment, 2:propagation, 4:loop following, 8:mirroring)") AddOptionRTC(rejectionStrategy, uint8_t, o2::gpu::GPUSettings::RejectionStrategyA, "", 0, "Enable rejection of TPC clusters for compression(0=no, 1=strategy A, 2=strategy B)") AddOptionRTC(mergeLoopersAfterburner, uint8_t, 1, "", 0, "Run afterburner for additional looper merging") AddOptionRTC(compressionTypeMask, uint8_t, o2::gpu::GPUSettings::CompressionFull, "", 0, "TPC Compression mode bits(1=truncate charge/width LSB, 2=differences, 4=track-model)") AddOptionRTC(compressionSortOrder, uint8_t, o2::gpu::GPUSettings::SortTime, "", 0, "Sort order of TPC compression(0=time, 1=pad, 2=Z-time-pad, 3=Z-pad-time, 4=no sorting(use incoming order))") AddOptionRTC(sigBitsCharge, uint8_t, 4, "", 0, "Number of significant bits for TPC cluster charge in compression mode 1") AddOptionRTC(sigBitsWidth, uint8_t, 3, "", 0, "Number of significant bits for TPC cluster width in compression mode 1") AddOptionRTC(dropLoopers, uint8_t, 0, "", 0, "Drop looping tracks starting from second loop") AddOptionRTC(mergerInterpolateErrors, uint8_t, 1, "", 0, "Use interpolation instead of extrapolation for chi2 based cluster rejection") AddOptionRTC(mergerInterpolateRejectAlsoOnCurrentPosition, uint8_t, 1, "", 0, "When using mergerInterpolateErrors, reject based on chi2 twice computed with interpolated and current track position starting from NDF > mergerNonInterpolateRejectMinNDF Merge tracks accross the central electrode Enable PID response Add sub threshold clusters in TPC dEdx computation OR mask of TPC flags that will reject the cluster in alternative dEdx Reject edge clusters based on uncorrected track Y during track fit Increase decrease removal by when on this number of pads from the edge h configStandalone Reconstruction rec_trd Max chi2 for TRD tracklets to be matched to a track Chi2 cut for strict matching mode Number of sigmas for ITS TPC track time error estimate Addition to search road around track prolongation along Y in cm Optimal tracklet rphi resolution in maximum chi2 per attached tracklet for TRD tracks take into account the full likelihood of the slope including exponential tails Tracks with less attached tracklets are discarded after the tracking Post trigger Pile up integration time in BCs fits all iterations but the last one in projections Use material lookup table for TPC refit Apply workarounds to avoid cyclic merge should not be needed tpc dyn configStandalone Processing proc_rtc Replace constant variables static by constexpr expressions Compile RTC in deterministic with NO_FAST_MATH flags and GPUCA_DETERMINISTIC_MODE define Use RTC to optimize GPU code h configStandalone Processing proc_rtctech Use a file lock to serialize access to the cache folder Print launch bounds used for RTC code as debugging for exit after Allow RTC with slave GPUReconstruction instances with optConstexpr and optSpecialcode Maximum number of RTC cache files per backend in the cache the oldest are Prepend RTC compilation commands by this string Load a parameter object containing the launch bounds from a file h configStandalone Processing proc_param TPC Cluster Z Error Parameterization h nn
configStandalone Reconstruction rec_tpc float
Tessellated::Vertex_t Vertex_t
ClassImp(O2Tessellated)
uint16_t pos
Definition RawData.h:3
uint32_t j
Definition RawData.h:0
uint32_t c
Definition RawData.h:2
uint32_t stack
Definition RawData.h:1
void SetSegsAndPols(TBuffer3D &buff) const override
Fills TBuffer3D structure for segments and polygons.
Double_t Safety(const Double_t *point, Bool_t in=kTRUE) const override
Safety.
bool Contains(const Double_t *point) const override
Contains.
void GetMeshNumbers(int &nvert, int &nsegs, int &npols) const override
Returns numbers of vertices, segments and polygons composing the shape mesh.
Double_t DistFromOutside_Loop(const Double_t *point, const Double_t *dir) const
trivial (non-BVH) DistFromOutside function
bool AddFacet(const Vertex_t &pt0, const Vertex_t &pt1, const Vertex_t &pt2)
Adding a triangular facet from vertex positions in absolute coordinates.
bool FacetCheck(int ifacet) const
Check validity of facet.
const TBuffer3D & GetBuffer3D(int reqSections, Bool_t localFrame) const override
Fills a static 3D buffer and returns a reference.
Double_t Capacity() const override
Capacity.
void ComputeBBox() override
Compute bounding box.
void CloseShape(bool check=true, bool fixFlipped=true, bool verbose=true)
Close the shape: calculate bounding box and compact vertices.
static O2Tessellated * ImportFromObjFormat(const char *objfile, bool check=false, bool verbose=false)
Reader from .obj format.
Double_t DistFromInside_Loop(const Double_t *point, const Double_t *dir) const
trivial (non-BVH) DistFromInside function
bool CheckClosure(bool fixFlipped=true, bool verbose=true)
Check closure of the solid and check/fix flipped normals.
void ComputeNormal(const Double_t *point, const Double_t *dir, Double_t *norm) const override
ComputeNormal interface.
void Print(Option_t *option="") const override
Prints basic info.
void ResizeCenter(double maxsize)
Resize and center the shape in a box of size maxsize.
TBuffer3D * MakeBuffer3D() const override
int AddVertex(const Vertex_t &vert)
Add a vertex checking for duplicates, returning the vertex index.
Bool_t GetPointsOnSegments(Int_t npoints, Double_t *array) const override
Fill array with npoints points on this solid's boundary: every vertex, then deterministic R2 samples ...
Vertex_t FacetComputeNormal(int ifacet, bool &degenerated) const
Compute normal for a given facet.
bool Contains_Loop(const Double_t *point) const
trivial (non-BVH) Contains
Tessellated::Vertex_t Vertex_t
Double_t DistFromInside(const Double_t *point, const Double_t *dir, Int_t iact=1, Double_t step=TGeoShape::Big(), Double_t *safe=nullptr) const override
DistFromOutside.
void SetPoints(double *points) const override
Fill tessellated points to an array.
Double_t DistFromOutside(const Double_t *point, const Double_t *dir, Int_t iact=1, Double_t step=TGeoShape::Big(), Double_t *safe=nullptr) const override
DistFromOutside.
GLdouble n
Definition glcorearb.h:1982
GLint GLenum GLint x
Definition glcorearb.h:403
GLuint buffer
Definition glcorearb.h:655
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLenum array
Definition glcorearb.h:4274
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLint first
Definition glcorearb.h:399
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLenum GLint * range
Definition glcorearb.h:1899
GLint y
Definition glcorearb.h:270
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLfloat v0
Definition glcorearb.h:811
GLfloat GLfloat v1
Definition glcorearb.h:812
GLfloat GLfloat GLfloat GLfloat v3
Definition glcorearb.h:814
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLubyte GLubyte GLubyte GLubyte w
Definition glcorearb.h:852
GLfloat GLfloat GLfloat v2
Definition glcorearb.h:813
GLsizei const GLint * box
Definition glcorearb.h:4697
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
bool contains(bvh::v2::BBox< T, 3 > const &box, bvh::v2::Vec< T, 3 > const &p)
auto SafetySqToNode(bvh::v2::BBox< T, 3 > const &box, bvh::v2::Vec< T, 3 > const &p)
Vec3 operator-(const Vec3 &firstVector, const Vec3 &secondVector)
double dot(const Vec3 &firstVector, const Vec3 &secondVector)
Vec3 cross(const Vec3 &firstVector, const Vec3 &secondVector)
double norm(const Vec3 &vector)
std::variant< OriginValueMatcher, DescriptionValueMatcher, SubSpecificationTypeValueMatcher, std::unique_ptr< DataDescriptorMatcher >, ConstantValueMatcher, StartTimeValueMatcher > Node
void check(const std::vector< std::string > &arguments, const std::vector< ConfigParamSpec > &workflowOptions, const std::vector< DeviceSpec > &deviceSpecs, CheckMatrix &matrix)
const float d3
Definition MathUtils.h:61
const float d1
Definition MathUtils.h:59
value_T d2
Definition TrackUtils.h:135
VectorOfTObjectPtrs other
vec clear()
char const *restrict const cmp
Definition x9.h:96