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
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