EGSnrc C++ class library  Report PIRS-898 (2021)
Iwan Kawrakow, Ernesto Mainegra-Hing, Frederic Tessier, Reid Townson and Blake Walters
egs_mesh.cpp
1 /*
2 ###############################################################################
3 #
4 # EGSnrc egs++ mesh geometry library implementation.
5 # Copyright (C) 2020 Mevex Corporation
6 #
7 # This file is part of EGSnrc.
8 
9 # EGSnrc is free software: you can redistribute it and/or modify it under
10 # the terms of the GNU Affero General Public License as published by the
11 # Free Software Foundation, either version 3 of the License, or (at your
12 # option) any later version.
13 #
14 # EGSnrc is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
17 # more details.
18 #
19 # You should have received a copy of the GNU Affero General Public License
20 # along with EGSnrc. If not, see <http://www.gnu.org/licenses/>.
21 #
22 ###############################################################################
23 #
24 # Authors: Dave Macrillo, 2020
25 # Matt Ronan,
26 # Nigel Vezeau,
27 # Lou Thompson,
28 # Max Orok
29 #
30 # Contributors: Pascal Michaud
31 #
32 ###############################################################################
33 #
34 # The original EGS_Mesh geometry started as a product of many people's hard
35 # work at Mevex Corporation. Thank you to the entire team. Special thanks to
36 # Dave Brown and Emily Craven for their support in releasing this work
37 # under a public licence.
38 #
39 # The EGS_Mesh library was then significantly extended and refactored by
40 # Max Orok, as the subject of his Masters degree in Applied Science at the
41 # University of Ottawa, in the department of Mechanical Engineering and under
42 # the supervision of Professor James McDonald.
43 #
44 # Parts of this file, namely, the closest_point_triangle and
45 # closest_point_tetrahedron functions, are adapted from Chapter 5 of
46 # "Real-Time Collision Detection" by Christer Ericson with the permission
47 # from the author and the publisher.
48 #
49 ###############################################################################
50 */
51 
52 
53 #include "egs_input.h"
54 #include "egs_mesh.h"
55 #include "egs_vector.h"
56 
57 #include "mesh_neighbours.h"
58 #include "msh_parser.h"
59 #include "tetgen_parser.h"
60 
61 #include <cassert>
62 #include <chrono>
63 #include <deque>
64 #include <limits>
65 #include <stdexcept>
66 #include <unordered_map>
67 
68 #ifndef WIN32
69  #include <unistd.h> // isatty
70 #else
71  #include <io.h> // _isatty
72 #endif
73 
74 // Have to define the move constructor, move assignment operator and destructor
75 // here instead of in egs_mesh.h because of the unique_ptr to forward declared
76 // EGS_Mesh_Octree members.
77 EGS_Mesh::~EGS_Mesh() = default;
78 EGS_Mesh::EGS_Mesh(EGS_Mesh &&) = default;
79 EGS_Mesh &EGS_Mesh::operator=(EGS_Mesh &&) = default;
80 
82  std::size_t n_max = std::numeric_limits<int>::max();
83  if (this->elements.size() >= n_max) {
84  throw std::runtime_error("maximum number of elements (" +
85  std::to_string(n_max) + ") exceeded (" +
86  std::to_string(this->elements.size()) + ")");
87  }
88  if (this->nodes.size() >= n_max) {
89  throw std::runtime_error("maximum number of nodes (" +
90  std::to_string(n_max) + ") exceeded (" +
91  std::to_string(this->nodes.size()) + ")");
92  }
93 }
94 
95 // exclude from doxygen
97 namespace egs_mesh {
98 namespace internal {
99 
100 PercentCounter::PercentCounter(EGS_InfoFunction info, const std::string &msg)
101  : info_(info), msg_(msg) {}
102 
103 void PercentCounter::start(EGS_Float goal) {
104  goal_ = goal;
105  t_start_ = std::chrono::system_clock::now();
106 #ifndef WIN32
107  interactive_ = isatty(STDOUT_FILENO);
108 #else
109  interactive_ = _isatty(STDOUT_FILENO);
110 #endif
111  if (!info_ || !interactive_) {
112  return;
113  }
114  info_("\r%s (0%%)", msg_.c_str());
115 }
116 
117 // Assumes delta is positive
118 void PercentCounter::step(EGS_Float delta) {
119  if (!info_ || !interactive_) {
120  return;
121  }
122  progress_ += delta;
123  const int percent = static_cast<int>((progress_ / goal_) * 100.0);
124  if (percent > old_percent_) {
125  info_("\r%s (%d%%)", msg_.c_str(), percent);
126  old_percent_ = percent;
127  }
128 }
129 
130 void PercentCounter::finish(const std::string &end_msg) {
131  if (!info_) {
132  return;
133  }
134  const auto t_end = std::chrono::system_clock::now();
135  const std::chrono::duration<float> elapsed = t_end - t_start_;
136  if (!interactive_) {
137  info_("%s in %0.3fs\n", end_msg.c_str(), elapsed.count());
138  return;
139  }
140  // overwrite any remaining text
141  info_("\r ");
142  info_("\r%s in %0.3fs\n", end_msg.c_str(), elapsed.count());
143 }
144 
145 } // namespace internal
146 } // namespace egs_mesh
147 
149 
150 // anonymous namespace
151 namespace {
152 
153 const EGS_Float eps = 1e-8;
154 
155 inline bool approx_eq(double a, double b, double e = eps) {
156  return (std::abs(a - b) <= e * (std::abs(a) + std::abs(b) + 1.0));
157 }
158 
159 inline bool is_zero(const EGS_Vector &v) {
160  return approx_eq(0.0, v.length(), eps);
161 }
162 
163 inline EGS_Float min3(EGS_Float a, EGS_Float b, EGS_Float c) {
164  return std::min(std::min(a, b), c);
165 }
166 
167 inline EGS_Float max3(EGS_Float a, EGS_Float b, EGS_Float c) {
168  return std::max(std::max(a, b), c);
169 }
170 
171 inline EGS_Float dot(const EGS_Vector &x, const EGS_Vector &y) {
172  return x * y;
173 }
174 
175 inline EGS_Vector cross(const EGS_Vector &x, const EGS_Vector &y) {
176  return x.times(y);
177 }
178 
179 inline EGS_Float distance2(const EGS_Vector &x, const EGS_Vector &y) {
180  return (x - y).length2();
181 }
182 
183 inline EGS_Float distance(const EGS_Vector &x, const EGS_Vector &y) {
184  return std::sqrt(distance2(x, y));
185 }
186 
187 EGS_Vector closest_point_triangle(const EGS_Vector &P, const EGS_Vector &A, const EGS_Vector &B, const EGS_Vector &C) {
188  // vertex region A
189  EGS_Vector ab = B - A;
190  EGS_Vector ac = C - A;
191  EGS_Vector ao = P - A;
192 
193  EGS_Float d1 = dot(ab, ao);
194  EGS_Float d2 = dot(ac, ao);
195  if (d1 <= 0.0 && d2 <= 0.0) {
196  return A;
197  }
198 
199  // vertex region B
200  EGS_Vector bo = P - B;
201  EGS_Float d3 = dot(ab, bo);
202  EGS_Float d4 = dot(ac, bo);
203  if (d3 >= 0.0 && d4 <= d3) {
204  return B;
205  }
206 
207  // edge region AB
208  EGS_Float vc = d1 * d4 - d3 * d2;
209  if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) {
210  EGS_Float v = d1 / (d1 - d3);
211  return A + v * ab;
212  }
213 
214  // vertex region C
215  EGS_Vector co = P - C;
216  EGS_Float d5 = dot(ab, co);
217  EGS_Float d6 = dot(ac, co);
218  if (d6 >= 0.0 && d5 <= d6) {
219  return C;
220  }
221 
222  // edge region AC
223  EGS_Float vb = d5 * d2 - d1 * d6;
224  if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) {
225  EGS_Float w = d2 / (d2 - d6);
226  return A + w * ac;
227  }
228 
229  // edge region BC
230  EGS_Float va = d3 * d6 - d5 * d4;
231  if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0) {
232  EGS_Float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
233  return B + w * (C - B);
234  }
235 
236  // inside the face
237  EGS_Float denom = 1.0 / (va + vb + vc);
238  EGS_Float v = vb * denom;
239  EGS_Float w = vc * denom;
240  return A + v * ab + w * ac;
241 }
242 
243 // Returns true if the point is on the outside of the plane defined by ABC using
244 // reference point D, i.e. if D and P are on opposite sides of the plane of ABC.
245 inline bool point_outside_of_plane(EGS_Vector P, EGS_Vector A, EGS_Vector B, EGS_Vector C, EGS_Vector D) {
246  return dot(P - A, cross(B - A, C - A)) * dot(D - A, cross(B - A, C - A)) < 0.0;
247 }
248 
249 EGS_Vector closest_point_tetrahedron(const EGS_Vector &P, const EGS_Vector &A, const EGS_Vector &B, const EGS_Vector &C, const EGS_Vector &D) {
250  EGS_Vector min_point = P;
251  EGS_Float min = std::numeric_limits<EGS_Float>::max();
252 
253  auto maybe_update_min_point = [&](const EGS_Vector& A, const EGS_Vector& B, const EGS_Vector& C) {
254  EGS_Vector q = closest_point_triangle(P, A, B, C);
255  EGS_Float dis = distance2(q, P);
256  if (dis < min) {
257  min = dis;
258  min_point = q;
259  }
260  };
261 
262  if (point_outside_of_plane(P, A, B, C, D)) {
263  maybe_update_min_point(A, B, C);
264  }
265 
266  if (point_outside_of_plane(P, A, C, D, B)) {
267  maybe_update_min_point(A, C, D);
268  }
269 
270  if (point_outside_of_plane(P, A, B, D, C)) {
271  maybe_update_min_point(A, B, D);
272  }
273  if (point_outside_of_plane(P, B, D, C, A)) {
274  maybe_update_min_point(B, D, C);
275  }
276 
277  return min_point;
278 }
279 
294 int exterior_triangle_ray_intersection(const EGS_Vector &p,
295  const EGS_Vector &v_norm, const EGS_Vector &a, const EGS_Vector &b,
296  const EGS_Vector &c, EGS_Float &dist) {
297  const EGS_Float eps = 1e-10;
298  EGS_Vector ab = b - a;
299  EGS_Vector ac = c - a;
300 
301  EGS_Vector pvec = cross(v_norm, ac);
302  EGS_Float det = dot(ab, pvec);
303 
304  if (det > -eps && det < eps) {
305  return 0;
306  }
307  EGS_Float inv_det = 1.0 / det;
308  EGS_Vector tvec = p - a;
309  EGS_Float u = dot(tvec, pvec) * inv_det;
310  if (u < 0.0 || u > 1.0) {
311  return 0;
312  }
313  EGS_Vector qvec = cross(tvec, ab);
314  EGS_Float v = dot(v_norm, qvec) * inv_det;
315  if (v < 0.0 || u + v > 1.0) {
316  return 0;
317  }
318  // intersection found
319  dist = dot(ac, qvec) * inv_det;
320  if (dist < 0.0) {
321  return 0;
322  }
323  return 1;
324 }
325 
326 } // anonymous namespace
327 
328 // exclude from doxygen
330 class EGS_Mesh_Octree {
331 private:
332  static double tet_min_x(const EGS_Mesh::Nodes &n) {
333  return std::min(n.A.x, std::min(n.B.x, std::min(n.C.x, n.D.x)));
334  }
335  static double tet_max_x(const EGS_Mesh::Nodes &n) {
336  return std::max(n.A.x, std::max(n.B.x, std::max(n.C.x, n.D.x)));
337  }
338  static double tet_min_y(const EGS_Mesh::Nodes &n) {
339  return std::min(n.A.y, std::min(n.B.y, std::min(n.C.y, n.D.y)));
340  }
341  static double tet_max_y(const EGS_Mesh::Nodes &n) {
342  return std::max(n.A.y, std::max(n.B.y, std::max(n.C.y, n.D.y)));
343  }
344  static double tet_min_z(const EGS_Mesh::Nodes &n) {
345  return std::min(n.A.z, std::min(n.B.z, std::min(n.C.z, n.D.z)));
346  }
347  static double tet_max_z(const EGS_Mesh::Nodes &n) {
348  return std::max(n.A.z, std::max(n.B.z, std::max(n.C.z, n.D.z)));
349  }
350 
351  // An axis-aligned bounding box.
352  struct BoundingBox {
353  double min_x = 0.0;
354  double max_x = 0.0;
355  double min_y = 0.0;
356  double max_y = 0.0;
357  double min_z = 0.0;
358  double max_z = 0.0;
359  BoundingBox() = default;
360  BoundingBox(double min_x, double max_x, double min_y, double max_y,
361  double min_z, double max_z) : min_x(min_x), max_x(max_x),
362  min_y(min_y), max_y(max_y), min_z(min_z), max_z(max_z) {}
363  double mid_x() const {
364  return (min_x + max_x) / 2.0;
365  }
366  double mid_y() const {
367  return (min_y + max_y) / 2.0;
368  }
369  double mid_z() const {
370  return (min_z + max_z) / 2.0;
371  }
372  double volume() const {
373  return (max_x - min_x) * (max_y - min_y) * (max_z - min_z);
374  }
375  void expand(double delta) {
376  min_x -= delta;
377  min_y -= delta;
378  min_z -= delta;
379  max_x += delta;
380  max_y += delta;
381  max_z += delta;
382  }
383  void print(std::ostream &out = std::cout) const {
384  out <<
385  std::setprecision(std::numeric_limits<double>::max_digits10) <<
386  "min_x: " << min_x << "\n";
387  out <<
388  std::setprecision(std::numeric_limits<double>::max_digits10) <<
389  "max_x: " << max_x << "\n";
390  out <<
391  std::setprecision(std::numeric_limits<double>::max_digits10) <<
392  "min_y: " << min_y << "\n";
393  out <<
394  std::setprecision(std::numeric_limits<double>::max_digits10) <<
395  "max_y: " << max_y << "\n";
396  out <<
397  std::setprecision(std::numeric_limits<double>::max_digits10) <<
398  "min_z: " << min_z << "\n";
399  out <<
400  std::setprecision(std::numeric_limits<double>::max_digits10) <<
401  "max_z: " << max_z << "\n";
402  }
403 
404  // Adapted from Ericson section 5.2.9 "Testing AABB Against Triangle".
405  // Uses a separating axis approach, as originally presented in Akenine-
406  // Möller's "Fast 3D Triangle-Box Overlap Testing" with 13 axes checked
407  // in total. There are three axis categories, and it is suggested the
408  // fastest way to check is 3, 1, 2.
409  //
410  // We use a more straightforward but less optimized formulation of the
411  // separating axis test than Ericson presents, because this test is
412  // intended to be done as part of the octree setup but not during the
413  // actual simulation.
414  //
415  // This routine should be robust for ray edges parallel with bounding
416  // box edges (category 3) but does not attempt to be robust for the case
417  // of degenerate triangle face normals (category 2). See Ericson 5.2.1.1
418  //
419  // The non-robustness of some cases should not be an issue for the most
420  // part as these will likely be false positives (harmless extra checks)
421  // instead of false negatives (missed intersections, a huge problem if
422  // present).
423  bool intersects_triangle(const EGS_Vector &a, const EGS_Vector &b,
424  const EGS_Vector &c) const {
425  if (min3(a.x, b.x, c.x) >= max_x ||
426  min3(a.y, b.y, c.y) >= max_y ||
427  min3(a.z, b.z, c.z) >= max_z ||
428  max3(a.x, b.x, c.x) <= min_x ||
429  max3(a.y, b.y, c.y) <= min_y ||
430  max3(a.z, b.z, c.z) <= min_z) {
431  return false;
432  }
433 
434  EGS_Vector centre(mid_x(), mid_y(), mid_z());
435  // extents
436  EGS_Float ex = (max_x - min_x) / 2.0;
437  EGS_Float ey = (max_y - min_y) / 2.0;
438  EGS_Float ez = (max_z - min_z) / 2.0;
439 
440  // move triangle to bounding box origin
441  EGS_Vector v0 = a - centre;
442  EGS_Vector v1 = b - centre;
443  EGS_Vector v2 = c - centre;
444 
445  // find triangle edge vectors
446  const std::array<EGS_Vector, 3> edge_vecs { v1-v0, v2-v1, v0-v2 };
447 
448  // Test the 9 category 3 axes (cross products between axis-aligned
449  // bounding box unit vectors and triangle edge vectors)
450  const EGS_Vector ux {1, 0, 0}, uy {0, 1, 0}, uz {0, 0, 1};
451  const std::array<EGS_Vector, 3> unit_vecs { ux, uy, uz};
452  for (const EGS_Vector &u : unit_vecs) {
453  for (const EGS_Vector &f : edge_vecs) {
454  const EGS_Vector a = cross(u, f);
455  if (is_zero(a)) {
456  // Ignore testing this axis, likely won't be a separating
457  // axis. This may lead to false positives, but not false
458  // negatives.
459  continue;
460  }
461  // find box projection radius
462  const EGS_Float r = ex * std::abs(dot(ux, a)) +
463  ey * std::abs(dot(uy, a)) + ez * std::abs(dot(uz, a));
464  // find three projections onto axis a
465  const EGS_Float p0 = dot(v0, a);
466  const EGS_Float p1 = dot(v1, a);
467  const EGS_Float p2 = dot(v2, a);
468  if (std::max(-max3(p0, p1, p2), min3(p0, p1, p2)) + eps > r) {
469  return false;
470  }
471  }
472  }
473  // category 1 - test overlap with AABB face normals
474  if (max3(v0.x, v1.x, v2.x) <= -ex || min3(v0.x, v1.x, v2.x) >= ex ||
475  max3(v0.y, v1.y, v2.y) <= -ey || min3(v0.y, v1.y, v2.y) >= ey ||
476  max3(v0.z, v1.z, v2.z) <= -ez || min3(v0.z, v1.z, v2.z) >= ez) {
477  return false;
478  }
479 
480  // category 2 - test overlap with triangle face normal using AABB
481  // plane test (5.2.3)
482 
483  // Cross product robustness issues are ignored here (assume
484  // non-degenerate and non-oversize triangles)
485  const EGS_Vector n = cross(edge_vecs[0], edge_vecs[1]);
486  // projection radius
487  const EGS_Float r = ex * std::abs(n.x) + ey * std::abs(n.y) +
488  ez * std::abs(n.z);
489  // distance from box centre to plane
490  //
491  // We have to use `a` here and not `v0` as in my printing since the
492  // bounding box was not translated to the origin. This is a known
493  // erratum, see http://realtimecollisiondetection.net/books/rtcd/errata/
494  const EGS_Float s = dot(n, centre) - dot(n, a);
495  // intersection if s falls within projection radius
496  return std::abs(s) <= r;
497  }
498 
499  bool intersects_tetrahedron(const EGS_Mesh::Nodes &tet) const {
500  return intersects_triangle(tet.A, tet.B, tet.C) ||
501  intersects_triangle(tet.A, tet.C, tet.D) ||
502  intersects_triangle(tet.A, tet.B, tet.D) ||
503  intersects_triangle(tet.B, tet.C, tet.D);
504  }
505 
506  // Adapted from Ericson section 5.3.3 "Intersecting Ray or Segment
507  // Against Box".
508  //
509  // Returns 1 if there is an intersection and 0 if not. If there is an
510  // intersection, the out parameter dist will be the distance along v to
511  // the intersection point q.
512  int ray_intersection(const EGS_Vector &p, const EGS_Vector &v,
513  EGS_Float &dist, EGS_Vector &q) const {
514  // check intersection of ray with three bounding box slabs
515  EGS_Float tmin = 0.0;
516  EGS_Float tmax = std::numeric_limits<EGS_Float>::max();
517  std::array<EGS_Float, 3> p_vec {p.x, p.y, p.z};
518  std::array<EGS_Float, 3> v_vec {v.x, v.y, v.z};
519  std::array<EGS_Float, 3> mins {min_x, min_y, min_z};
520  std::array<EGS_Float, 3> maxs {max_x, max_y, max_z};
521  for (std::size_t i = 0; i < 3; i++) {
522  // Parallel to slab. Point must be within slab bounds to hit
523  // the bounding box
524  if (std::abs(v_vec[i]) < eps) {
525  // Outside slab bounds
526  if (p_vec[i] < mins[i] || p_vec[i] > maxs[i]) {
527  return 0;
528  }
529  }
530  else {
531  // intersect ray with slab planes
532  EGS_Float inv_vel = 1.0 / v_vec[i];
533  EGS_Float t1 = (mins[i] - p_vec[i]) * inv_vel;
534  EGS_Float t2 = (maxs[i] - p_vec[i]) * inv_vel;
535  // convention is t1 is near plane, t2 is far plane
536  if (t1 > t2) {
537  std::swap(t1, t2);
538  }
539  tmin = std::max(tmin, t1);
540  tmax = std::min(tmax, t2);
541  if (tmin > tmax) {
542  return 0;
543  }
544  }
545  }
546  q = p + v * tmin;
547  dist = tmin;
548  return 1;
549  }
550 
551  // Given an interior point, return the minimum distance to a boundary.
552  // This is a helper method for hownear, as the minimum of the boundary
553  // distance and tetrahedron distance will be hownear's result.
554  //
555  // TODO maybe need to clamp to 0.0 here for results slightly under zero?
556  EGS_Float min_interior_distance(const EGS_Vector &point) const {
557  return std::min(point.x - min_x, std::min(point.y - min_y,
558  std::min(point.z - min_z, std::min(max_x - point.x,
559  std::min(max_y - point.y, max_z - point.z)))));
560  }
561 
562  // Returns the closest point on the bounding box to the given point.
563  // If the given point is inside the bounding box, it is considered the
564  // closest point. This method is intended for use by hownear, to decide
565  // where to search first.
566  //
567  // See section 5.1.3 Ericson.
568  EGS_Vector closest_point(const EGS_Vector &point) const {
569  std::array<EGS_Float, 3> p = {point.x, point.y, point.z};
570  std::array<EGS_Float, 3> mins = {min_x, min_y, min_z};
571  std::array<EGS_Float, 3> maxs = {max_x, max_y, max_z};
572  // set q to p, then clamp it to min/max bounds as needed
573  std::array<EGS_Float, 3> q = p;
574  for (int i = 0; i < 3; i++) {
575  if (p[i] < mins[i]) {
576  q[i] = mins[i];
577  }
578  if (p[i] > maxs[i]) {
579  q[i] = maxs[i];
580  }
581  }
582  return EGS_Vector(q[0], q[1], q[2]);
583  }
584 
585  bool contains(const EGS_Vector &point) const {
586  // Inclusive at the lower bound, non-inclusive at the upper bound,
587  // so points on the interface between two bounding boxes only belong
588  // to one of them:
589  //
590  // +---+---+
591  // | x |
592  // +---+---+
593  // ^ belongs here
594  //
595  return point.x >= min_x && point.x < max_x &&
596  point.y >= min_y && point.y < max_y &&
597  point.z >= min_z && point.z < max_z;
598  }
599 
600  bool is_indivisible() const {
601  // check if we're running up against precision limits
602  return approx_eq(min_x, mid_x()) ||
603  approx_eq(max_x, mid_x()) ||
604  approx_eq(min_y, mid_y()) ||
605  approx_eq(max_y, mid_y()) ||
606  approx_eq(min_z, mid_z()) ||
607  approx_eq(max_z, mid_z());
608 
609  }
610 
611  // Split into 8 equal octants. Octant numbering follows an S, i.e:
612  //
613  // -z +z
614  // +---+---+ +---+---+
615  // | 2 | 3 | | 6 | 7 |
616  // y +---+---+ +---+---+
617  // ^ | 0 | 1 | | 4 | 5 |
618  // | +---+---+ +---+---+
619  // + -- > x
620  //
621  std::array<BoundingBox, 8> divide8() const {
622  return {
623  BoundingBox(
624  min_x, mid_x(),
625  min_y, mid_y(),
626  min_z, mid_z()
627  ),
628  BoundingBox(
629  mid_x(), max_x,
630  min_y, mid_y(),
631  min_z, mid_z()
632  ),
633  BoundingBox(
634  min_x, mid_x(),
635  mid_y(), max_y,
636  min_z, mid_z()
637  ),
638  BoundingBox(
639  mid_x(), max_x,
640  mid_y(), max_y,
641  min_z, mid_z()
642  ),
643  BoundingBox(
644  min_x, mid_x(),
645  min_y, mid_y(),
646  mid_z(), max_z
647  ),
648  BoundingBox(
649  mid_x(), max_x,
650  min_y, mid_y(),
651  mid_z(), max_z
652  ),
653  BoundingBox(
654  min_x, mid_x(),
655  mid_y(), max_y,
656  mid_z(), max_z
657  ),
658  BoundingBox(
659  mid_x(), max_x,
660  mid_y(), max_y,
661  mid_z(), max_z
662  )
663  };
664  }
665  };
666  struct Node {
667  std::vector<int> elts_;
668  std::vector<Node> children_;
669  BoundingBox bbox_;
670 
671  Node() = default;
672  Node(const std::vector<int> &elts, const BoundingBox &bbox,
673  std::size_t n_max, const EGS_Mesh &mesh,
674  egs_mesh::internal::PercentCounter &progress) : bbox_(bbox) {
675  // TODO: max level and precision warning
676  if (bbox_.is_indivisible() || elts.size() < n_max) {
677  elts_ = elts;
678  progress.step(bbox_.volume());
679  return;
680  }
681 
682  std::array<std::vector<int>, 8> octants;
683  std::array<BoundingBox, 8> bbs = bbox_.divide8();
684 
685  // elements may be in more than one bounding box
686  for (const auto &e : elts) {
687  for (int i = 0; i < 8; i++) {
688  if (bbs[i].intersects_tetrahedron(mesh.element_nodes(e))) {
689  octants[i].push_back(e);
690  }
691  }
692  }
693  for (int i = 0; i < 8; i++) {
694  children_.push_back(Node(
695  std::move(octants[i]), bbs[i], n_max, mesh, progress
696  ));
697  }
698  }
699 
700  bool isLeaf() const {
701  return children_.empty();
702  }
703 
704  void print(std::ostream &out, int level) const {
705  out << "Level " << level << "\n";
706  bbox_.print(out);
707  if (children_.empty()) {
708  out << "num_elts: " << elts_.size() << "\n";
709  for (const auto &e: elts_) {
710  out << e << " ";
711  }
712  out << "\n";
713  return;
714  }
715  for (int i = 0; i < 8; i++) {
716  children_.at(i).print(out, level + 1);
717  }
718  }
719 
720  int findOctant(const EGS_Vector &p) const {
721  // Our choice of octant ordering (see BoundingBox.divide8) means we
722  // can determine the correct octant with three checks. E.g. octant 0
723  // is (-x, -y, -z), octant 1 is (+x, -y, -z), octant 4 is (-x, -y, +z)
724  // octant 7 is (+x, +y, +z), etc.
725  std::size_t octant = 0;
726  if (p.x >= bbox_.mid_x()) {
727  octant += 1;
728  };
729  if (p.y >= bbox_.mid_y()) {
730  octant += 2;
731  };
732  if (p.z >= bbox_.mid_z()) {
733  octant += 4;
734  };
735  return octant;
736  }
737 
738  // Octants are returned ordered by minimum intersection distance
739  std::vector<int> findOtherIntersectedOctants(const EGS_Vector &p,
740  const EGS_Vector &v, int exclude_octant) const {
741  if (isLeaf()) {
742  throw std::runtime_error(
743  "findOtherIntersectedOctants called on leaf node");
744  }
745  // Perf note: this function was changed to use std::array, but there
746  // wasn't any observed performance change during benchmarking. Since
747  // the std::array logic was more complicated, the std::vector impl
748  // was kept.
749  std::vector<std::pair<EGS_Float, int>> intersections;
750  for (int i = 0; i < 8; i++) {
751  if (i == exclude_octant) {
752  continue;
753  }
754  EGS_Vector intersection;
755  EGS_Float dist;
756  if (children_[i].bbox_.ray_intersection(p, v, dist, intersection)) {
757  intersections.push_back({dist, i});
758  }
759  }
760  std::sort(intersections.begin(), intersections.end());
761  std::vector<int> octants;
762  for (const auto &i : intersections) {
763  octants.push_back(i.second);
764  }
765  return octants;
766  }
767 
768  // Leaf node: search all bounded elements, returning the minimum
769  // distance to a boundary tetrahedron or a bounding box surface.
770  EGS_Float hownear_leaf_search(const EGS_Vector &p, EGS_Mesh &mesh) const {
771  const EGS_Float best_dist = bbox_.min_interior_distance(p);
772  // Use squared distance to avoid computing square roots in the
773  // loop. This has the added bonus of ridding ourselves of any
774  // negatives from near-zero floating-point issues
775  EGS_Float best_dist2 = best_dist * best_dist;
776  for (const auto &e: elts_) {
777  const auto &n = mesh.element_nodes(e);
778  best_dist2 = std::min(best_dist2, distance2(p,
779  closest_point_tetrahedron(p, n.A, n.B, n.C, n.D)));
780  }
781  return std::sqrt(best_dist2);
782  }
783 
784  EGS_Float hownear_exterior(const EGS_Vector &p, EGS_Mesh &mesh) const {
785  // Leaf node: find a lower bound on the mesh exterior distance
786  // closest distance
787  if (isLeaf()) {
788  return hownear_leaf_search(p, mesh);
789  }
790  // Parent node: decide which octant to search and descend the tree
791  const auto octant = findOctant(p);
792  return children_[octant].hownear_exterior(p, mesh);
793  }
794 
795  // Does not mutate the EGS_Mesh.
796  int isWhere(const EGS_Vector &p, /*const*/ EGS_Mesh &mesh) const {
797  // Leaf node: search all bounded elements, returning -1 if the
798  // element wasn't found.
799  if (isLeaf()) {
800  for (const auto &e: elts_) {
801  if (mesh.insideElement(e, p)) {
802  return e;
803  }
804  }
805  return -1;
806  }
807 
808  // Parent node: decide which octant to search and descend the tree
809  return children_[findOctant(p)].isWhere(p, mesh);
810  }
811 
812  // TODO split into two functions
813  int howfar_exterior(const EGS_Vector &p, const EGS_Vector &v,
814  const EGS_Float &max_dist, EGS_Float &t, /* const */ EGS_Mesh &mesh)
815  const {
816  // Leaf node: check for intersection with any boundary elements
817  EGS_Float min_dist = std::numeric_limits<EGS_Float>::max();
818  int min_elt = -1;
819  if (isLeaf()) {
820  for (const auto &e: elts_) {
821  if (!mesh.is_boundary(e)) {
822  continue;
823  }
824  // closest_boundary_face only counts intersections where the
825  // point is on the outside of the face, when it's possible
826  // to intersect the boundary face directly
827  auto intersection = mesh.closest_boundary_face(e, p, v);
828  if (intersection.dist < min_dist) {
829  min_elt = e;
830  min_dist = intersection.dist;
831  }
832  }
833  t = min_dist;
834  return min_elt; // min_elt may be -1 if there is no intersection
835  }
836  // Parent node: decide which octant to search and descend the tree
837  EGS_Vector intersection;
838  EGS_Float dist;
839  auto hit = bbox_.ray_intersection(p, v, dist, intersection);
840  // case 1: there's no intersection with this bounding box, return
841  if (!hit) {
842  return -1;
843  }
844  // case 2: we have a hit. Descend into the most likely intersecting
845  // child octant's bounding box to find any intersecting elements
846  auto octant = findOctant(intersection);
847  auto elt = children_[octant].howfar_exterior(
848  p, v, max_dist, t, mesh
849  );
850  // If we find a valid element, return it
851  if (elt != -1) {
852  return elt;
853  }
854  // Otherwise, if there was no intersection in the most likely
855  // octant, examine the other octants that are intersected by
856  // the ray:
857  for (const auto &o : findOtherIntersectedOctants(p, v, octant)) {
858  auto elt = children_[o].howfar_exterior(
859  p, v, max_dist, t, mesh
860  );
861  // If we find a valid element, return it
862  if (elt != -1) {
863  return elt;
864  }
865  }
866  return -1;
867  }
868  };
869 
870  Node root_;
871 public:
872  EGS_Mesh_Octree() = default;
873  EGS_Mesh_Octree(const std::vector<int> &elts, std::size_t n_max,
874  const EGS_Mesh &mesh, egs_mesh::internal::PercentCounter &progress) {
875  if (elts.empty()) {
876  throw std::runtime_error("EGS_Mesh_Octree: empty elements vector");
877  }
878  if (elts.size() > std::numeric_limits<int>::max()) {
879  throw std::runtime_error("EGS_Mesh_Octree: num elts must fit into an int");
880  }
881 
882  const EGS_Float INF = std::numeric_limits<EGS_Float>::infinity();
883  BoundingBox g_bounds(INF, -INF, INF, -INF, INF, -INF);
884  for (const auto &e : elts) {
885  const auto &nodes = mesh.element_nodes(e);
886  g_bounds.min_x = std::min(g_bounds.min_x, tet_min_x(nodes));
887  g_bounds.max_x = std::max(g_bounds.max_x, tet_max_x(nodes));
888  g_bounds.min_y = std::min(g_bounds.min_y, tet_min_y(nodes));
889  g_bounds.max_y = std::max(g_bounds.max_y, tet_max_y(nodes));
890  g_bounds.min_z = std::min(g_bounds.min_z, tet_min_z(nodes));
891  g_bounds.max_z = std::max(g_bounds.max_z, tet_max_z(nodes));
892  }
893  // Add a small delta around the bounding box to avoid numerical problems
894  // at the boundary
895  g_bounds.expand(1e-8);
896 
897  // Track progress using how much volume has been covered
898  progress.start(g_bounds.volume());
899  root_ = Node(elts, g_bounds, n_max, mesh, progress);
900  }
901 
902  int isWhere(const EGS_Vector &p, /*const*/ EGS_Mesh &mesh) const {
903  if (!root_.bbox_.contains(p)) {
904  return -1;
905  }
906  return root_.isWhere(p, mesh);
907  }
908 
909  void print(std::ostream &out) const {
910  root_.print(out, 0);
911  }
912 
913  int howfar_exterior(const EGS_Vector &p, const EGS_Vector &v,
914  const EGS_Float &max_dist, EGS_Float &t, EGS_Mesh &mesh) const {
915  EGS_Vector intersection;
916  EGS_Float dist;
917  auto hit = root_.bbox_.ray_intersection(p, v, dist, intersection);
918  if (!hit || dist > max_dist) {
919  return -1;
920  }
921  return root_.howfar_exterior(p, v, max_dist, t, mesh);
922  }
923 
924  // Returns a lower bound on the distance to the mesh exterior boundary.
925  // The actual distance to the mesh may be larger, i.e. a distance to an
926  // axis-aligned bounding box might be returned instead. This is allowed by
927  // the HOWNEAR spec, PIRS-701 section 3.6, "Specifications for HOWNEAR":
928  //
929  // > In complex geometries, the mathematics of HOWNEAR can become difficult
930  // and sometimes almost impossible! If it is easier for the user to
931  // compute some lower bound to the nearest distance, this could be used...
932  EGS_Float hownear_exterior(const EGS_Vector &p, EGS_Mesh &mesh) const {
933  // If the point is outside the octree bounding box, return the distance
934  // to the bounding box.
935  if (!root_.bbox_.contains(p)) {
936  return distance(root_.bbox_.closest_point(p), p);
937  }
938  // Otherwise, descend the octree
939  return root_.hownear_exterior(p, mesh);
940  }
941 };
943 
945  EGS_BaseGeometry(EGS_BaseGeometry::getUniqueName()) {
946  spec.checkValid();
947  initializeElements(std::move(spec.elements), std::move(spec.nodes),
948  std::move(spec.media));
949  initializeNeighbours();
950  initializeOctrees();
951  initializeNormals();
952 }
953 
954 void EGS_Mesh::initializeElements(
955  std::vector<EGS_MeshSpec::Tetrahedron> elements,
956  std::vector<EGS_MeshSpec::Node> nodes,
957  std::vector<EGS_MeshSpec::Medium> materials) {
958  EGS_BaseGeometry::nreg = elements.size();
959 
960  elt_tags_.reserve(elements.size());
961  elt_node_indices_.reserve(elements.size());
962  nodes_.reserve(nodes.size());
963 
964  std::unordered_map<int, int> node_map;
965  node_map.reserve(nodes.size());
966  for (int i = 0; i < static_cast<int>(nodes.size()); i++) {
967  const auto &n = nodes[i];
968  node_map.insert({n.tag, i});
969  nodes_.push_back(EGS_Vector(n.x, n.y, n.z));
970  }
971  if (node_map.size() != nodes.size()) {
972  throw std::runtime_error("duplicate nodes in node list");
973  }
974  // Find the matching node indices for every tetrahedron
975  auto find_node = [&](int node_tag) -> int {
976  auto node_it = node_map.find(node_tag);
977  if (node_it == node_map.end()) {
978  throw std::runtime_error("No mesh node with tag: " + std::to_string(node_tag));
979  }
980  return node_it->second;
981  };
982  for (int i = 0; i < static_cast<int>(elements.size()); i++) {
983  const auto &e = elements[i];
984  elt_tags_.push_back(e.tag);
985  elt_node_indices_.push_back({
986  find_node(e.a), find_node(e.b), find_node(e.c), find_node(e.d)
987  });
988  }
989 
990  initializeMedia(std::move(elements), std::move(materials));
991 }
992 
993 void EGS_Mesh::initializeMedia(std::vector<EGS_MeshSpec::Tetrahedron> elements,
994  std::vector<EGS_MeshSpec::Medium> materials) {
995  std::unordered_map<int, int> medium_offsets;
996  for (const auto &m : materials) {
997  // If the medium was already registered, returns its offset. For new
998  // media, addMedium adds them to the list and returns the new offset.
999  const int media_offset = EGS_BaseGeometry::addMedium(m.medium_name);
1000  bool inserted = medium_offsets.insert({m.tag, media_offset}).second;
1001  if (!inserted) {
1002  throw std::runtime_error("duplicate medium tag: "
1003  + std::to_string(m.tag));
1004  }
1005  }
1006 
1007  medium_indices_.reserve(elements.size());
1008  for (const auto &e: elements) {
1009  medium_indices_.push_back(medium_offsets.at(e.medium_tag));
1010  }
1011 }
1012 
1013 void EGS_Mesh::initializeNeighbours() {
1014  std::vector<mesh_neighbours::Tetrahedron> neighbour_elts;
1015  neighbour_elts.reserve(num_elements());
1016  for (const auto &e: elt_node_indices_) {
1017  neighbour_elts.emplace_back(mesh_neighbours::Tetrahedron(e[0], e[1], e[2], e[3]));
1018  }
1019 
1020  egs_mesh::internal::PercentCounter progress(get_logger(),
1021  "EGS_Mesh: finding element neighbours");
1022 
1023  neighbours_ = mesh_neighbours::tetrahedron_neighbours(
1024  neighbour_elts, progress);
1025 
1026  progress.finish("EGS_Mesh: found element neighbours");
1027 
1028  boundary_faces_.reserve(num_elements() * 4);
1029  for (const auto &ns: neighbours_) {
1030  for (const auto &n: ns) {
1031  boundary_faces_.push_back(n == mesh_neighbours::NONE);
1032  }
1033  }
1034 }
1035 
1036 void EGS_Mesh::initializeNormals() {
1037  face_normals_.reserve(num_elements());
1038  for (int i = 0; i < static_cast<int>(num_elements()); i++) {
1039  auto get_normal = [](const EGS_Vector& a, const EGS_Vector& b,
1040  const EGS_Vector& c, const EGS_Vector& d) -> EGS_Vector {
1041  EGS_Vector normal = cross(b - a, c - a);
1042  normal.normalize();
1043  if (dot(normal, d-a) < 0) {
1044  normal *= -1.0;
1045  }
1046  return normal;
1047  };
1048  const auto &n = element_nodes(i);
1049  face_normals_.push_back({
1050  get_normal(n.B, n.C, n.D, n.A),
1051  get_normal(n.A, n.C, n.D, n.B),
1052  get_normal(n.A, n.B, n.D, n.C),
1053  get_normal(n.A, n.B, n.C, n.D)
1054  });
1055  }
1056 }
1057 
1058 void EGS_Mesh::initializeOctrees() {
1059  std::vector<int> elts;
1060  std::vector<int> boundary_elts;
1061  elts.reserve(num_elements());
1062  for (int i = 0; i < num_elements(); i++) {
1063  elts.push_back(i);
1064  if (is_boundary(i)) {
1065  boundary_elts.push_back(i);
1066  }
1067  }
1068  // Max element sizes from Furuta et al section 2.1.1
1069  std::size_t n_vol = 200;
1070  egs_mesh::internal::PercentCounter vol_progress(get_logger(),
1071  "EGS_Mesh: building volume octree");
1072  volume_tree_ = std::unique_ptr<EGS_Mesh_Octree>(
1073  new EGS_Mesh_Octree(elts, n_vol, *this, vol_progress)
1074  );
1075  vol_progress.finish("EGS_Mesh: built volume octree");
1076 
1077  std::size_t n_surf = 100;
1078  egs_mesh::internal::PercentCounter surf_progress(get_logger(),
1079  "EGS_Mesh: building surface octree");
1080  surface_tree_ = std::unique_ptr<EGS_Mesh_Octree>(
1081  new EGS_Mesh_Octree(boundary_elts, n_surf, *this, surf_progress)
1082  );
1083  surf_progress.finish("EGS_Mesh: built surface octree");
1084 }
1085 
1086 bool EGS_Mesh::isInside(const EGS_Vector &x) {
1087  return isWhere(x) != -1;
1088 }
1089 
1090 int EGS_Mesh::inside(const EGS_Vector &x) {
1091  return isWhere(x);
1092 }
1093 
1094 int EGS_Mesh::medium(int ireg) const {
1095  return medium_indices_.at(ireg);
1096 }
1097 
1098 bool EGS_Mesh::insideElement(int i, const EGS_Vector &x) { /* const */
1099  const auto &n = element_nodes(i);
1100  if (point_outside_of_plane(x, n.A, n.B, n.C, n.D)) {
1101  return false;
1102  }
1103  if (point_outside_of_plane(x, n.A, n.C, n.D, n.B)) {
1104  return false;
1105  }
1106  if (point_outside_of_plane(x, n.A, n.B, n.D, n.C)) {
1107  return false;
1108  }
1109  if (point_outside_of_plane(x, n.B, n.C, n.D, n.A)) {
1110  return false;
1111  }
1112  return true;
1113 }
1114 
1115 int EGS_Mesh::isWhere(const EGS_Vector &x) {
1116  return volume_tree_->isWhere(x, *this);
1117 }
1118 
1119 EGS_Float EGS_Mesh::hownear(int ireg, const EGS_Vector &x) {
1120  // inside
1121  if (ireg >= 0) {
1122  return min_interior_face_dist(ireg, x);
1123  }
1124  // outside
1125  return min_exterior_face_dist(x);
1126 }
1127 
1128 // Assumes the input normal is normalized. Returns the absolute value of the
1129 // distance.
1130 EGS_Float distance_to_plane(const EGS_Vector &x,
1131  const EGS_Vector &unit_plane_normal, const EGS_Vector &plane_point) {
1132  return std::abs(dot(unit_plane_normal, x - plane_point));
1133 }
1134 
1135 EGS_Float EGS_Mesh::min_interior_face_dist(int ireg, const EGS_Vector &x) {
1136  const auto &n = element_nodes(ireg);
1137 
1138  // First face is BCD, second is ACD, third is ABD, fourth is ABC
1139  EGS_Float min_dist = distance_to_plane(x, face_normals_[ireg][0], n.B);
1140  min_dist = std::min(min_dist,
1141  distance_to_plane(x, face_normals_[ireg][1], n.A));
1142  min_dist = std::min(min_dist,
1143  distance_to_plane(x, face_normals_[ireg][2], n.A));
1144  min_dist = std::min(min_dist,
1145  distance_to_plane(x, face_normals_[ireg][3], n.A));
1146 
1147  return min_dist;
1148 }
1149 
1150 EGS_Float EGS_Mesh::min_exterior_face_dist(const EGS_Vector &x) {
1151  return surface_tree_->hownear_exterior(x, *this);
1152 }
1153 
1154 int EGS_Mesh::howfar(int ireg, const EGS_Vector &x, const EGS_Vector &u,
1155  EGS_Float &t, int *newmed /* =0 */, EGS_Vector *normal /* =0 */) {
1156  if (ireg < 0) {
1157  return howfar_exterior(x, u, t, newmed, normal);
1158  }
1159  // Find the minimum distance to an element boundary. If this distance is
1160  // smaller than the intended step, adjust the step length and return the
1161  // neighbouring element. If the step length is larger than the distance to a
1162  // boundary, don't adjust it upwards! This will break particle transport.
1163  EGS_Float distance_to_boundary = veryFar;
1164  auto new_reg = howfar_interior(
1165  ireg, x, u, distance_to_boundary, newmed, normal);
1166 
1167  if (distance_to_boundary < t) {
1168  t = distance_to_boundary;
1169  return new_reg;
1170  }
1171  // Otherwise, return the current region and don't change the value of t.
1172  return ireg;
1173 }
1174 
1175 // howfar_interior is the most complicated EGS_BaseGeometry method. Apart from
1176 // the intersection logic, there are exceptional cases that must be carefully
1177 // handled. In particular, the region number and the position `x` may not agree.
1178 // For example, the region may be 1, but the position x may be slightly outside
1179 // of region 1 because of numerical undershoot. The region number takes priority
1180 // because it is where EGS thinks the particle should be based on the simulation
1181 // so far, and we assume steps like boundary crossing calculations have already
1182 // taken place. So we have to do our best to calculate intersections as if the
1183 // position really is inside the given tetrahedron.
1184 int EGS_Mesh::howfar_interior(int ireg, const EGS_Vector &x, const EGS_Vector &u,
1185  EGS_Float &t, int *newmed, EGS_Vector *normal) {
1186  // General idea is to use ray-plane intersection because it's watertight and
1187  // uses less flops than ray-triangle intersection. To my understanding, this
1188  // approach is also used by Geant, PHITS and MCNP.
1189  //
1190  // Because planes are infinite, intersections can be found far away from
1191  // the actual tetrahedron bounds if the particle is travelling away from the
1192  // element. So we limit valid intersections with planes by element face
1193  // distances and normal angles.
1194  //
1195  // OK, if d < eps, and theta > -angle_eps
1196  //
1197  // |<-d->| | /
1198  // | | i /
1199  // | n * | /
1200  // * -> | --> | | /
1201  // | v |/
1202  // | /
1203  // | /
1204  // x
1205  //
1206  // There is a chance that floating-point rounding may cause computed
1207  // quantities to be inconsistent. The most important thing is the simulation
1208  // should try to continue by always at least taking a small step. Returning
1209  // 0.0 opens the door to hanging the transport routine entirely and for
1210  // enough histories, it will almost certainly hang. Even returning a small
1211  // step does not ensure forward progress for all possible input meshes,
1212  // since if the step is too small, it may be wiped out by rounding error
1213  // after being added to a large number. But if the step is too large, small
1214  // tetrahedrons may be skipped entirely.
1215 
1216  // TODO add test for transport after intersection point lands right on a
1217  // corner node.
1218 
1219  const auto &n = element_nodes(ireg);
1220  // Pick an arbitrary face point to do plane math with. Face 0 is BCD, Face 1
1221  // is ACD, etc...
1222  std::array<EGS_Vector, 4> face_points {n.B, n.A, n.A, n.A};
1223  std::array<PointLocation, 4> intersect_tests {};
1224  for (int i = 0; i < 4; ++i) {
1225  intersect_tests[i] = find_point_location(
1226  x, u, face_points[i], face_normals_[ireg][i]);
1227  }
1228  // If the particle is not strictly inside the element, try transporting
1229  // along a thick plane.
1230  if (intersect_tests[0].signed_distance < 0.0 ||
1231  intersect_tests[1].signed_distance < 0.0 ||
1232  intersect_tests[2].signed_distance < 0.0 ||
1233  intersect_tests[3].signed_distance < 0.0) {
1234  return howfar_interior_thick_plane(intersect_tests, ireg, x, u, t,
1235  newmed, normal);
1236  }
1237 
1238  // Otherwise, if points are inside the element, calculate the minimum
1239  // distance to a plane.
1240  auto ix = find_interior_intersection(intersect_tests);
1241  if (ix.dist < 0.0 || ix.face_index == -1) {
1242  egsWarning("\nEGS_Mesh warning: bad interior intersection t = %.17g, face_index = %d in region %d: "
1243  "x=(%.17g,%.17g,%.17g) u=(%.17g,%.17g,%.17g)\n", ix.dist, ix.face_index,
1244  ireg, x.x, x.y, x.z, u.x, u.y, u.z);
1246  return ireg;
1247  }
1248  // Very small distances might get swallowed up by rounding error, so enforce
1249  // a minimum step size.
1250  if (ix.dist < EGS_Mesh::min_step_size) {
1251  ix.dist = EGS_Mesh::min_step_size;
1252  }
1253  t = ix.dist;
1254  int new_reg = neighbours_[ireg].at(ix.face_index);
1255  update_medium(new_reg, newmed);
1256  update_normal(face_normals_[ireg].at(ix.face_index), u, normal);
1257  return new_reg;
1258 }
1259 
1260 // Returns the position of the point relative to the face.
1261 EGS_Mesh::PointLocation EGS_Mesh::find_point_location(const EGS_Vector &x,
1262  const EGS_Vector &u, const EGS_Vector &plane_point,
1263  const EGS_Vector &plane_normal) {
1264  // TODO handle degenerate triangles, by not finding any intersections.
1265 
1266  // Face normals point inwards, to the tetrahedron centroid. So if
1267  // dot(u, n) < 0, the particle is travelling towards the plane and will
1268  // intersect it at some point. The intersection point might be outside the
1269  // face's triangular bounds though.
1270  EGS_Float direction_dot_normal = dot(u, plane_normal);
1271 
1272  // Find the signed distance to the plane, to see if it lies in the thick
1273  // plane and so might be a candidate for transport even if it's not strictly
1274  // inside the tetrahedron. This is the distance along the plane normal, not
1275  // the distance along the velocity vector.
1276  //
1277  // (-) (+)
1278  // |
1279  // * |-> n
1280  // |
1281  // |-----+
1282  // d
1283  //
1284  EGS_Float signed_distance = dot(plane_normal, x - plane_point);
1285  return PointLocation(direction_dot_normal, signed_distance);
1286 }
1287 
1288 // Assuming the point is inside the element, find the plane intersection point.
1289 //
1290 // Caller is responsible for checking that each intersections signed_distance is
1291 // >= 0.
1292 EGS_Mesh::Intersection EGS_Mesh::find_interior_intersection(
1293  const std::array<PointLocation, 4> &ixs) {
1294  EGS_Float t_min = veryFar;
1295  int min_face_index = -1;
1296  for (int i = 0; i < 4; ++i) {
1297  // If the particle is travelling away from the face, it will not
1298  // intersect it.
1299  if (ixs[i].direction_dot_normal >= 0.0) {
1300  continue;
1301  }
1302  EGS_Float t_i = -ixs[i].signed_distance / ixs[i].direction_dot_normal;
1303  if (t_i < t_min) {
1304  t_min = t_i;
1305  min_face_index = i;
1306  }
1307  }
1308  // Is there a risk of returning min_face_index = -1? Could t_min also be negative?
1309  return Intersection(t_min, min_face_index);
1310 }
1311 
1312 // Try and transport the particle using thick plane intersections.
1313 int EGS_Mesh::howfar_interior_thick_plane(const std::array<PointLocation, 4> &
1314  intersect_tests, int ireg, const EGS_Vector &x, const EGS_Vector &u,
1315  EGS_Float &t, int *newmed, EGS_Vector *normal) {
1316  // The particle isn't inside the element, but it might be a candidate for
1317  // transport along a thick plane, as long as it is parallel or travelling
1318  // towards the element.
1319  //
1320  // OK not OK not OK
1321  //
1322  // * ^
1323  // - * -> - | - |
1324  // d | | d | v d | *
1325  // -----v--- ------ ------
1326  //
1327 
1328  // Find the largest negative distance to a face plane. If it is bigger
1329  // than the thick plane tolerance, the particle isn't close enough to be
1330  // considered part of the element.
1331  EGS_Float max_neg_dist = veryFar;
1332  int face_index = -1;
1333  for (int i = 0; i < 4; ++i) {
1334  if (intersect_tests[i].signed_distance < max_neg_dist) {
1335  max_neg_dist = intersect_tests[i].signed_distance;
1336  face_index = i;
1337  }
1338  }
1339  if (face_index == -1) {
1340  egsWarning("\nEGS_Mesh warning: howfar_interior_thick_plane face_index %d in region %d: "
1341  "x=(%.17g,%.17g,%.17g) u=(%.17g,%.17g,%.17g)\n",
1342  face_index, ireg, x.x, x.y, x.z, u.x, u.y, u.z);
1344  return ireg;
1345  }
1346 
1347  // If the perpendicular distance to the plane is too big to be a thick
1348  // plane, or if the particle is travelling away from the plane, push the
1349  // particle by a small step and return the region the particle is in.
1350 
1351  // Some small epsilon, TODO maybe look into gamma bounds for this?
1352  // Or EGS_BaseGeometry::BoundaryTolerance?
1353  constexpr EGS_Float thick_plane_bounds = EGS_Mesh::min_step_size;
1354  if (max_neg_dist < -thick_plane_bounds ||
1355  intersect_tests.at(face_index).direction_dot_normal < -thick_plane_bounds) {
1356  return howfar_interior_recover_lost_particle(ireg, x, u, t, newmed);
1357  }
1358 
1359  // If the particle is inside the thick plane and travelling towards a face
1360  // plane, find the intersection point. This might be outside the strict
1361  // bounds of the tetrahedron, but necessary for "wall-riding" particles to
1362  // be transported without tanking simulation efficiency.
1363  //
1364  // \ /
1365  // out \ * -> x
1366  // \--------/
1367  // in \ /
1368  //
1369  EGS_Float t_min = veryFar;
1370  int min_face_index = -1;
1371  for (int i = 0; i < 4; ++i) {
1372  // If the particle is travelling away from the face, it will not
1373  // intersect it.
1374  if (intersect_tests[i].direction_dot_normal >= 0.0) {
1375  continue;
1376  }
1377 
1378  EGS_Float t_i = -intersect_tests[i].signed_distance
1379  / intersect_tests[i].direction_dot_normal;
1380 
1381  if (t_i < t_min) {
1382  t_min = t_i;
1383  min_face_index = i;
1384  }
1385  }
1386 
1387  // Rarely, rounding error near an edge or corner can cause t_min to be
1388  // negative or a very small positive number. For this case, try to recover
1389  // as if the particle is lost.
1390  if (t_min < EGS_Mesh::min_step_size) {
1391  return howfar_interior_recover_lost_particle(ireg, x, u, t, newmed);
1392  }
1393  if (min_face_index == -1) {
1394  egsWarning("\nEGS_Mesh warning: face_index %d in region %d: "
1395  "x=(%.17g,%.17g,%.17g) u=(%.17g,%.17g,%.17g)\n",
1396  min_face_index, ireg, x.x, x.y, x.z, u.x, u.y, u.z);
1398  return ireg;
1399  }
1400  t = t_min;
1401  int new_reg = neighbours_[ireg].at(min_face_index);
1402  update_medium(new_reg, newmed);
1403  update_normal(face_normals_[ireg].at(min_face_index), u, normal);
1404  return new_reg;
1405 }
1406 
1407 /* No valid intersections were found for this particle. Push it along the
1408  momentum vector by a small step and try to recover.
1409 
1410  /\
1411  <- * / \
1412  /____\
1413 
1414  ^^^^ i.e., which region is this particle actually in?
1415 
1416  There are many cases where this could happen, so this routine can't make
1417  too many assumptions. Some possibiltites:
1418 
1419  * The particle is right on an edge and a negative distance was calculated
1420  * The particle is inside the thick plane but travelling away from ireg
1421  * The particle is outside the thick plane but travelling away or towards ireg
1422 */
1423 int EGS_Mesh::howfar_interior_recover_lost_particle(int ireg,
1424  const EGS_Vector &x, const EGS_Vector &u, EGS_Float &t, int *newmed) {
1425  t = EGS_Mesh::min_step_size;
1426  // This may hang if min_step_size is too small for the mesh.
1427  EGS_Vector new_pos = x + u * t;
1428  // Fast path: particle is in a neighbouring element.
1429  for (int i = 0; i < 4; ++i) {
1430  const auto neighbour = neighbours_[ireg][i];
1431  if (neighbour == -1) {
1432  continue;
1433  }
1434  if (insideElement(neighbour, new_pos)) {
1435  update_medium(neighbour, newmed);
1436  return neighbour;
1437  }
1438  }
1439  // Slow path: particle isn't in a neighbouring element, initiate a full
1440  // octree search. This can also happen if the particle is leaving the
1441  // mesh.
1442  auto actual_elt = isWhere(new_pos);
1443  update_medium(actual_elt, newmed);
1444  // We can't determine which normal to display (which is only for
1445  // egs_view in any case), so we don't update the normal for this
1446  // exceptional case.
1447  return actual_elt;
1448 }
1449 
1450 // exclude from doxygen
1452 EGS_Mesh::Intersection EGS_Mesh::closest_boundary_face(int ireg, const EGS_Vector &x,
1453  const EGS_Vector &u) {
1454  assert(is_boundary(ireg));
1455  EGS_Float min_dist = std::numeric_limits<EGS_Float>::max();
1456 
1457  auto dist = min_dist;
1458  auto closest_face = -1;
1459 
1460  auto check_face_intersection = [&](int face, const EGS_Vector& A, const EGS_Vector& B,
1461  const EGS_Vector& C, const EGS_Vector& D) {
1462  if (boundary_faces_[4*ireg + face] &&
1463  // check if the point is on the outside looking in (rather than just
1464  // clipping the edge of a boundary face)
1465  point_outside_of_plane(x, A, B, C, D) &&
1466  dot(face_normals_[ireg][face], u) > 0.0 && // point might be in a thick plane
1467  exterior_triangle_ray_intersection(x, u, A, B, C, dist) &&
1468  dist < min_dist) {
1469  min_dist = dist;
1470  closest_face = face;
1471  }
1472  };
1473 
1474  const auto &n = element_nodes(ireg);
1475  // face 0 (BCD), face 1 (ACD) etc.
1476  check_face_intersection(0, n.B, n.C, n.D, n.A);
1477  check_face_intersection(1, n.A, n.C, n.D, n.B);
1478  check_face_intersection(2, n.A, n.B, n.D, n.C);
1479  check_face_intersection(3, n.A, n.B, n.C, n.D);
1480 
1481  return EGS_Mesh::Intersection(min_dist, closest_face);
1482 }
1484 
1485 int EGS_Mesh::howfar_exterior(const EGS_Vector &x, const EGS_Vector &u,
1486  EGS_Float &t, int *newmed, EGS_Vector *normal) {
1487  EGS_Float min_dist = 1e30;
1488  auto min_reg = surface_tree_->howfar_exterior(x, u, t, min_dist, *this);
1489 
1490  // no intersection
1491  if (min_dist > t || min_reg == -1) {
1492  return -1;
1493  }
1494 
1495  // intersection found, update out parameters
1496  t = min_dist;
1497  if (newmed) {
1498  *newmed = medium(min_reg);
1499  }
1500  if (normal) {
1501  auto intersection = closest_boundary_face(min_reg, x, u);
1502  EGS_Vector tmp_normal = face_normals_[min_reg]
1503  .at(intersection.face_index);
1504  // egs++ convention is normal pointing opposite view ray
1505  if (dot(tmp_normal, u) > 0) {
1506  tmp_normal = -1.0 * tmp_normal;
1507  }
1508  *normal = tmp_normal;
1509  }
1510  return min_reg;
1511 }
1512 
1513 const std::string EGS_Mesh::type = "EGS_Mesh";
1514 
1515 void EGS_Mesh::printInfo() const {
1517  std::ostringstream oss;
1518  printElement(0, oss);
1519  egsInformation(oss.str().c_str());
1520 }
1521 
1522 // Parse a mesh file from the input file to an EGS_MeshSpec.
1523 //
1524 // Supported file types are:
1525 // * Gmsh msh4.1 (.msh)
1526 // * TetGen elt and node file pairs (.ele, .node)
1527 //
1528 // Throws a std::runtime_error if parsing fails.
1529 static EGS_MeshSpec parse_mesh_file(const std::string &mesh_file) {
1530  // Needs to be at least four characters long (.ele, .msh)
1531  auto ends_with = [](const std::string& str, const std::string& suffix)
1532  -> bool {
1533  if (suffix.size() > str.size()) {
1534  return false;
1535  }
1536  return str.compare(str.size() - suffix.size(), str.size(), suffix) == 0;
1537  };
1538 
1539  if (ends_with(mesh_file, ".msh")) {
1540  std::ifstream file_stream(mesh_file);
1541  if (!file_stream) {
1542  throw std::runtime_error(std::string("mesh file: `") + mesh_file
1543  + "` does not exist or is not readable");
1544  }
1545  try {
1546  return msh_parser::parse_msh_file(file_stream, egsInformation);
1547  }
1548  catch (const std::runtime_error &e) {
1549  throw std::runtime_error(std::string("Gmsh msh file parsing failed")
1550  + "\nerror: " + e.what() + "\n");
1551  }
1552  }
1553 
1554  if (ends_with(mesh_file, ".ele")) {
1555  return tetgen_parser::parse_tetgen_files(mesh_file,
1556  tetgen_parser::TetGenFile::Ele, egsInformation);
1557  }
1558 
1559  if (ends_with(mesh_file, ".node")) {
1560  return tetgen_parser::parse_tetgen_files(mesh_file,
1561  tetgen_parser::TetGenFile::Node, egsInformation);
1562  }
1563 
1564  throw std::runtime_error(std::string("unknown extension for mesh file `")
1565  + mesh_file + "`, supported extensions are msh, ele, node");
1566 }
1567 
1568 extern "C" {
1569  EGS_MESH_EXPORT EGS_BaseGeometry *createGeometry(EGS_Input *input) {
1570  if (!input) {
1571  egsWarning("createGeometry(EGS_Mesh): null input\n");
1572  return nullptr;
1573  }
1574  std::string mesh_file;
1575  int err = input->getInput("file", mesh_file);
1576  if (err) {
1577  egsWarning("createGeometry(EGS_Mesh): no mesh file key `file` in input\n");
1578  return nullptr;
1579  }
1580 
1581  EGS_MeshSpec mesh_spec;
1582  try {
1583  mesh_spec = parse_mesh_file(mesh_file);
1584  }
1585  catch (const std::runtime_error &e) {
1586  std::string error_msg = std::string("createGeometry(EGS_Mesh): ") +
1587  e.what() + "\n";
1588  egsWarning("\n%s", error_msg.c_str());
1589  return nullptr;
1590  }
1591 
1592  EGS_Float scale = 0.0;
1593  err = input->getInput("scale", scale);
1594  if (!err) {
1595  if (scale > 0.0) {
1596  mesh_spec.scale(scale);
1597  }
1598  else {
1599  egsFatal("createGeometry(EGS_Mesh): invalid scale value (%g), "
1600  "expected a positive number\n", scale);
1601  }
1602  }
1603 
1604  EGS_Mesh *mesh = nullptr;
1605  try {
1606  mesh = new EGS_Mesh(std::move(mesh_spec));
1607  }
1608  catch (const std::runtime_error &e) {
1609  std::string error_msg = std::string("createGeometry(EGS_Mesh): ") +
1610  "bad input to EGS_Mesh\nerror: " + e.what() + "\n";
1611  egsWarning("\n%s", error_msg.c_str());
1612  return nullptr;
1613  }
1614 
1615  mesh->setBoundaryTolerance(input);
1616  mesh->setName(input);
1617  mesh->setLabels(input);
1618  return mesh;
1619  }
1620 }
void checkValid() const
Definition: egs_mesh.cpp:81
virtual void printInfo() const
Print information about this geometry.
A container for raw unstructured tetrahedral mesh data.
Definition: egs_mesh.h:94
EGS_Input class header file.
EGS_Vector methods for the manipulation of 3D vectors in cartesian co-ordinates.
EGS_Float y
y-component
Definition: egs_vector.h:61
void scale(EGS_Float factor)
Multiply all node coordinates by a constant factor.
Definition: egs_mesh.h:144
Tetrahedral mesh geometry: header.
A class representing 3D vectors.
Definition: egs_vector.h:56
int setLabels(EGS_Input *input)
Set the labels from an input block.
void printElement(int i, std::ostream &elt_info=std::cout) const
Print information about element i to the stream elt_info.
Definition: egs_mesh.h:301
const EGS_Float veryFar
A very large float.
EGS_GLIB_EXPORT EGS_BaseGeometry * createGeometry(EGS_Input *input)
Definition: egs_glib.cpp:57
Base geometry class. Every geometry class must be derived from EGS_BaseGeometry.
void setBoundaryTolerance(EGS_Input *inp)
Set the value of the boundary tolerance from the input inp.
EGS_Float z
z-component
Definition: egs_vector.h:62
EGS_InfoFunction EGS_EXPORT egsFatal
Always use this function for reporting fatal errors.
A tetrahedral mesh geometry.
Definition: egs_mesh.h:242
static int addMedium(const string &medname)
Add a medium or get the index of an existing medium.
EGS_InfoFunction EGS_EXPORT egsInformation
Always use this function for reporting the progress of a simulation and any other type of information...
EGS_Mesh(EGS_MeshSpec spec)
Definition: egs_mesh.cpp:944
void setName(EGS_Input *inp)
Set the name of the geometry from the input inp.
Nodes element_nodes(int element) const
Given an element offset, return the element&#39;s node coordinates.
Definition: egs_mesh.h:364
std::vector< EGS_MeshSpec::Tetrahedron > elements
Unique mesh elements.
Definition: egs_mesh.h:155
std::vector< EGS_MeshSpec::Node > nodes
Unique nodes.
Definition: egs_mesh.h:157
bool is_boundary(int reg) const
Definition: egs_mesh.h:295
EGS_Float x
x-component
Definition: egs_vector.h:60
bool insideElement(int i, const EGS_Vector &x)
Check if a point x is inside element i.
Definition: egs_mesh.cpp:1098
static int error_flag
Set to non-zero status if a geometry problem is encountered.
A class for storing information in a tree-like structure of key-value pairs. This class is used throu...
Definition: egs_input.h:182
void(* EGS_InfoFunction)(const char *,...)
Defines a function printf-like prototype for functions to be used to report info, warnings...
int nreg
Number of local regions in this geometry.
int num_elements() const
Returns the number of mesh elements.
Definition: egs_mesh.h:260
std::vector< EGS_MeshSpec::Medium > media
Unique medium information.
Definition: egs_mesh.h:159
int getInput(const string &key, vector< string > &values) const
Assign values to an array of strings from an input identified by key.
Definition: egs_input.cpp:338
EGS_InfoFunction EGS_EXPORT egsWarning
Always use this function for reporting warnings.