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
490
492{
493 const int nvert = fNvert;
494 const int nsegs = fNseg;
495 const int npols = GetNfacets();
496 auto buff = new TBuffer3D(TBuffer3DTypes::kGeneric, nvert, 3 * nvert, nsegs, 3 * nsegs, npols, 6 * npols);
497 if (buff) {
498 SetPoints(buff->fPnts);
499 SetSegsAndPols(*buff);
500 }
501 return buff;
502}
503
506
507void O2Tessellated::Print(Option_t*) const
508{
509 std::cout << "=== Tessellated shape " << GetName() << " having " << GetNvertices() << " vertices and "
510 << GetNfacets() << " facets\n";
511}
512
515
516void O2Tessellated::SetSegsAndPols(TBuffer3D& buff) const
517{
518 const int c = GetBasicColor();
519 int* segs = buff.fSegs;
520 int* pols = buff.fPols;
521
522 int indseg = 0; // segment internal data index
523 int indpol = 0; // polygon internal data index
524 int sind = 0; // segment index
525 for (const auto& facet : fFacets) {
526 auto nvert = facet.GetNvert();
527 pols[indpol++] = c;
528 pols[indpol++] = nvert;
529 for (auto j = 0; j < nvert; ++j) {
530 int k = (j + 1) % nvert;
531 // segment made by next consecutive points
532 segs[indseg++] = c;
533 segs[indseg++] = facet[j];
534 segs[indseg++] = facet[k];
535 // add segment to current polygon and increment segment index
536 pols[indpol + nvert - j - 1] = sind++;
537 }
538 indpol += nvert;
539 }
540}
541
544
545void O2Tessellated::SetPoints(double* points) const
546{
547 int ind = 0;
548 for (const auto& vertex : fVertices) {
549 vertex.CopyTo(&points[ind]);
550 ind += 3;
551 }
552}
553
556
558{
559 int ind = 0;
560 for (const auto& vertex : fVertices) {
561 points[ind++] = vertex.x();
562 points[ind++] = vertex.y();
563 points[ind++] = vertex.z();
564 }
565}
566
569
570void O2Tessellated::ResizeCenter(double maxsize)
571{
572 using Vector3_t = Vertex_t;
573
574 if (!fDefined) {
575 Error("ResizeCenter", "Not all faces are defined");
576 return;
577 }
578 Vector3_t origin(fOrigin[0], fOrigin[1], fOrigin[2]);
579 double maxedge = TMath::Max(TMath::Max(fDX, fDY), fDZ);
580 double scale = maxsize / maxedge;
581 for (size_t i = 0; i < fVertices.size(); ++i) {
582 fVertices[i] = scale * (fVertices[i] - origin);
583 }
584 fOrigin[0] = fOrigin[1] = fOrigin[2] = 0;
585 fDX *= scale;
586 fDY *= scale;
587 fDZ *= scale;
588}
589
592
593const TBuffer3D& O2Tessellated::GetBuffer3D(int reqSections, Bool_t localFrame) const
594{
595 static TBuffer3D buffer(TBuffer3DTypes::kGeneric);
596
597 FillBuffer3D(buffer, reqSections, localFrame);
598
599 const int nvert = fNvert;
600 const int nsegs = fNseg;
601 const int npols = GetNfacets();
602
603 if (reqSections & TBuffer3D::kRawSizes) {
604 if (buffer.SetRawSizes(nvert, 3 * nvert, nsegs, 3 * nsegs, npols, 6 * npols)) {
605 buffer.SetSectionsValid(TBuffer3D::kRawSizes);
606 }
607 }
608 if ((reqSections & TBuffer3D::kRaw) && buffer.SectionsValid(TBuffer3D::kRawSizes)) {
609 SetPoints(buffer.fPnts);
610 if (!buffer.fLocalFrame) {
611 TransformPoints(buffer.fPnts, buffer.NbPnts());
612 }
613
615 buffer.SetSectionsValid(TBuffer3D::kRaw);
616 }
617
618 return buffer;
619}
620
623
624O2Tessellated* O2Tessellated::ImportFromObjFormat(const char* objfile, bool check, bool verbose)
625{
626 using std::vector, std::string, std::ifstream, std::stringstream, std::endl;
627
628 vector<Vertex_t> vertices;
629 vector<string> sfacets;
630
631 struct FacetInd_t {
632 int i0 = -1;
633 int i1 = -1;
634 int i2 = -1;
635 int i3 = -1;
636 int nvert = 0;
637 FacetInd_t(int a, int b, int c)
638 {
639 i0 = a;
640 i1 = b;
641 i2 = c;
642 nvert = 3;
643 };
644 FacetInd_t(int a, int b, int c, int d)
645 {
646 i0 = a;
647 i1 = b;
648 i2 = c;
649 i3 = d;
650 nvert = 4;
651 };
652 };
653
654 vector<FacetInd_t> facets;
655 // List of geometric vertices, with (x, y, z [,w]) coordinates, w is optional and defaults to 1.0.
656 // struct vtx_t { double x = 0; double y = 0; double z = 0; double w = 1; };
657
658 // Texture coordinates in u, [,v ,w]) coordinates, these will vary between 0 and 1. v, w are optional and default to
659 // 0.
660 // struct tex_t { double u; double v; double w; };
661
662 // List of vertex normals in (x,y,z) form; normals might not be unit vectors.
663 // struct vn_t { double x; double y; double z; };
664
665 // Parameter space vertices in ( u [,v] [,w] ) form; free form geometry statement
666 // struct vp_t { double u; double v; double w; };
667
668 // Faces are defined using lists of vertex, texture and normal indices which start at 1.
669 // Polygons such as quadrilaterals can be defined by using more than three vertex/texture/normal indices.
670 // f v1//vn1 v2//vn2 v3//vn3 ...
671
672 // Records starting with the letter "l" specify the order of the vertices which build a polyline.
673 // l v1 v2 v3 v4 v5 v6 ...
674
675 string line;
676 int ind[4] = {0};
677 ifstream file(objfile);
678 if (!file.is_open()) {
679 ::Error("O2Tessellated::ImportFromObjFormat", "Unable to open %s", objfile);
680 return nullptr;
681 }
682
683 while (getline(file, line)) {
684 stringstream ss(line);
685 string tag;
686
687 // We ignore everything which is not a vertex or a face
688 if (line.rfind('v', 0) == 0 && line.rfind("vt", 0) != 0 && line.rfind("vn", 0) != 0 && line.rfind("vn", 0) != 0) {
689 // Decode the vertex
690 double pos[4] = {0, 0, 0, 1};
691 ss >> tag >> pos[0] >> pos[1] >> pos[2] >> pos[3];
692 vertices.emplace_back(pos[0] * pos[3], pos[1] * pos[3], pos[2] * pos[3]);
693 }
694
695 else if (line.rfind('f', 0) == 0) {
696 // Decode the face
697 ss >> tag;
698 string word;
699 sfacets.clear();
700 while (ss >> word)
701 sfacets.push_back(word);
702 if (sfacets.size() > 4 || sfacets.size() < 3) {
703 ::Error("O2Tessellated::ImportFromObjFormat", "Detected face having unsupported %zu vertices",
704 sfacets.size());
705 return nullptr;
706 }
707 int nvert = 0;
708 for (auto& sword : sfacets) {
709 stringstream ssword(sword);
710 string token;
711 getline(ssword, token, '/'); // just need the vertex index, which is the first token
712 // Convert string token to integer
713
714 ind[nvert++] = stoi(token) - 1;
715 if (ind[nvert - 1] < 0) {
716 ::Error("O2Tessellated::ImportFromObjFormat", "Unsupported relative vertex index definition in %s",
717 objfile);
718 return nullptr;
719 }
720 }
721 if (nvert == 3)
722 facets.emplace_back(ind[0], ind[1], ind[2]);
723 else
724 facets.emplace_back(ind[0], ind[1], ind[2], ind[3]);
725 }
726 }
727
728 int nvertices = (int)vertices.size();
729 int nfacets = (int)facets.size();
730 if (nfacets < 3) {
731 ::Error("O2Tessellated::ImportFromObjFormat", "Not enough faces detected in %s", objfile);
732 return nullptr;
733 }
734
735 string sobjfile(objfile);
736 if (verbose)
737 std::cout << "Read " << nvertices << " vertices and " << nfacets << " facets from " << sobjfile << endl;
738
739 auto tsl = new O2Tessellated(sobjfile.erase(sobjfile.find_last_of('.')).c_str(), vertices);
740
741 for (int i = 0; i < nfacets; ++i) {
742 auto facet = facets[i];
743 if (facet.nvert == 3)
744 tsl->AddFacet(facet.i0, facet.i1, facet.i2);
745 else
746 tsl->AddFacet(facet.i0, facet.i1, facet.i2, facet.i3);
747 }
748 tsl->CloseShape(check, true, verbose);
749 tsl->Print();
750 return tsl;
751}
752
753// implementation of some geometry helper functions in anonymous namespace
754namespace
755{
756
757using Vertex_t = Tessellated::Vertex_t;
758// The classic Moeller-Trumbore ray triangle-intersection kernel:
759// - Compute triangle edges e1, e2
760// - Compute determinant det
761// - Reject parallel rays
762// - Compute barycentric coordinates u, v
763// - Compute ray parameter t
764double rayTriangle(const Vertex_t& orig, const Vertex_t& dir, const Vertex_t& v0, const Vertex_t& v1,
765 const Vertex_t& v2, double rayEPS = 1e-8)
766{
767 constexpr double EPS = 1e-8;
768 const double INF = std::numeric_limits<double>::infinity();
769 Vertex_t e1{v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]};
770 Vertex_t e2{v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]};
771 auto p = Vertex_t::Cross(dir, e2);
772 auto det = e1.Dot(p);
773 if (std::abs(det) <= EPS) {
774 return INF;
775 }
776
777 Vertex_t tvec{orig[0] - v0[0], orig[1] - v0[1], orig[2] - v0[2]};
778 auto invDet = 1.0 / det;
779 auto u = tvec.Dot(p) * invDet;
780 if (u < 0.0 || u > 1.0) {
781 return INF;
782 }
783 auto q = Vertex_t::Cross(tvec, e1);
784 auto v = dir.Dot(q) * invDet;
785 if (v < 0.0 || u + v > 1.0) {
786 return INF;
787 }
788 auto t = e2.Dot(q) * invDet;
789 return (t > rayEPS) ? t : INF;
790}
791
792template <typename T = float>
793struct Vec3f {
794 T x, y, z;
795};
796
797template <typename T>
798inline Vec3f<T> operator-(const Vec3f<T>& a, const Vec3f<T>& b)
799{
800 return {a.x - b.x, a.y - b.y, a.z - b.z};
801}
802
803template <typename T>
804inline Vec3f<T> cross(const Vec3f<T>& a, const Vec3f<T>& b)
805{
806 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};
807}
808
809template <typename T>
810inline T dot(const Vec3f<T>& a, const Vec3f<T>& b)
811{
812 return a.x * b.x + a.y * b.y + a.z * b.z;
813}
814
815// Kernel to get closest/shortest distance between a point and a triangl (a,b,c).
816// Performed by default in float since Safety can be approximate.
817// Project point onto triangle plane
818// If projection lies inside → distance to plane
819// Otherwise compute min distance to the three edges
820// Return squared distance
821template <typename T = float>
822T pointTriangleDistSq(const Vec3f<T>& p, const Vec3f<T>& a, const Vec3f<T>& b, const Vec3f<T>& c)
823{
824 // Edges
825 Vec3f<T> ab = b - a;
826 Vec3f<T> ac = c - a;
827 Vec3f<T> ap = p - a;
828
829 auto d1 = dot(ab, ap);
830 auto d2 = dot(ac, ap);
831 if (d1 <= T(0.0) && d2 <= T(0.0)) {
832 return dot(ap, ap); // barycentric (1,0,0)
833 }
834
835 Vec3f<T> bp = p - b;
836 auto d3 = dot(ab, bp);
837 auto d4 = dot(ac, bp);
838 if (d3 >= T(0.0) && d4 <= d3) {
839 return dot(bp, bp); // (0,1,0)
840 }
841
842 T vc = d1 * d4 - d3 * d2;
843 if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) {
844 T v = d1 / (d1 - d3);
845 Vec3f<T> proj = {a.x + v * ab.x, a.y + v * ab.y, a.z + v * ab.z};
846 Vec3f<T> d = p - proj;
847 return dot(d, d); // edge AB
848 }
849
850 Vec3f<T> cp = p - c;
851 T d5 = dot(ab, cp);
852 T d6 = dot(ac, cp);
853 if (d6 >= T(0.0f) && d5 <= d6) {
854 return dot(cp, cp); // (0,0,1)
855 }
856
857 T vb = d5 * d2 - d1 * d6;
858 if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) {
859 T w = d2 / (d2 - d6);
860 Vec3f<T> proj = {a.x + w * ac.x, a.y + w * ac.y, a.z + w * ac.z};
861 Vec3f<T> d = p - proj;
862 return dot(d, d); // edge AC
863 }
864
865 T va = d3 * d6 - d5 * d4;
866 if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) {
867 T w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
868 Vec3f<T> proj = {b.x + w * (c.x - b.x), b.y + w * (c.y - b.y), b.z + w * (c.z - b.z)};
869 Vec3f<T> d = p - proj;
870 return dot(d, d); // edge BC
871 }
872
873 // Inside face region
874 T denom = T(1.0f) / (va + vb + vc);
875 T v = vb * denom;
876 T w = vc * denom;
877
878 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};
879
880 Vec3f<T> d = p - proj;
881 return dot(d, d);
882}
883
884template <typename T>
885inline Vec3f<T> normalize(const Vec3f<T>& v)
886{
887 T len2 = dot(v, v);
888 if (len2 == T(0.0f)) {
889 std::cerr << "Degnerate triangle. Cannot determine normal";
890 return {0, 0, 0};
891 }
892 T invLen = T(1.0f) / std::sqrt(len2);
893 return {v.x * invLen, v.y * invLen, v.z * invLen};
894}
895
896template <typename T>
897inline Vec3f<T> triangleNormal(const Vec3f<T>& a, const Vec3f<T>& b, const Vec3f<T>& c)
898{
899 const Vec3f<T> e1 = b - a;
900 const Vec3f<T> e2 = c - a;
901 return normalize(cross(e1, e2));
902}
903
904} // end anonymous namespace
905
908
909Double_t O2Tessellated::DistFromOutside(const Double_t* point, const Double_t* dir, Int_t /*iact*/, Double_t stepmax,
910 Double_t* /*safe*/) const
911{
912 // use the BVH intersector in combination with leaf ray-triangle testing
913 double local_step = Big(); // we need this otherwise the lambda get's confused
914
915 using Scalar = float;
916 using Vec3 = bvh::v2::Vec<Scalar, 3>;
917 using Node = bvh::v2::Node<Scalar, 3>;
918 using Bvh = bvh::v2::Bvh<Node>;
919 using Ray = bvh::v2::Ray<Scalar, 3>;
920
921 // let's fetch the bvh
922 auto mybvh = (Bvh*)fBVH;
923 if (!mybvh) {
924 assert(false);
925 return -1.;
926 }
927
928 auto truncate_roundup = [](double orig) {
929 float epsilon = std::numeric_limits<float>::epsilon() * std::fabs(orig);
930 // Add the bias to x before assigning it to y
931 return static_cast<float>(orig + epsilon);
932 };
933
934 // let's do very quick checks against the top node
935 const auto topnode_bbox = mybvh->get_root().get_bbox();
936 if ((-point[0] + topnode_bbox.min[0]) > stepmax) {
937 return Big();
938 }
939 if ((-point[1] + topnode_bbox.min[1]) > stepmax) {
940 return Big();
941 }
942 if ((-point[2] + topnode_bbox.min[2]) > stepmax) {
943 return Big();
944 }
945 if ((point[0] - topnode_bbox.max[0]) > stepmax) {
946 return Big();
947 }
948 if ((point[1] - topnode_bbox.max[1]) > stepmax) {
949 return Big();
950 }
951 if ((point[2] - topnode_bbox.max[2]) > stepmax) {
952 return Big();
953 }
954
955 // the ray used for bvh interaction
956 Ray ray(Vec3(point[0], point[1], point[2]), // origin
957 Vec3(dir[0], dir[1], dir[2]), // direction
958 0.0f, // minimum distance (could give stepmax ?)
959 truncate_roundup(local_step));
960
961 static constexpr bool use_robust_traversal = true;
962
963 Vertex_t dir_v{dir[0], dir[1], dir[2]};
964 // Traverse the BVH and apply concrete object intersection in BVH leafs
965 bvh::v2::GrowingStack<Bvh::Index> stack;
966 mybvh->intersect<false, use_robust_traversal>(ray, mybvh->get_root().index, stack, [&](size_t begin, size_t end) {
967 for (size_t prim_id = begin; prim_id < end; ++prim_id) {
968 auto objectid = mybvh->prim_ids[prim_id];
969 const auto& facet = fFacets[objectid];
970 const auto& n = fOutwardNormals[objectid];
971
972 // quick normal test. Coming from outside, the dot product must be negative
973 if (n.Dot(dir_v) > 0.) {
974 continue;
975 }
976
977 auto thisdist = rayTriangle(Vertex_t(point[0], point[1], point[2]), dir_v,
978 fVertices[facet[0]], fVertices[facet[1]], fVertices[facet[2]], 0.);
979
980 if (thisdist < local_step) {
981 local_step = thisdist;
982 }
983 }
984 return false; // go on after this
985 });
986
987 return local_step;
988}
989
992
993Double_t O2Tessellated::DistFromInside(const Double_t* point, const Double_t* dir, Int_t /*iact*/, Double_t /*stepmax*/,
994 Double_t* /*safe*/) const
995{
996 // use the BVH intersector in combination with leaf ray-triangle testing
997 double local_step = Big(); // we need this otherwise the lambda get's confused
998
999 using Scalar = float;
1000 using Vec3 = bvh::v2::Vec<Scalar, 3>;
1001 using Node = bvh::v2::Node<Scalar, 3>;
1002 using Bvh = bvh::v2::Bvh<Node>;
1003 using Ray = bvh::v2::Ray<Scalar, 3>;
1004
1005 // let's fetch the bvh
1006 auto mybvh = (Bvh*)fBVH;
1007 if (!mybvh) {
1008 assert(false);
1009 return -1.;
1010 }
1011
1012 auto truncate_roundup = [](double orig) {
1013 float epsilon = std::numeric_limits<float>::epsilon() * std::fabs(orig);
1014 // Add the bias to x before assigning it to y
1015 return static_cast<float>(orig + epsilon);
1016 };
1017
1018 // the ray used for bvh interaction
1019 Ray ray(Vec3(point[0], point[1], point[2]), // origin
1020 Vec3(dir[0], dir[1], dir[2]), // direction
1021 0., // minimum distance (could give stepmax ?)
1022 truncate_roundup(local_step));
1023
1024 static constexpr bool use_robust_traversal = true;
1025
1026 Vertex_t dir_v{dir[0], dir[1], dir[2]};
1027 // Traverse the BVH and apply concrete object intersection in BVH leafs
1028 bvh::v2::GrowingStack<Bvh::Index> stack;
1029 mybvh->intersect<false, use_robust_traversal>(ray, mybvh->get_root().index, stack, [&](size_t begin, size_t end) {
1030 for (size_t prim_id = begin; prim_id < end; ++prim_id) {
1031 auto objectid = mybvh->prim_ids[prim_id];
1032 auto facet = fFacets[objectid];
1033 const auto& n = fOutwardNormals[objectid];
1034
1035 // Only exiting surfaces are relevant (from inside--> dot product must be positive)
1036 if (n.Dot(dir_v) <= 0.) {
1037 continue;
1038 }
1039
1040 const auto& v0 = fVertices[facet[0]];
1041 const auto& v1 = fVertices[facet[1]];
1042 const auto& v2 = fVertices[facet[2]];
1043
1044 const double t =
1045 rayTriangle(Vertex_t{point[0], point[1], point[2]}, dir_v, v0, v1, v2, 0.);
1046 if (t < local_step) {
1047 local_step = t;
1048 }
1049 }
1050 return false; // go on after this
1051 });
1052
1053 return local_step;
1054}
1055
1058
1060{
1061 // For explanation of the following algorithm see:
1062 // https://en.wikipedia.org/wiki/Polyhedron#Volume
1063 // http://wwwf.imperial.ac.uk/~rn/centroid.pdf
1064
1065 double vol = 0.0;
1066 for (size_t i = 0; i < fFacets.size(); ++i) {
1067 auto& facet = fFacets[i];
1068 auto a = fVertices[facet[0]];
1069 auto b = fVertices[facet[1]];
1070 auto c = fVertices[facet[2]];
1071 vol +=
1072 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]);
1073 }
1074 return vol / 6.0;
1075}
1076
1079
1080void O2Tessellated::BuildBVH()
1081{
1082 using Scalar = float;
1083 using BBox = bvh::v2::BBox<Scalar, 3>;
1084 using Vec3 = bvh::v2::Vec<Scalar, 3>;
1085 using Node = bvh::v2::Node<Scalar, 3>;
1086 using Bvh = bvh::v2::Bvh<Node>;
1087
1088 // helper determining axis aligned bounding box from a facet;
1089 auto GetBoundingBox = [this](TGeoFacet const& facet) {
1090#ifndef NDEBUG
1091 const auto nvertices = facet.GetNvert();
1092 assert(nvertices == 3); // for now only triangles
1093#endif
1094 const auto& v1 = fVertices[facet[0]];
1095 const auto& v2 = fVertices[facet[1]];
1096 const auto& v3 = fVertices[facet[2]];
1097 BBox bbox;
1098 bbox.min[0] = std::min(std::min(v1[0], v2[0]), v3[0]) - 0.001f;
1099 bbox.min[1] = std::min(std::min(v1[1], v2[1]), v3[1]) - 0.001f;
1100 bbox.min[2] = std::min(std::min(v1[2], v2[2]), v3[2]) - 0.001f;
1101 bbox.max[0] = std::max(std::max(v1[0], v2[0]), v3[0]) + 0.001f;
1102 bbox.max[1] = std::max(std::max(v1[1], v2[1]), v3[1]) + 0.001f;
1103 bbox.max[2] = std::max(std::max(v1[2], v2[2]), v3[2]) + 0.001f;
1104 return bbox;
1105 };
1106
1107 // we need bounding boxes enclosing the primitives and centers of primitives
1108 // (replaced here by centers of bounding boxes) to build the bvh
1109 std::vector<BBox> bboxes;
1110 std::vector<Vec3> centers;
1111
1112 // loop over all the triangles/Facets;
1113 int nd = fFacets.size();
1114 for (int i = 0; i < nd; ++i) {
1115 auto& facet = fFacets[i];
1116
1117 // fetch the bounding box of this node and add to the vector of bounding boxes
1118 (bboxes).push_back(GetBoundingBox(facet));
1119 centers.emplace_back((bboxes).back().get_center());
1120 }
1121
1122 // check if some previous object is registered and delete if necessary
1123 if (fBVH) {
1124 delete (Bvh*)fBVH;
1125 fBVH = nullptr;
1126 }
1127
1128 // create the bvh
1129 typename bvh::v2::DefaultBuilder<Node>::Config config;
1130 config.quality = bvh::v2::DefaultBuilder<Node>::Quality::High;
1131 auto bvh = bvh::v2::DefaultBuilder<Node>::build(bboxes, centers, config);
1132 auto bvhptr = new Bvh;
1133 *bvhptr = std::move(bvh); // copy structure
1134 fBVH = (void*)(bvhptr);
1135
1136 return;
1137}
1138
1141
1142bool O2Tessellated::Contains(Double_t const* point) const
1143{
1144 // we do the parity test
1145 using Scalar = float;
1146 using Vec3 = bvh::v2::Vec<Scalar, 3>;
1147 using Node = bvh::v2::Node<Scalar, 3>;
1148 using Bvh = bvh::v2::Bvh<Node>;
1149 using Ray = bvh::v2::Ray<Scalar, 3>;
1150
1151 // let's fetch the bvh
1152 auto mybvh = (Bvh*)fBVH;
1153 if (!mybvh) {
1154 assert(false);
1155 return false;
1156 }
1157
1158 auto truncate_roundup = [](double orig) {
1159 float epsilon = std::numeric_limits<float>::epsilon() * std::fabs(orig);
1160 // Add the bias to x before assigning it to y
1161 return static_cast<float>(orig + epsilon);
1162 };
1163
1164 // let's do very quick checks against the top node
1165 if (!TGeoBBox::Contains(point)) {
1166 return false;
1167 }
1168
1169 // An arbitrary test direction.
1170 // Doesn't need to be normalized and probes all normals. Also ensuring to be skewed somewhat
1171 // without evident symmetries.
1172 Vertex_t test_dir{1.0, 1.41421356237, 1.73205080757};
1173
1174 double local_step = Big();
1175 // the ray used for bvh interaction
1176 Ray ray(Vec3(point[0], point[1], point[2]), // origin
1177 Vec3(test_dir[0], test_dir[1], test_dir[2]), // direction
1178 0.0f, // minimum distance (could give stepmax ?)
1179 truncate_roundup(local_step));
1180
1181 static constexpr bool use_robust_traversal = true;
1182
1183 // Traverse the BVH and apply concrete object intersection in BVH leafs
1184 bvh::v2::GrowingStack<Bvh::Index> stack;
1185 size_t crossings = 0;
1186 mybvh->intersect<false, use_robust_traversal>(ray, mybvh->get_root().index, stack, [&](size_t begin, size_t end) {
1187 for (size_t prim_id = begin; prim_id < end; ++prim_id) {
1188 auto objectid = mybvh->prim_ids[prim_id];
1189 auto& facet = fFacets[objectid];
1190
1191 // for the parity test, we probe all crossing surfaces
1192 const auto& v0 = fVertices[facet[0]];
1193 const auto& v1 = fVertices[facet[1]];
1194 const auto& v2 = fVertices[facet[2]];
1195
1196 const double t = rayTriangle(Vertex_t(point[0], point[1], point[2]),
1197 test_dir, v0, v1, v2, 0.);
1198
1199 if (t != std::numeric_limits<double>::infinity()) {
1200 ++crossings;
1201 }
1202 }
1203 return false;
1204 });
1205
1206 return crossings & 1;
1207}
1208
1209namespace
1210{
1211
1212// Helper classes/structs used for priority queue - BVH traversal
1213// structure keeping cost (value) for a BVH index
1214struct BVHPrioElement {
1215 size_t bvh_node_id;
1216 float value;
1217};
1218
1219// A priority queue for BVHPrioElement with an additional clear method
1220// for quick reset. We intentionally derive from std::priority_queue here to expose a
1221// clear() convenience method via access to the protected container `c`.
1222// This is internal, non-polymorphic code and relies on standard-library
1223// implementation details that are stable across supported platforms.
1224template <typename Comparator>
1225class BVHPrioQueue : public std::priority_queue<BVHPrioElement, std::vector<BVHPrioElement>, Comparator>
1226{
1227 public:
1228 using std::priority_queue<BVHPrioElement, std::vector<BVHPrioElement>,
1229 Comparator>::priority_queue; // constructor inclusion
1230
1231 // convenience method to quickly clear/reset the queue (instead of having to pop one by one)
1232 void clear() { this->c.clear(); }
1233};
1234
1235} // namespace
1236
1238template <bool returnFace>
1239inline Double_t O2Tessellated::SafetyKernel(const Double_t* point, bool in, int* closest_facet_id) const
1240{
1241 // This is the classic traversal/pruning of a BVH based on priority queue search
1242
1243 float smallest_safety_sq = TGeoShape::Big();
1244
1245 using Scalar = float;
1246 using Vec3 = bvh::v2::Vec<Scalar, 3>;
1247 using Node = bvh::v2::Node<Scalar, 3>;
1248 using Bvh = bvh::v2::Bvh<Node>;
1249
1250 // let's fetch the bvh
1251 auto mybvh = (Bvh*)fBVH;
1252
1253 // testpoint object in float for quick BVH interaction
1254 Vec3 testpoint(point[0], point[1], point[2]);
1255
1256 auto currnode = mybvh->nodes[0]; // we start from the top BVH node
1257 // we do a quick check on the top node (in case we are outside shape)
1258 bool outside_top = false;
1259 if (!in) {
1260 outside_top = !bvh::v2::extra::contains(currnode.get_bbox(), testpoint);
1261 if (outside_top) {
1262 const auto safety_sq_to_top = bvh::v2::extra::SafetySqToNode(currnode.get_bbox(), testpoint);
1263 // we simply return safety to the outer bounding box as an estimate
1264 return std::sqrt(safety_sq_to_top);
1265 }
1266 }
1267
1268 // comparator bringing out "smallest" value on top
1269 auto cmp = [](BVHPrioElement a, BVHPrioElement b) { return a.value > b.value; };
1270 static thread_local BVHPrioQueue<decltype(cmp)> queue(cmp);
1271 queue.clear();
1272
1273 // algorithm is based on standard iterative tree traversal with priority queues
1274 float current_safety_to_node_sq = 0.f;
1275
1276 if (returnFace) {
1277 *closest_facet_id = -1;
1278 }
1279
1280 do {
1281 if (currnode.is_leaf()) {
1282 // we are in a leaf node and actually talk to a face/triangular primitive
1283 const auto begin_prim_id = currnode.index.first_id();
1284 const auto end_prim_id = begin_prim_id + currnode.index.prim_count();
1285
1286 for (auto p_id = begin_prim_id; p_id < end_prim_id; p_id++) {
1287 const auto object_id = mybvh->prim_ids[p_id];
1288
1289 const auto& facet = fFacets[object_id];
1290 const auto& v1 = fVertices[facet[0]];
1291 const auto& v2 = fVertices[facet[1]];
1292 const auto& v3 = fVertices[facet[2]];
1293
1294 auto thissafetySQ = pointTriangleDistSq(Vec3f{point[0], point[1], point[2]}, Vec3f{v1[0], v1[1], v1[2]},
1295 Vec3f{v2[0], v2[1], v2[2]}, Vec3f{v3[0], v3[1], v3[2]});
1296
1297 if (thissafetySQ < smallest_safety_sq) {
1298 smallest_safety_sq = thissafetySQ;
1299 if (returnFace) {
1300 *closest_facet_id = object_id;
1301 }
1302 }
1303 }
1304 } else {
1305 // not a leave node ... for further traversal,
1306 // we inject the children into priority queue based on distance to it's bounding box
1307 const auto leftchild_id = currnode.index.first_id();
1308 const auto rightchild_id = leftchild_id + 1;
1309
1310 for (size_t childid : {leftchild_id, rightchild_id}) {
1311 if (childid >= mybvh->nodes.size()) {
1312 continue;
1313 }
1314
1315 const auto& node = mybvh->nodes[childid];
1316 const auto inside = bvh::v2::extra::contains(node.get_bbox(), testpoint);
1317
1318 if (inside) {
1319 // this must be further considered because we are inside the bounding box
1320 queue.push(BVHPrioElement{childid, -1.});
1321 } else {
1322 auto safety_to_node_square = bvh::v2::extra::SafetySqToNode(node.get_bbox(), testpoint);
1323 if (safety_to_node_square <= smallest_safety_sq) {
1324 // this should be further considered
1325 queue.push(BVHPrioElement{childid, safety_to_node_square});
1326 }
1327 }
1328 }
1329 }
1330
1331 if (queue.size() > 0) {
1332 auto currElement = queue.top();
1333 currnode = mybvh->nodes[currElement.bvh_node_id];
1334 current_safety_to_node_sq = currElement.value;
1335 queue.pop();
1336 } else {
1337 break;
1338 }
1339 } while (current_safety_to_node_sq <= smallest_safety_sq);
1340
1341 return std::nextafter(std::sqrt(smallest_safety_sq), 0.0f);
1342}
1343
1346
1347Double_t O2Tessellated::Safety(const Double_t* point, Bool_t in) const
1348{
1349 // we could use some caching here (in future) since queries to the solid will likely
1350 // be made with some locality
1351
1352 // fall-back to precise safety kernel
1353 return SafetyKernel<false>(point, in);
1354}
1355
1358
1359void O2Tessellated::ComputeNormal(const Double_t* point, const Double_t* dir, Double_t* norm) const
1360{
1361 // We take the approach to identify closest facet to the point via safety
1362 // and returning the normal from this face.
1363
1364 // TODO: Before doing that we could check for cached points from other queries
1365
1366 // use safety kernel
1367 int closest_face_id = -1;
1368 SafetyKernel<true>(point, true, &closest_face_id);
1369
1370 if (closest_face_id < 0) {
1371 norm[0] = 1.;
1372 norm[1] = 0.;
1373 norm[2] = 0.;
1374 return;
1375 }
1376
1377 const auto& n = fOutwardNormals[closest_face_id];
1378 norm[0] = n[0];
1379 norm[1] = n[1];
1380 norm[2] = n[2];
1381
1382 // change sign depending on dir
1383 if (norm[0] * dir[0] + norm[1] * dir[1] + norm[2] * dir[2] < 0) {
1384 norm[0] = -norm[0];
1385 norm[1] = -norm[1];
1386 norm[2] = -norm[2];
1387 }
1388 return;
1389}
1390
1393
1394Double_t O2Tessellated::DistFromInside_Loop(const Double_t* point, const Double_t* dir) const
1395{
1396 Vertex_t p(point[0], point[1], point[2]);
1397 Vertex_t d(dir[0], dir[1], dir[2]);
1398
1399 double dist = Big();
1400 for (size_t i = 0; i < fFacets.size(); ++i) {
1401 const auto& facet = fFacets[i];
1402 const auto& n = fOutwardNormals[i];
1403
1404 // Only exiting surfaces are relevant (from inside--> dot product must be positive)
1405 if (n.Dot(d) <= 0.0) {
1406 continue;
1407 }
1408
1409 const auto& v0 = fVertices[facet[0]];
1410 const auto& v1 = fVertices[facet[1]];
1411 const auto& v2 = fVertices[facet[2]];
1412
1413 const double t = rayTriangle(p, d, v0, v1, v2, 0.);
1414
1415 if (t < dist) {
1416 dist = t;
1417 }
1418 }
1419 return dist;
1420}
1421
1424
1425Double_t O2Tessellated::DistFromOutside_Loop(const Double_t* point, const Double_t* dir) const
1426{
1427 Vertex_t p(point[0], point[1], point[2]);
1428 Vertex_t d(dir[0], dir[1], dir[2]);
1429
1430 double dist = Big();
1431 for (size_t i = 0; i < fFacets.size(); ++i) {
1432 const auto& facet = fFacets[i];
1433 const auto& n = fOutwardNormals[i];
1434
1435 // Only exiting surfaces are relevant (from outside, the dot product must be negative)
1436 if (n.Dot(d) > 0.0) {
1437 continue;
1438 }
1439
1440 const auto& v0 = fVertices[facet[0]];
1441 const auto& v1 = fVertices[facet[1]];
1442 const auto& v2 = fVertices[facet[2]];
1443
1444 const double t = rayTriangle(p, d, v0, v1, v2, 0.);
1445
1446 if (t < dist) {
1447 dist = t;
1448 }
1449 }
1450 return dist;
1451}
1452
1455
1456bool O2Tessellated::Contains_Loop(const Double_t* point) const
1457{
1458 // Fixed ray direction
1459 const Vertex_t test_dir{1.0, 1.41421356237, 1.73205080757};
1460
1461 Vertex_t p(point[0], point[1], point[2]);
1462
1463 int crossings = 0;
1464 for (size_t i = 0; i < fFacets.size(); ++i) {
1465 const auto& facet = fFacets[i];
1466
1467 const auto& v0 = fVertices[facet[0]];
1468 const auto& v1 = fVertices[facet[1]];
1469 const auto& v2 = fVertices[facet[2]];
1470
1471 const double t = rayTriangle(p, test_dir, v0, v1, v2, 0.);
1472 if (t != std::numeric_limits<double>::infinity()) {
1473 ++crossings;
1474 }
1475 }
1476 return (crossings & 1);
1477}
1478
1482
1483void O2Tessellated::Streamer(TBuffer& b)
1484{
1485 if (b.IsReading()) {
1486 b.ReadClassBuffer(O2Tessellated::Class(), this);
1487 CloseShape(false); // close shape but do not re-perform checks
1488 } else {
1489 b.WriteClassBuffer(O2Tessellated::Class(), this);
1490 }
1491}
1492
1495
1496void O2Tessellated::CalculateNormals()
1497{
1498 fOutwardNormals.clear();
1499 for (auto& facet : fFacets) {
1500 auto& v1 = fVertices[facet[0]];
1501 auto& v2 = fVertices[facet[1]];
1502 auto& v3 = fVertices[facet[2]];
1503 using Vec3d = Vec3f<double>;
1504 auto norm = triangleNormal(Vec3d{v1[0], v1[1], v1[2]}, Vec3d{v2[0], v2[1], v2[2]}, Vec3d{v3[0], v3[1], v3[2]});
1505 fOutwardNormals.emplace_back(Vertex_t{norm.x, norm.y, norm.z});
1506 }
1507}
1508
1509// 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.
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
GLuint const GLchar * name
Definition glcorearb.h:781
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
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)
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)
Vertex< T > operator-(const Vertex< T > &a, const Vertex< T > &b)
Definition Vertex.h:98
auto dot(const Vertex< T > &a, const Vertex< T > &b) -> decltype(a.x *b.x)
Definition Vertex.h:104
value_T d2
Definition TrackUtils.h:135
VectorOfTObjectPtrs other
vec clear()
char const *restrict const cmp
Definition x9.h:96