EGSnrc C++ class library  Report PIRS-898 (2021)
Iwan Kawrakow, Ernesto Mainegra-Hing, Frederic Tessier, Reid Townson and Blake Walters
egs_triangle_mesh.cpp
1 /*
2 ###############################################################################
3 #
4 # EGSnrc egs++ triangle mesh geometry library implementation.
5 # Copyright (C) 2022 Max Orok
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: Max Orok, 2022
25 #
26 # Contributors: Alexandre Demelo
27 #
28 ###############################################################################
29 */
30 
31 #include "egs_functions.h"
32 #include "egs_input.h"
33 #include "egs_triangle_mesh.h"
34 
35 #include "stl_parser.h"
36 
37 #include <algorithm>
38 #include <array>
39 #include <stdexcept>
40 #include <limits>
41 
42 namespace { // anonymous namespace for low-level geometry routines
43 
44 static bool EGS_TRIANGLE_MESH_LOCAL inputSet = false;
45 
46 const EGS_Float eps = 1e-8;
47 
48 inline EGS_Float dot(const EGS_Vector &x, const EGS_Vector &y) {
49  return x * y;
50 }
51 
52 inline EGS_Vector cross(const EGS_Vector &x, const EGS_Vector &y) {
53  return x.times(y);
54 }
55 
56 inline EGS_Float distance2(const EGS_Vector &x, const EGS_Vector &y) {
57  return (x - y).length2();
58 }
59 
60 inline EGS_Float distance(const EGS_Vector &x, const EGS_Vector &y) {
61  return std::sqrt(distance2(x, y));
62 }
63 
64 inline EGS_Float min3(EGS_Float a, EGS_Float b, EGS_Float c) {
65  return std::min(std::min(a, b), c);
66 }
67 
68 inline EGS_Float max3(EGS_Float a, EGS_Float b, EGS_Float c) {
69  return std::max(std::max(a, b), c);
70 }
71 
72 inline bool approx_eq(double a, double b, double e = eps) {
73  return (std::abs(a - b) <= e * (std::abs(a) + std::abs(b) + 1.0));
74 } // this is a helper function for the is_indivisible method
75 
76 inline bool is_zero(const EGS_Vector &v) {
77  return approx_eq(0.0, v.length(), eps);
78 }
79 
80 // Test whether the point `p` is in front of the plane defined by the triangle's
81 // normal `n` and sample point `a` (i.e. in the plane's positive halfspace).
82 //
83 // This is a low-level method, callers must add additional logic to handle
84 // various edge cases if required (e.g. point on plane, etc.).
85 bool is_outside_of_triangle_plane(const EGS_Vector &p, const EGS_Vector &n,
86  const EGS_Vector &a) {
87  return dot(n, (p - a)) >= 0.0;
88 }
89 
90 // Find the closest point on triangle ABC to query point P.
91 EGS_Vector closest_point_triangle(const EGS_Vector &P, const EGS_Vector &A, const EGS_Vector &B, const EGS_Vector &C) {
92  // vertex region A
93  EGS_Vector ab = B - A;
94  EGS_Vector ac = C - A;
95  EGS_Vector ao = P - A;
96 
97  EGS_Float d1 = dot(ab, ao);
98  EGS_Float d2 = dot(ac, ao);
99  if (d1 <= 0.0 && d2 <= 0.0) {
100  return A;
101  }
102 
103  // vertex region B
104  EGS_Vector bo = P - B;
105  EGS_Float d3 = dot(ab, bo);
106  EGS_Float d4 = dot(ac, bo);
107  if (d3 >= 0.0 && d4 <= d3) {
108  return B;
109  }
110 
111  // edge region AB
112  EGS_Float vc = d1 * d4 - d3 * d2;
113  if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) {
114  EGS_Float v = d1 / (d1 - d3);
115  return A + v * ab;
116  }
117 
118  // vertex region C
119  EGS_Vector co = P - C;
120  EGS_Float d5 = dot(ab, co);
121  EGS_Float d6 = dot(ac, co);
122  if (d6 >= 0.0 && d5 <= d6) {
123  return C;
124  }
125 
126  // edge region AC
127  EGS_Float vb = d5 * d2 - d1 * d6;
128  if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) {
129  EGS_Float w = d2 / (d2 - d6);
130  return A + w * ac;
131  }
132 
133  // edge region BC
134  EGS_Float va = d3 * d6 - d5 * d4;
135  if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0) {
136  EGS_Float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
137  return B + w * (C - B);
138  }
139 
140  // inside the face
141  EGS_Float denom = 1.0 / (va + vb + vc);
142  EGS_Float v = vb * denom;
143  EGS_Float w = vc * denom;
144  return A + v * ab + w * ac;
145 }
146 
147 // Möller-Trumbore triangle-ray intersection algorithm.
148 //
149 // Returns true if there is an intersection with the triangle abc and false
150 // otherwise. If there is an intersection, the out parameter `dist` is set to
151 // the intersection distance. Only positive values of `dist` are set. Particles
152 // travelling parallel to the triangle are considered to not intersect.
153 bool triangle_ray_intersection(const EGS_Vector &p,
154  const EGS_Vector &v_norm, const EGS_Vector &a, const EGS_Vector &b,
155  const EGS_Vector &c, EGS_Float &dist) {
156  const EGS_Float eps = 1e-10;
157  EGS_Vector ab = b - a;
158  EGS_Vector ac = c - a;
159 
160  EGS_Vector pvec = cross(v_norm, ac);
161  EGS_Float det = dot(ab, pvec);
162 
163  if (det > -eps && det < eps) {
164  return false;
165  }
166  EGS_Float inv_det = 1.0 / det;
167  EGS_Vector tvec = p - a;
168  EGS_Float u = dot(tvec, pvec) * inv_det;
169  if (u < 0.0 || u > 1.0) {
170  return false;
171  }
172  EGS_Vector qvec = cross(tvec, ab);
173  EGS_Float v = dot(v_norm, qvec) * inv_det;
174  if (v < 0.0 || u + v > 1.0) {
175  return false;
176  }
177  // intersection found
178  dist = dot(ac, qvec) * inv_det;
179  // along a negative direction of the ray
180  if (dist < 0.0) {
181  return false;
182  }
183  return true;
184 }
185 
186 } // anonymous namespace
187 
188 // exclude from doxygen
190 class EGS_TriangleMeshBbox {
191 public:
192  EGS_TriangleMeshBbox() = default;
193  EGS_TriangleMeshBbox(double min_x, double max_x, double min_y, double max_y,
194  double min_z, double max_z) : min_x(min_x), max_x(max_x),
195  min_y(min_y), max_y(max_y), min_z(min_z), max_z(max_z) {}
196 
197  void expand(double delta) {
198  min_x -= delta;
199  min_y -= delta;
200  min_z -= delta;
201  max_x += delta;
202  max_y += delta;
203  max_z += delta;
204  }
205  // get the midpoints of the bounding box along each axis to create the octets by dividing boxes into 8
206  double mid_x() const {
207  return (min_x + max_x) / 2.0;
208  }
209  double mid_y() const {
210  return (min_y + max_y) / 2.0;
211  }
212  double mid_z() const {
213  return (min_z + max_z) / 2.0;
214  }
215 
216  bool is_indivisible() const {
217  // check if we're running up against precision limits
218  return approx_eq(min_x, mid_x()) ||
219  approx_eq(max_x, mid_x()) ||
220  approx_eq(min_y, mid_y()) ||
221  approx_eq(max_y, mid_y()) ||
222  approx_eq(min_z, mid_z()) ||
223  approx_eq(max_z, mid_z());
224 
225  }
226 
227  std::array<EGS_TriangleMeshBbox, 8> divide8() const {
228  return {
229  EGS_TriangleMeshBbox(
230  min_x, mid_x(),
231  min_y, mid_y(),
232  min_z, mid_z()
233  ),
234  EGS_TriangleMeshBbox(
235  mid_x(), max_x,
236  min_y, mid_y(),
237  min_z, mid_z()
238  ),
239  EGS_TriangleMeshBbox(
240  min_x, mid_x(),
241  mid_y(), max_y,
242  min_z, mid_z()
243  ),
244  EGS_TriangleMeshBbox(
245  mid_x(), max_x,
246  mid_y(), max_y,
247  min_z, mid_z()
248  ),
249  EGS_TriangleMeshBbox(
250  min_x, mid_x(),
251  min_y, mid_y(),
252  mid_z(), max_z
253  ),
254  EGS_TriangleMeshBbox(
255  mid_x(), max_x,
256  min_y, mid_y(),
257  mid_z(), max_z
258  ),
259  EGS_TriangleMeshBbox(
260  min_x, mid_x(),
261  mid_y(), max_y,
262  mid_z(), max_z
263  ),
264  EGS_TriangleMeshBbox(
265  mid_x(), max_x,
266  mid_y(), max_y,
267  mid_z(), max_z
268  )
269  };
270  }
271 
272  bool contains(const EGS_Vector &point) const {
273  // non-inclusive on the boundary
274  // so points on the interface between two bounding boxes only belong
275  // to one of them:
276  // +---+---+
277  // | x |
278  // +---+---+
279  // ^ belongs here
280  return point.x > min_x && point.x < max_x &&
281  point.y > min_y && point.y < max_y &&
282  point.z > min_z && point.z < max_z;
283  }
284 
285  // Returns the closest point on the bounding box to the given point.
286  // If the given point is inside the bounding box, it is considered the
287  // closest point (should only be called if the point is outside).
288  //
289  // See section 5.1.3 of Ericson's Real-Time Collision Detection.
290  EGS_Vector closest_point(const EGS_Vector &point) const {
291  std::array<EGS_Float, 3> p = {point.x, point.y, point.z};
292  std::array<EGS_Float, 3> mins = {min_x, min_y, min_z};
293  std::array<EGS_Float, 3> maxs = {max_x, max_y, max_z};
294  // set q to p, then clamp it to min/max bounds as needed
295  std::array<EGS_Float, 3> q = p;
296  for (int i = 0; i < 3; i++) {
297  if (p[i] < mins[i]) {
298  q[i] = mins[i];
299  }
300  if (p[i] > maxs[i]) {
301  q[i] = maxs[i];
302  }
303  }
304  return EGS_Vector(q[0], q[1], q[2]);
305  }
306 
307  EGS_Float min_interior_distance(const EGS_Vector &point) const {
308  return std::min(point.x - min_x, std::min(point.y - min_y,
309  std::min(point.z - min_z, std::min(max_x - point.x,
310  std::min(max_y - point.y, max_z - point.z)))));
311  }
312 
313  // Returns 1 if there is an intersection and 0 if not. If there is an
314  // intersection, the out parameter dist will be the distance along v to
315  // the intersection point q.
316  //
317  // Adapted from Ericson section 5.3.3 "Intersecting Ray or Segment
318  // Against Box".
319  int ray_intersection(const EGS_Vector &p, const EGS_Vector &v, EGS_Float &dist, EGS_Vector &q) const {
320  // check intersection of ray with three bounding box slabs
321  EGS_Float tmin = 0.0;
322  EGS_Float tmax = veryFar;
323  std::array<EGS_Float, 3> p_vec {p.x, p.y, p.z};
324  std::array<EGS_Float, 3> v_vec {v.x, v.y, v.z};
325  std::array<EGS_Float, 3> mins {min_x, min_y, min_z};
326  std::array<EGS_Float, 3> maxs {max_x, max_y, max_z};
327  for (std::size_t i = 0; i < 3; i++) {
328  // Parallel to slab. Point must be within slab bounds to hit
329  // the bounding box
330  if (std::abs(v_vec[i]) < 1e-10) {
331  // Outside slab bounds
332  if (p_vec[i] < mins[i] || p_vec[i] > maxs[i]) {
333  return 0;
334  }
335  }
336  else {
337  // intersect ray with slab planes
338  EGS_Float inv_vel = 1.0 / v_vec[i];
339  EGS_Float t1 = (mins[i] - p_vec[i]) * inv_vel;
340  EGS_Float t2 = (maxs[i] - p_vec[i]) * inv_vel;
341  // convention is t1 is near plane, t2 is far plane
342  if (t1 > t2) {
343  std::swap(t1, t2);
344  }
345  tmin = std::max(tmin, t1);
346  tmax = std::min(tmax, t2);
347  if (tmin > tmax) {
348  return 0;
349  }
350  }
351  }
352  q = p + v * tmin;
353  dist = tmin;
354  return 1;
355  }
356 
357  // Adapted from Ericson section 5.2.9 "Testing AABB Against Triangle".
358  // Uses a separating axis approach, as originally presented in Akenine-
359  // Möller's "Fast 3D Triangle-Box Overlap Testing" with 13 axes checked
360  // in total. There are three axis categories, and it is suggested the
361  // fastest way to check is 3, 1, 2.
362  //
363  // We use a more straightforward but less optimized formulation of the
364  // separating axis test than Ericson presents, because this test is
365  // intended to be done as part of the octree setup but not during the
366  // actual simulation.
367  //
368  // This routine should be robust for ray edges parallel with bounding
369  // box edges (category 3) but does not attempt to be robust for the case
370  // of degenerate triangle face normals (category 2). See Ericson 5.2.1.1
371  //
372  // The non-robustness of some cases should not be an issue for the most
373  // part as these will likely be false positives (harmless extra checks)
374  // instead of false negatives (missed intersections, a huge problem if
375  // present).
376  bool intersects_triangle(const EGS_Vector &a, const EGS_Vector &b, const EGS_Vector &c) const {
377  if (min3(a.x, b.x, c.x) >= max_x+1e-10 ||
378  min3(a.y, b.y, c.y) >= max_y+1e-10 ||
379  min3(a.z, b.z, c.z) >= max_z+1e-10 ||
380  max3(a.x, b.x, c.x) <= min_x-1e-10 ||
381  max3(a.y, b.y, c.y) <= min_y-1e-10 ||
382  max3(a.z, b.z, c.z) <= min_z-1e-10) {
383  return false;
384  }
385 
386  EGS_Vector centre(mid_x(), mid_y(), mid_z());
387  // extents
388  EGS_Float ex = (max_x - min_x) / 2.0;
389  EGS_Float ey = (max_y - min_y) / 2.0;
390  EGS_Float ez = (max_z - min_z) / 2.0;
391 
392  // move triangle to bounding box origin
393  EGS_Vector v0 = a - centre;
394  EGS_Vector v1 = b - centre;
395  EGS_Vector v2 = c - centre;
396 
397  // find triangle edge vectors
398  const std::array<EGS_Vector, 3> edge_vecs { v1-v0, v2-v1, v0-v2 };
399 
400  // Test the 9 category 3 axes (cross products between axis-aligned
401  // bounding box unit vectors and triangle edge vectors)
402  const EGS_Vector ux {1, 0, 0}, uy {0, 1, 0}, uz {0, 0, 1};
403  const std::array<EGS_Vector, 3> unit_vecs { ux, uy, uz};
404  for (const EGS_Vector &u : unit_vecs) {
405  for (const EGS_Vector &f : edge_vecs) {
406  const EGS_Vector u_cross_f = cross(u, f);
407  if (is_zero(u_cross_f)) {
408  // Ignore testing this axis, likely won't be a separating
409  // axis. This may lead to false positives, but not false
410  // negatives.
411  continue;
412  }
413  // find box projection radius
414  const EGS_Float r = ex * std::abs(dot(ux, u_cross_f)) + ey * std::abs(dot(uy, u_cross_f)) + ez * std::abs(dot(uz, u_cross_f));
415  // find three projections onto axis u_cross_f
416  const EGS_Float p0 = dot(v0, u_cross_f);
417  const EGS_Float p1 = dot(v1, u_cross_f);
418  const EGS_Float p2 = dot(v2, u_cross_f);
419  if (std::max(-max3(p0, p1, p2), min3(p0, p1, p2)) > r+ 1e-10) {
420  return false;
421  }
422  }
423  }
424  // category 1 - test overlap with AABB face normals
425  if (max3(v0.x, v1.x, v2.x) <= -ex || min3(v0.x, v1.x, v2.x) >= ex ||
426  max3(v0.y, v1.y, v2.y) <= -ey || min3(v0.y, v1.y, v2.y) >= ey ||
427  max3(v0.z, v1.z, v2.z) <= -ez || min3(v0.z, v1.z, v2.z) >= ez) {
428  return false;
429  }
430 
431  // category 2 - test overlap with triangle face normal using AABB
432  // plane test (5.2.3)
433 
434  // Cross product robustness issues are ignored here (assume
435  // non-degenerate and non-oversize triangles)
436  const EGS_Vector n = cross(edge_vecs[0], edge_vecs[1]);
437  // projection radius
438  const EGS_Float r = ex * std::abs(n.x) + ey * std::abs(n.y) + ez * std::abs(n.z);
439  // distance from box centre to plane
440  //
441  // We have to use `a` here and not `v0` as in my printing since the
442  // bounding box was not translated to the origin. This is a known
443  // erratum, see http://realtimecollisiondetection.net/books/rtcd/errata/
444  const EGS_Float s = dot(n, centre) - dot(n, a);
445  // intersection if s falls within projection radius
446  return std::abs(s) <= r;
447  }
448 
449 private:
450  EGS_Float min_x = 0.0;
451  EGS_Float max_x = 0.0;
452  EGS_Float min_y = 0.0;
453  EGS_Float max_y = 0.0;
454  EGS_Float min_z = 0.0;
455  EGS_Float max_z = 0.0;
456 };
457 
458 class TriNode {
459 public:
460  std::vector<int> elts_; // list of elements within this node of the octree (i.e triangles intersecting bbox_)
461  std::vector<TriNode> children_; // the octants that this node is divided into if it is not a leaf (this is empty if it is a leaf)
462  EGS_TriangleMeshBbox bbox_;// region of the total bounding box represented by this octree node
463 
464  TriNode() = default;
465  TriNode(const std::vector<int> &elts, const EGS_TriangleMeshBbox &bbox, std::size_t n_max, const EGS_TriangleMesh &mesh) : bbox_(bbox) {
466  if (bbox_.is_indivisible() || elts.size() < n_max) {
467  elts_ = elts;
468  // this is then a leaf either because it cannot be further divided or has gotten its element number below the required maximum
469  return; // return so no new children are produced (its children is empty is is_leaf() is true)
470  }
471 
472  std::array<std::vector<int>, 8> octants;
473  std::array<EGS_TriangleMeshBbox, 8> bbs = bbox_.divide8();
474 
475  // elements may be in more than one bounding box
476  for (const auto &e : elts) {
477  // get relevant information for triangle corresponding to element e
478  const auto &xs = mesh.triangle_xs(e);
479  const auto &ys = mesh.triangle_ys(e);
480  const auto &zs = mesh.triangle_zs(e);
481  int added = -1;
482  for (int i = 0; i < 8; i++) {
483  // check if the triangle corresponding to element e intersects the bounding box of our current node
484  if (bbs[i].intersects_triangle(EGS_Vector(xs[0], ys[0], zs[0]), EGS_Vector(xs[1], ys[1], zs[1]), EGS_Vector(xs[2], ys[2], zs[2]))) {
485  added++;
486  octants[i].push_back(e);
487  }
488  }
489  if (added==-1) {
490  egsInformation("EGS_TriangleMesh: no octant intersection found for triangle %d with points "
491  "a = (%g, %g, %g) b = (%g, %g, %g) c = (%g, %g, %g)\n",
492  e,
493  xs[0], ys[0], zs[0],
494  xs[1], ys[1], zs[1],
495  xs[2], ys[2], zs[2]);
496  }
497  }
498  for (int i = 0; i < 8; i++) {
499  children_.push_back(TriNode(std::move(octants[i]), bbs[i], n_max, mesh));
500  }
501  }
502 
503  bool isLeaf() const {
504  return children_.empty();
505  }
506 
507  int findOctant(const EGS_Vector &p) const {
508  // Our choice of octant ordering (see BoundingBox.divide8) means we
509  // can determine the correct octant with three checks. E.g. octant 0
510  // is (-x, -y, -z), octant 1 is (+x, -y, -z), octant 4 is (-x, -y, +z)
511  // octant 7 is (+x, +y, +z), etc.
512  std::size_t octant = 0;
513  if (p.x >= bbox_.mid_x()) {
514  octant += 1;
515  };
516  if (p.y >= bbox_.mid_y()) {
517  octant += 2;
518  };
519  if (p.z >= bbox_.mid_z()) {
520  octant += 4;
521  };
522  return octant;
523  }
524 
525  // Octants are returned ordered by minimum intersection distance
526  std::array<std::pair<EGS_Float, int>, 7> findOtherIntersectedOctants(const EGS_Vector &p, const EGS_Vector &v, int exclude_octant, int &n) const {
527  if (isLeaf()) {
528  throw std::runtime_error("findOtherIntersectedOctants called on leaf node");
529  }
530  std::array<std::pair<EGS_Float, int>, 7> intersections;
531  n = 0;
532  for (int i = 0; i < 8; i++) {
533  if (i == exclude_octant) {
534  continue;
535  }
536  EGS_Vector intersection;
537  EGS_Float dist;
538  if (children_[i].bbox_.ray_intersection(p, v, dist, intersection)) {
539  intersections[n++] = {dist, i};
540  }
541  }
542  std::sort(intersections.begin(), intersections.begin() + n);
543  return intersections;
544  }
545 
546  int howfar(const EGS_Vector &x, const EGS_Vector &u, double &min_dist, int &min_tri, const bool inside_mesh, EGS_TriangleMesh &trimesh) const {
547  // leaf
548  if (isLeaf()) {
549  if (elts_.size()>0) {
550  for (const auto &i: elts_) {
551  const auto &xs = trimesh.triangle_xs(i);
552  const auto &ys = trimesh.triangle_ys(i);
553  const auto &zs = trimesh.triangle_zs(i);
554 
555  bool outward_triangle = is_outside_of_triangle_plane(x, trimesh.triangle_normal(i), EGS_Vector(xs[0], ys[0], zs[0]));
556 
557  // if the point is inside the mesh, skip testing all outward triangles
558  if (inside_mesh && outward_triangle) {
559  continue;
560  }
561  // if the point is outside the mesh, skip testing all inward triangles
562  if (!inside_mesh && !outward_triangle) {
563  continue;
564  }
565  // otherwise, test this triangle for intersection
566  double dist = veryFar;
567  trimesh.inctricheck_OHF();
568  if (!triangle_ray_intersection(x, u, EGS_Vector(xs[0], ys[0], zs[0]), EGS_Vector(xs[1], ys[1], zs[1]), EGS_Vector(xs[2], ys[2], zs[2]), dist)) {
569  // no intersection
570  continue;
571  }
572  // intersection, if intersection distance zero, particle is on the plane from its last step. Not true intersection so ignore it
573  if (dist>-1e-10 && dist<1e-10) { // 0-1e-08 < dist && 0+1e-08 > dist
574  continue; // this fixes the floating point bug without modifying step size, as it prevents the particle from going back into the plane it just crossed. Will not count an intersection
575  // if the distance is zero as it means it previously intersected and is now sitting on the plane. Not a valid intersection.
576  }
577 
578  // intersection, and distance is larger than zero so it is a true intersection
579  // update min_dist if smaller
580  if (dist > min_dist) {
581  continue;
582  }
583  min_dist= dist;
584  min_tri = i;
585  }
586  }
587  return min_tri; // return min tri. Program knows there was no intersect in this octant if it is still -1
588  }
589 
590  // TODO: the current traversal resets to the root node on every
591  // octant transition, then searches sibling octants via
592  // findOtherIntersectedOctants. This could be replaced with a
593  // location-code traversal that encodes each leaf position as a
594  // binary code, allowing direct neighbour lookup by bit manipulation
595  // without ascending the whole tree. This could reduce traversal
596  // cost for rays crossing many octant boundaries in fine meshes.
597  //
598  // This was implemented in egs_octree: see getNeighborNodeX in
599  // egs_octree.h for inspiration. The idea is to leverage the fact
600  // that the path to reach any leaf uniquely maps to a bit field.
601  // This generalizes to an octree (using one bit field per axis).
602  // To find the next octant in any direction, we only need to walk
603  // up to the common parent node of adjacent nodes in a given direction,
604  // which is determined by XOR logic. Then we descend directly in
605  // the neighbour.
606  //
607  // The storage overhead in each TriNode would be:
608  //
609  // - a parent pointer
610  // - one level integer
611  // - 3 integer location codes (one per axis)
612  //
613  // This adds 24 bytes for each TriNode, a 25% increase. For a tree
614  // with a depth of d, the computational savings are a factor of d:
615  // For k octant traversals, resetting to the tree root means the
616  // algorithm is ~ O(k*d), whereas with location codes it is
617  // ~ O(k*log2(d)). There would be cache hit degradation on account
618  // of more memory per node, however fewer nodes are hit. Overall,
619  // since the octree currently bottoms out at 30 triangles per node,
620  // the depth d for n triangles is a modest ~ log8(n/30) ~ 5 for up
621  // to a million triangles, for an efficiency gain of ~ 2. At any rate,
622  // this may be worth a try!
623 
624  // parent
625 
626  EGS_Vector intersection;
627  EGS_Float interdist;
628  auto hit = bbox_.ray_intersection(x, u, interdist, intersection);
629  // case 1: there's no intersection with this bounding box, return
630  if (!hit) {
631  return -1;
632  }
633  // case 2: we have a hit. Descend into the most likely intersecting
634  // child octant's bounding box to find any intersecting elements
635  auto octant = findOctant(intersection);
636  auto elt = children_[octant].howfar(x, u, min_dist, min_tri, inside_mesh, trimesh);
637  // If we find a valid element, return it
638  if (elt != -1) {
639  return elt;
640  }
641  // Otherwise, if there was no intersection in the most likely
642  // octant, examine the other octants that are intersected by
643  // the ray:
644  int n_octants = 0;
645  const auto others = findOtherIntersectedOctants(x, u, octant, n_octants);
646  for (int i = 0; i < n_octants; i++) {
647  auto elt = children_[others[i].second].howfar(x, u, min_dist, min_tri, inside_mesh, trimesh);
648  // If we find a valid element, return it
649  if (elt != -1) {
650  return elt;
651  }
652  }
653  return -1;
654 
655  }
656 
657  int isWhere(const EGS_Vector &x, const EGS_Vector &arbitrary_unit_velocity, double &min_dist_interior, double &min_dist_exterior, EGS_TriangleMesh &trimesh) {
658  // leaf
659  if (isLeaf()) {
660  int tri=-1;
661  if (elts_.size()>0) {
662  for (const auto &i: elts_) {
663  const auto &xs = trimesh.triangle_xs(i);
664  const auto &ys = trimesh.triangle_ys(i);
665  const auto &zs = trimesh.triangle_zs(i);
666 
667  // test for intersection
668  double dist = veryFar;
669  trimesh.inctricheck_OIW();
670  // iswhere bug has something to do with this ray intersection check
671  if (!triangle_ray_intersection(x, arbitrary_unit_velocity,
672  EGS_Vector(xs[0], ys[0], zs[0]), EGS_Vector(xs[1], ys[1], zs[1]),
673  EGS_Vector(xs[2], ys[2], zs[2]), dist)) {
674  // no intersection
675  continue;
676  }
677  tri=i;
678  // There's an intersection, check whether it is an inner or outer face.
679  //
680  // TODO check if adding epsilon check around 0.0 (parallel) is important here.
681  if (is_outside_of_triangle_plane(x, trimesh.triangle_normal(i), EGS_Vector(xs[0], ys[0], zs[0]))) {
682  min_dist_exterior = std::min(dist, min_dist_exterior);
683  }
684  else {
685  min_dist_interior = std::min(dist, min_dist_interior);
686  }
687  }
688  }
689  else {
690  }
691  return tri;
692  }
693  // parent
694  EGS_Vector intersection;
695  EGS_Float interdist;
696  auto hit = bbox_.ray_intersection(x, arbitrary_unit_velocity, interdist, intersection);
697  // case 1: there's no intersection with this bounding box, return
698  if (!hit) {
699  return -1;
700  }
701  // case 2: we have a hit. Descend into the most likely intersecting
702  // child octant's bounding box to find any intersecting elements
703  auto octant = findOctant(intersection);
704  auto elt = children_[octant].isWhere(x, arbitrary_unit_velocity, min_dist_interior, min_dist_exterior, trimesh);
705  // If we find a valid element, return it
706  if (elt != -1) {
707  return elt;
708  }
709  // Otherwise, if there was no intersection in the most likely
710  // octant, examine the other octants that are intersected by
711  // the ray:
712  int n_octants = 0;
713  const auto others = findOtherIntersectedOctants(x, arbitrary_unit_velocity, octant, n_octants);
714  for (int i = 0; i < n_octants; i++) {
715  auto elt = children_[others[i].second].isWhere(x, arbitrary_unit_velocity, min_dist_interior, min_dist_exterior, trimesh);
716  // If we find a valid element, return it
717  if (elt != -1) {
718  return elt;
719  }
720  }
721  return -1;
722  }
723 
724  void hownear(const EGS_Vector &x, EGS_Float &min_t, EGS_Vector &min_point, EGS_TriangleMesh &trimesh) {
725  // leaf
726  if (isLeaf()) {
727  // min_t=distance(bbox_.closest_point(x), x);
728  min_t=bbox_.min_interior_distance(x);
729  EGS_Float min_t2=min_t*min_t;
730  for (const auto &i: elts_) {
731  const auto &xs = trimesh.triangle_xs(i);
732  const auto &ys = trimesh.triangle_ys(i);
733  const auto &zs = trimesh.triangle_zs(i);
734 
735  trimesh.inctricheck_OHN();
736  EGS_Vector q = closest_point_triangle(x, EGS_Vector(xs[0], ys[0], zs[0]), EGS_Vector(xs[1], ys[1], zs[1]), EGS_Vector(xs[2], ys[2], zs[2]));
737  EGS_Float dis2 = distance2(q, x);
738 
739  if (dis2 < min_t2) {
740  min_t2 = dis2;
741  min_point = q;
742  }
743  }
744  min_t=std::sqrt(min_t2);
745  return;
746  }
747  // parent
748 
749  // Descend into the leaf octant containing the particle position
750  auto octant = findOctant(x);
751  children_[octant].hownear(x, min_t, min_point, trimesh);
752  // If we find a valid element, return it
753  }
754 };
755 
756 class EGS_TriangleMesh_Octree {
757 private:
758  TriNode root_;
759 public:
760  EGS_TriangleMesh_Octree() = default;
761  EGS_TriangleMesh_Octree(const std::vector<int> &elts, std::size_t n_max,
762  const EGS_TriangleMesh &mesh, EGS_TriangleMeshBbox &basebox) {
763  if (elts.empty()) {
764  throw std::runtime_error("EGS_Mesh_Octree: empty elements vector");
765  }
766  if (elts.size() > std::numeric_limits<int>::max()) {
767  throw std::runtime_error("EGS_Mesh_Octree: num elts must fit into an int");
768  }
769  root_ = TriNode(elts, basebox, n_max, mesh);
770  }
771 
772  int howfar(const EGS_Vector &x, const EGS_Vector &u, double &min_dist, const EGS_Float &max_dist, int &min_tri, const bool inside_mesh, EGS_TriangleMesh &trimesh) const {
773  EGS_Vector intersection;
774  EGS_Float dist;
775  auto hit = root_.bbox_.ray_intersection(x, u, dist, intersection);
776  if (!hit || dist > max_dist) {
777  // ray doesn't reach the bounding box within the current step
778  return -1;
779  }
780  return root_.howfar(x, u, min_dist, min_tri, inside_mesh, trimesh);
781  }
782 
783  int isWhere(const EGS_Vector &x, const EGS_Vector &arbitrary_unit_velocity, double &min_dist_interior, double &min_dist_exterior, EGS_TriangleMesh &trimesh) {
784  if (!root_.bbox_.contains(x)) {
785  return -1;
786  }
787  return root_.isWhere(x, arbitrary_unit_velocity, min_dist_interior, min_dist_exterior, trimesh);
788  }
789 
790  void hownear(const EGS_Vector &x, EGS_Float &min_t, EGS_Vector &min_point, EGS_TriangleMesh &trimesh) {
791  if (!root_.bbox_.contains(x)) {
792  return;
793  }
794  root_.hownear(x, min_t, min_point, trimesh);
795  }
796 };
797 
798 // No checks are done on element validity, triangles are used as-is
799 EGS_TriangleMesh::EGS_TriangleMesh(EGS_TriangleMeshSpec spec, bool oct_set, bool use_stored_normals) :
800  n_tris(spec.elements.size()), EGS_BaseGeometry(EGS_BaseGeometry::getUniqueName()),octree_acc_on(oct_set) {
801 
802  egsInformation("EGS_TriangleMesh: mesh contains %d triangles\n", n_tris);
803  // The volume bounded by the surface mesh is a single transport region.
805 
806  // We could check for less than four elements here, since that's the
807  // minimum required to make a closed 3D surface, but it seems pedantic.
808  if (this->n_tris == 0) {
809  throw std::runtime_error("empty triangles vector in EGS_TriangleMesh constructor");
810  }
811 
812  xs.reserve(this->n_tris);
813  ys.reserve(this->n_tris);
814  zs.reserve(this->n_tris);
815  ns.reserve(this->n_tris);
816 
817  EGS_Float bbox_min_x = veryFar;
818  EGS_Float bbox_min_y = veryFar;
819  EGS_Float bbox_min_z = veryFar;
820  EGS_Float bbox_max_x = -veryFar;
821  EGS_Float bbox_max_y = -veryFar;
822  EGS_Float bbox_max_z = -veryFar;
823 
824  int n_bad_normals = 0;
825  for (const auto &tri: spec.elements) {
826  xs.push_back({tri.a.x, tri.b.x, tri.c.x});
827  ys.push_back({tri.a.y, tri.b.y, tri.c.y});
828  zs.push_back({tri.a.z, tri.b.z, tri.c.z});
829 
830  bbox_min_x = std::min(bbox_min_x, std::min(tri.a.x, std::min(tri.b.x, tri.c.x)));
831  bbox_min_y = std::min(bbox_min_y, std::min(tri.a.y, std::min(tri.b.y, tri.c.y)));
832  bbox_min_z = std::min(bbox_min_z, std::min(tri.a.z, std::min(tri.b.z, tri.c.z)));
833 
834  bbox_max_x = std::max(bbox_max_x, std::max(tri.a.x, std::max(tri.b.x, tri.c.x)));
835  bbox_max_y = std::max(bbox_max_y, std::max(tri.a.y, std::max(tri.b.y, tri.c.y)));
836  bbox_max_z = std::max(bbox_max_z, std::max(tri.a.z, std::max(tri.b.z, tri.c.z)));
837 
838  // Validate and correct triangle normals. Recompute the normal
839  // from the vertex winding order and check it agrees with the
840  // stored normal. Correct silently but count corrections.
841  EGS_Vector ab = tri.b - tri.a;
842  EGS_Vector ac = tri.c - tri.a;
843  EGS_Vector normal = ab.times(ac);
844  EGS_Float normal_len2 = normal.length2();
845  EGS_Float edge_scale = std::max(ab.length2(), ac.length2());
846  if (edge_scale < epsilon || normal_len2 < epsilon * edge_scale) {
847  // Degenerate triangle: cannot recompute normal from winding
848  // order, keep stored normal as-is regardless of normals option.
849  ns.push_back(tri.n);
850  }
851  else {
852  EGS_Vector normal_unit = (1.0 / std::sqrt(normal_len2)) * normal;
853  if (use_stored_normals) {
854  // Trust the stored STL normals, but warn if they disagree
855  // with the winding order.
856  if (tri.n * normal_unit < 0.0) {
857  n_bad_normals++;
858  }
859  ns.push_back(tri.n);
860  } else {
861  // Use normals recomputed from vertex winding order.
862  // Warn if they disagree with the stored normals, but
863  // push normal_unit unconditionally: it is by definition
864  // consistent with the winding order.
865  if (tri.n * normal_unit < 0.0) {
866  n_bad_normals++;
867  }
868  ns.push_back(normal_unit);
869  }
870  }
871  }
872 
873  if (n_bad_normals > 0) {
874  egsWarning("EGS_TriangleMesh: %d triangles had normals inconsistent "
875  "with vertex winding order. Using %s normals.\n",
876  n_bad_normals, use_stored_normals ? "stored" : "calculated");
877  }
878 
879  bbox = std::unique_ptr<EGS_TriangleMeshBbox>(new EGS_TriangleMeshBbox(
880  bbox_min_x, bbox_max_x,
881  bbox_min_y, bbox_max_y,
882  bbox_min_z, bbox_max_z
883  ));
884 
885  // expand bounding box by a small amount to avoid issues at the boundary
886  bbox->expand(1e-8);
887  // below here likely will be the starting point for all the octree initialization stuff
888  // at this point, we have saved all the triangle vertices and normals, we have created and properly sized the bounding box, and the media has been "initialized' by the usual getinput
889  // so we have essentially all we need to get started on creating the octrtee
890  if (getOctBool()) {
891  egsInformation("EGS_TriangleMesh: initializing octree\n");
892  initializeOctree();
893  }
894  else {
895  egsInformation("EGS_TriangleMesh: skip octree creation\n");
896  }
897 }
898 
899 void EGS_TriangleMesh::initializeOctree() {
900  std::vector<int> elts; // this tracks the indices of the triangles in the mesh for the octree to assign to octants
901  // in this case there is no boundary list like the egs_mesh has as it is not relevant. There is only surface elements, no inner or outer elements
902 
903  elts.reserve(num_triangles());
904  for (int i = 0; i < num_triangles(); i++) {
905  elts.push_back(i);
906  }
907  std::size_t n_surf = 30; // the maximum number of elements allowed in a single octant (this may need to be fine tuned for best results)
908  surface_tree_ = std::unique_ptr<EGS_TriangleMesh_Octree>(new EGS_TriangleMesh_Octree(elts, n_surf, *this, *bbox)); // creating the octree (in this case a surface octree i suppose but no point in differentiating)
909  // note that surface tree is an attribute of the triangle mesh, hence how we will access the octree, which will access its root_, which then allows access to all of the other nodes in the tree
910 }
911 
912 EGS_TriangleMesh::~EGS_TriangleMesh() = default;
913 EGS_TriangleMesh::EGS_TriangleMesh(EGS_TriangleMesh &&) = default;
914 EGS_TriangleMesh &EGS_TriangleMesh::operator=(EGS_TriangleMesh &&) = default;
915 
916 
917 bool EGS_TriangleMesh::isInside(const EGS_Vector &x) {
918  return isWhere(x) != -1;
919 }
920 
921 int EGS_TriangleMesh::inside(const EGS_Vector &x) {
922  return isWhere(x);
923 }
924 
925 int EGS_TriangleMesh::isWhere(const EGS_Vector &x) {
926  n_hist++;
927  // Bounding box check to avoid isWhere mesh search
928  if (!bbox->contains(x)) {
929  return -1;
930  }
931 
932  // Pick an arbitrary direction vector, then loop over all elements, testing
933  // for intersection. Compute the minimum distances to an interior face and
934  // an exterior face. If the interior face is closer than the exterior face,
935  // point `x` is inside the mesh (region 0), otherwise it is outside (-1).
936  //
937 
938  const double FRAC_1_SQRT_3 = 0.57735026919;
939  const EGS_Vector arbitrary_unit_velocity(FRAC_1_SQRT_3, FRAC_1_SQRT_3, FRAC_1_SQRT_3);
940 
941  double min_dist_interior = veryFar;
942  double min_dist_exterior = veryFar;
943  if (octree_acc_on) {
944  surface_tree_->isWhere(x, arbitrary_unit_velocity, min_dist_interior, min_dist_exterior, *this);
945  }
946  else {
947  for (int i = 0; i < num_triangles(); i++) {
948  const auto &xs = triangle_xs(i);
949  const auto &ys = triangle_ys(i);
950  const auto &zs = triangle_zs(i);
951 
952  // test for intersection
953  double dist = veryFar;
954  inctricheck_NIW();
955  if (!triangle_ray_intersection(x, arbitrary_unit_velocity,
956  EGS_Vector(xs[0], ys[0], zs[0]), EGS_Vector(xs[1], ys[1], zs[1]),
957  EGS_Vector(xs[2], ys[2], zs[2]), dist)) {
958  // no intersection
959  continue;
960  }
961 
962  // There's an intersection, check whether it is an inner or outer face.
963  //
964  // TODO check if adding epsilon check around 0.0 (parallel) is important here.
965  if (is_outside_of_triangle_plane(x, triangle_normal(i), EGS_Vector(xs[0], ys[0], zs[0]))) {
966  min_dist_exterior = std::min(dist, min_dist_exterior);
967  }
968  else {
969  min_dist_interior = std::min(dist, min_dist_interior);
970  }
971  }
972  }
973 
974  // If there were no intersections found, we must have been outside of the
975  // mesh, ignoring watertightness issues at mesh corners and edges, or if
976  // the point lies exactly on a mesh feature aligned with the fixed test
977  // ray direction (1/√3, 1/√3, 1/√3).
978  if (min_dist_interior == veryFar && min_dist_exterior == veryFar) {
979  return -1;
980  }
981  // If the closest exterior face is closer than the closest interior face,
982  // we are outside the mesh. Less-or-equal (<=) is needed as points outside
983  // the mesh can be intersect both inner and outer faces at corners at the
984  // same distance.
985  if (min_dist_exterior <= min_dist_interior) {
986  return -1;
987  }
988  // Otherwise, we must be inside the region bounded by the mesh.
989  return 0;
990 }
991 
992 EGS_Float EGS_TriangleMesh::hownear(int ireg, const EGS_Vector &x) {
993  // Bounding box check to avoid full mesh search.
994  //
995  // If the point is outside the mesh bounding box, the HOWNEAR spec allows
996  // for returning a lower bound, which in this case is the minimum distance
997  // to the bounding box.
998  if (ireg == -1 && !bbox->contains(x)) {
999  // TODO test potential performance improvement by calculating the
1000  // distance explicitly without finding the closest point.
1001  return distance(bbox->closest_point(x), x);
1002  }
1003 
1004  // naive impl: loop over all elements, finding the global minimum distance
1005 
1006  EGS_Vector min_point = x;
1007  EGS_Float min_t = veryFar;
1008 
1009  if (octree_acc_on) {
1010  surface_tree_->hownear(x, min_t, min_point, *this);
1011  }
1012  else {
1013  for (int i = 0; i < num_triangles(); i++) {
1014  const auto &xs = triangle_xs(i);
1015  const auto &ys = triangle_ys(i);
1016  const auto &zs = triangle_zs(i);
1017 
1018  inctricheck_NHN();
1019  EGS_Vector q = closest_point_triangle(x, EGS_Vector(xs[0], ys[0], zs[0]), EGS_Vector(xs[1], ys[1], zs[1]), EGS_Vector(xs[2], ys[2], zs[2]));
1020  EGS_Float dis = distance(q, x);
1021  if (dis < min_t) {
1022  min_t = dis;
1023  min_point = q;
1024  }
1025  }
1026  }
1027  return min_t;
1028 }
1029 
1030 int EGS_TriangleMesh::howfar(int ireg, const EGS_Vector &x, const EGS_Vector &u,
1031  EGS_Float &t, int *newmed, EGS_Vector *normal) {
1032  // If the particle doesn't intersect the mesh bounding box, it can't intersect the mesh.
1033  EGS_Vector intersection;
1034  EGS_Float dist;
1035  if (!bbox->ray_intersection(x, u, dist, intersection)) {
1036  return -1;
1037  }
1038 
1039  // The old implementation would Loop over all elements, testing for intersection.
1040  // In order to accelerate this process, the octree is used, first we determine
1041  // which octants the ray will intersect, and check only the triangles contained in these octants.
1042 
1043  // If the point is outside the mesh, only outward facing triangles are tested. Otherwise
1044  // if the point is inside the mesh, only inward facing triangles are tested.
1045  double min_dist = veryFar;
1046  int min_tri = -1;
1047  const bool inside_mesh = ireg != -1;
1048 
1049  if (octree_acc_on) {
1050  surface_tree_->howfar(x, u, min_dist, t, min_tri, inside_mesh, *this);
1051  }
1052  else {
1053  for (int i = 0; i < num_triangles(); i++) {
1054  const auto &xs = triangle_xs(i);
1055  const auto &ys = triangle_ys(i);
1056  const auto &zs = triangle_zs(i);
1057 
1058  bool outward_triangle = is_outside_of_triangle_plane(x, triangle_normal(i), EGS_Vector(xs[0], ys[0], zs[0]));
1059 
1060  // if the point is inside the mesh, skip testing all outward triangles
1061  if (inside_mesh && outward_triangle) {
1062  continue;
1063  }
1064  // if the point is outside the mesh, skip testing all inward triangles
1065  if (!inside_mesh && !outward_triangle) {
1066  continue;
1067  }
1068  // otherwise, test this triangle for intersection
1069  double dist = veryFar;
1070  inctricheck_NHF();
1071  if (!triangle_ray_intersection(x, u, EGS_Vector(xs[0], ys[0], zs[0]),
1072  EGS_Vector(xs[1], ys[1], zs[1]), EGS_Vector(xs[2], ys[2], zs[2]), dist)) {
1073  // no intersection
1074  continue;
1075  }
1076  // intersection, if intersection distance zero, particle is on the plane from its last step. Not true intersection so ignore it
1077  if (dist>-1e-10 && dist<1e-10) {
1078  continue; // this fixes the floating point bug without modifying step size, as it prevents the particle from going back into the plane it just crossed. Will not count an intersection
1079  // if the distance is zero as it means it previously intersected and is now sitting on the plane. Not a valid intersection.
1080  }
1081  /* bug was that outward_triangle would not differentiate between truly in front of the plane and on the plane itself. Because of this particles would exit geometry,
1082  * re-enter via the same triangle (min dist would be zero), and once "inside" undefined behaviour would follow causing the particle to continue moving "inside mesh" while
1083  * travelling outside of the mesh geometry due to particle direction. Requiring the intersection distance be larger than zero makes it clear where the particle is relative
1084  * to the surface mesh and prevents invalid intersections which yield unphysical results */
1085 
1086  // intersection, and distance is larger than zero so it is a true intersection
1087  // update min_dist if smaller
1088  if (dist > min_dist) {
1089  continue;
1090  }
1091  min_dist= dist;
1092  min_tri = i;
1093  }
1094  }
1095 
1096  if (min_dist >= t) {
1097  return ireg;
1098  }
1099 
1100  // otherwise, if min_dist is smaller than t, update out parameters
1101 
1102  if (newmed) {
1103  if (inside_mesh) {
1104  // new medium is outside the mesh (-1)
1105  *newmed = -1;
1106  }
1107  else { /* outside_mesh */
1108  // new medium is the mesh medium
1109  *newmed = EGS_BaseGeometry::med;
1110  }
1111  }
1112 
1113  if (normal) {
1114  // egs++ convention is normal pointing opposite view ray
1115  if (triangle_normal(min_tri) * u > 0.0) {
1116  *normal = -1.0 * triangle_normal(min_tri);
1117  }
1118  else {
1119  *normal = triangle_normal(min_tri);
1120  }
1121  }
1122 
1123  t = min_dist;
1124  if (inside_mesh) {
1125  return -1; // new region is outside the mesh
1126  }
1127  // outside mesh, new region is inside the mesh
1128  return 0;
1129 }
1130 
1131 const std::string EGS_TriangleMesh::type = "EGS_TriangleMesh";
1132 
1133 extern "C" {
1134 
1135  static void setInputs() {
1136  inputSet = true;
1137 
1138  setBaseGeometryInputs(true);
1139 
1140  geomBlockInput->getSingleInput("library")->setValues({"egs_triangle_mesh"});
1141 
1142  // Format: name, isRequired, description, vector string of allowed values
1143  geomBlockInput->addSingleInput("file", true, "The full filepath to the .msh, .node or .ele file, including the extension.");
1144  geomBlockInput->addSingleInput("scale", false, "Apply a multiplative scaling factor to positions. E.g. if your model is in mm, scale to cm using scale=0.1.");
1145  }
1146 
1147  EGS_TRIANGLE_MESH_EXPORT string getExample() {
1148  string example;
1149  example = {
1150  R"(
1151  #:start geometry:
1152  name = my_surface_mesh
1153  library = egs_triangle_mesh
1154  file = model.stl # Full filepath, including extension
1155  :start media input: # Only one medium supported
1156  media = water
1157  :stop media input:
1158  # scale = 0.1 # optional scaling factor. Mesh files are assumed to be in `cm` for scale=1.
1159  :stop geometry:
1160 )"};
1161  return example;
1162  }
1163 
1164  EGS_TRIANGLE_MESH_EXPORT shared_ptr<EGS_BlockInput> getInputs() {
1165  if(!inputSet) {
1166  setInputs();
1167  }
1168  return geomBlockInput;
1169  }
1170 
1171  EGS_TRIANGLE_MESH_EXPORT EGS_BaseGeometry *createGeometry(EGS_Input *input) {
1172  if (!input) {
1173  egsWarning("createGeometry(EGS_TriangleMesh): null input?\n");
1174  return nullptr;
1175  }
1176  std::string mesh_file;
1177  int err = input->getInput("file", mesh_file);
1178  if (err) {
1179  egsWarning("createGeometry(EGS_TriangleMesh): no mesh file key `file` in input\n");
1180  return nullptr;
1181  }
1182 
1183  bool ends_with_stl = mesh_file.size() >= 3 &&
1184  mesh_file.compare(mesh_file.size() - 3, 3, "stl") == 0;
1185  if (!ends_with_stl) {
1186  throw std::runtime_error("unknown extension for triangle mesh file `"
1187  + mesh_file + "`, only STL files are supported");
1188  }
1189 
1190  EGS_TriangleMeshSpec mesh_spec;
1191  try {
1192  mesh_spec = stl_parser::parse_stl_file(mesh_file);
1193  }
1194  catch (const std::runtime_error &e) {
1195  std::string error_msg = std::string("createGeometry(EGS_TriangleMesh): ") +
1196  e.what() + "\n";
1197  egsWarning("\n%s", error_msg.c_str());
1198  return nullptr;
1199  }
1200 
1201  EGS_Float scale = 0.0;
1202  err = input->getInput("scale", scale);
1203  if (!err) {
1204  if (scale > 0.0) {
1205  mesh_spec.scale(scale);
1206  }
1207  else {
1208  egsFatal("createGeometry(EGS_TriangleMesh): invalid scale value (%g), "
1209  "expected a positive number\n", scale);
1210  }
1211  }
1212 
1213  vector<string> oct_options;
1214  oct_options.push_back("no");
1215  oct_options.push_back("yes");
1216  bool oct_set = input->getInput("octree accelerate", oct_options, 1); // default: yes
1217  if (oct_set) {
1218  egsInformation("EGS_TriangleMesh: octree acceleration enabled\n");
1219  }
1220  else {
1221  egsInformation("EGS_TriangleMesh: octree acceleration disabled\n");
1222  }
1223 
1224  vector<string> normal_options;
1225  normal_options.push_back("calculate");
1226  normal_options.push_back("stored");
1227  int normal_opt = input->getInput("normals", normal_options, 0); // default: calculate
1228  bool use_stored_normals = (normal_opt == 1);
1229 
1230  EGS_TriangleMesh *result = nullptr;
1231  try {
1232  result = new EGS_TriangleMesh(std::move(mesh_spec), oct_set, use_stored_normals);
1233  }
1234  catch (const std::runtime_error &e) {
1235  std::string error_msg = std::string("createGeometry(EGS_TriangleMesh): ") +
1236  "bad input to EGS_TriangleMesh\nerror: " + e.what() + "\n";
1237  egsWarning("\n%s", error_msg.c_str());
1238  return nullptr;
1239  }
1240 
1241  result->setName(input);
1242  result->setMedia(input);
1243  result->setLabels(input);
1244  return result;
1245  }
1246 }
Base geometry class. Every geometry class must be derived from EGS_BaseGeometry.
void setMedia(EGS_Input *inp)
Set the media in the geometry from the input pointed to by inp.
int nreg
Number of local regions in this geometry.
void setName(EGS_Input *inp)
Set the name of the geometry from the input inp.
int med
Medium index.
int setLabels(EGS_Input *input)
Set the labels from an input block.
A class for storing information in a tree-like structure of key-value pairs. This class is used throu...
Definition: egs_input.h:182
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:341
A container for raw unstructured triangle surface mesh data.
void scale(EGS_Float factor)
Multiply all node coordinates by a constant factor.
std::vector< EGS_TriangleMeshSpec::Triangle > elements
Unique elements.
A triangular surface mesh geometry.
int num_triangles() const
const std::array< EGS_Float, 3 > & triangle_xs(int tri) const
Returns the three triangle node x-coordinates.
const EGS_Vector & triangle_normal(int tri) const
Returns the outward-facing triangle unit normal.
const std::array< EGS_Float, 3 > & triangle_zs(int tri) const
Returns the three triangle node z-coordinates.
const std::array< EGS_Float, 3 > & triangle_ys(int tri) const
Returns the three triangle node y-coordinates.
A class representing 3D vectors.
Definition: egs_vector.h:57
EGS_Float y
y-component
Definition: egs_vector.h:62
EGS_Float z
z-component
Definition: egs_vector.h:63
EGS_Float x
x-component
Definition: egs_vector.h:61
Global egspp functions header file.
EGS_GLIB_EXPORT EGS_BaseGeometry * createGeometry(EGS_Input *input)
Definition: egs_glib.cpp:84
EGS_Input class header file.
Triangle surface mesh geometry: header.
EGS_InfoFunction EGS_EXPORT egsInformation
Always use this function for reporting the progress of a simulation and any other type of information...
EGS_InfoFunction EGS_EXPORT egsFatal
Always use this function for reporting fatal errors.
const EGS_Float epsilon
The epsilon constant for floating point comparisons.
Definition: egs_functions.h:62
EGS_InfoFunction EGS_EXPORT egsWarning
Always use this function for reporting warnings.
const EGS_Float veryFar
A very large float.