57 #include "mesh_neighbours.h"
58 #include "msh_parser.h"
59 #include "tetgen_parser.h"
66 #include <unordered_map>
77 EGS_Mesh::~EGS_Mesh() =
default;
81 static bool EGS_MESH_LOCAL inputSet =
false;
84 std::size_t n_max = std::numeric_limits<int>::max();
85 if (this->
elements.size() >= n_max) {
86 throw std::runtime_error(
"maximum number of elements (" +
87 std::to_string(n_max) +
") exceeded (" +
88 std::to_string(this->
elements.size()) +
")");
90 if (this->
nodes.size() >= n_max) {
91 throw std::runtime_error(
"maximum number of nodes (" +
92 std::to_string(n_max) +
") exceeded (" +
93 std::to_string(this->
nodes.size()) +
")");
102 PercentCounter::PercentCounter(
EGS_InfoFunction info,
const std::string &msg)
103 : info_(info), msg_(msg) {}
105 void PercentCounter::start(EGS_Float goal) {
107 t_start_ = std::chrono::system_clock::now();
109 interactive_ = isatty(STDOUT_FILENO);
111 interactive_ = _isatty(STDOUT_FILENO);
113 if (!info_ || !interactive_) {
116 info_(
"\r%s (0%%)", msg_.c_str());
120 void PercentCounter::step(EGS_Float delta) {
121 if (!info_ || !interactive_) {
125 const int percent =
static_cast<int>((progress_ / goal_) * 100.0);
126 if (percent > old_percent_) {
127 info_(
"\r%s (%d%%)", msg_.c_str(), percent);
128 old_percent_ = percent;
132 void PercentCounter::finish(
const std::string &end_msg) {
136 const auto t_end = std::chrono::system_clock::now();
137 const std::chrono::duration<float> elapsed = t_end - t_start_;
139 info_(
"%s in %0.3fs\n", end_msg.c_str(), elapsed.count());
144 info_(
"\r%s in %0.3fs\n", end_msg.c_str(), elapsed.count());
155 const EGS_Float eps = 1e-8;
157 inline bool approx_eq(
double a,
double b,
double e = eps) {
158 return (std::abs(a - b) <= e * (std::abs(a) + std::abs(b) + 1.0));
162 return approx_eq(0.0, v.length(), eps);
165 inline EGS_Float min3(EGS_Float a, EGS_Float b, EGS_Float c) {
166 return std::min(std::min(a, b), c);
169 inline EGS_Float max3(EGS_Float a, EGS_Float b, EGS_Float c) {
170 return std::max(std::max(a, b), c);
182 return (x - y).length2();
186 return std::sqrt(distance2(x, y));
195 EGS_Float d1 = dot(ab, ao);
196 EGS_Float d2 = dot(ac, ao);
197 if (d1 <= 0.0 && d2 <= 0.0) {
203 EGS_Float d3 = dot(ab, bo);
204 EGS_Float d4 = dot(ac, bo);
205 if (d3 >= 0.0 && d4 <= d3) {
210 EGS_Float vc = d1 * d4 - d3 * d2;
211 if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) {
212 EGS_Float v = d1 / (d1 - d3);
218 EGS_Float d5 = dot(ab, co);
219 EGS_Float d6 = dot(ac, co);
220 if (d6 >= 0.0 && d5 <= d6) {
225 EGS_Float vb = d5 * d2 - d1 * d6;
226 if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) {
227 EGS_Float w = d2 / (d2 - d6);
232 EGS_Float va = d3 * d6 - d5 * d4;
233 if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0) {
234 EGS_Float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
235 return B + w * (C - B);
239 EGS_Float denom = 1.0 / (va + vb + vc);
240 EGS_Float v = vb * denom;
241 EGS_Float w = vc * denom;
242 return A + v * ab + w * ac;
248 return dot(P - A, cross(B - A, C - A)) * dot(D - A, cross(B - A, C - A)) < 0.0;
253 EGS_Float min = std::numeric_limits<EGS_Float>::max();
256 EGS_Vector q = closest_point_triangle(P, A, B, C);
257 EGS_Float dis = distance2(q, P);
264 if (point_outside_of_plane(P, A, B, C, D)) {
265 maybe_update_min_point(A, B, C);
268 if (point_outside_of_plane(P, A, C, D, B)) {
269 maybe_update_min_point(A, C, D);
272 if (point_outside_of_plane(P, A, B, D, C)) {
273 maybe_update_min_point(A, B, D);
275 if (point_outside_of_plane(P, B, D, C, A)) {
276 maybe_update_min_point(B, D, C);
296 int exterior_triangle_ray_intersection(
const EGS_Vector &p,
299 const EGS_Float eps = 1e-10;
304 EGS_Float det = dot(ab, pvec);
306 if (det > -eps && det < eps) {
309 EGS_Float inv_det = 1.0 / det;
311 EGS_Float u = dot(tvec, pvec) * inv_det;
312 if (u < 0.0 || u > 1.0) {
316 EGS_Float v = dot(v_norm, qvec) * inv_det;
317 if (v < 0.0 || u + v > 1.0) {
321 dist = dot(ac, qvec) * inv_det;
332 class EGS_Mesh_Octree {
335 return std::min(n.A.
x, std::min(n.B.
x, std::min(n.C.
x, n.D.
x)));
338 return std::max(n.A.
x, std::max(n.B.
x, std::max(n.C.
x, n.D.
x)));
341 return std::min(n.A.
y, std::min(n.B.
y, std::min(n.C.
y, n.D.
y)));
344 return std::max(n.A.
y, std::max(n.B.
y, std::max(n.C.
y, n.D.
y)));
347 return std::min(n.A.
z, std::min(n.B.
z, std::min(n.C.
z, n.D.
z)));
350 return std::max(n.A.
z, std::max(n.B.
z, std::max(n.C.
z, n.D.
z)));
361 BoundingBox() =
default;
362 BoundingBox(
double min_x,
double max_x,
double min_y,
double max_y,
363 double min_z,
double max_z) : min_x(min_x), max_x(max_x),
364 min_y(min_y), max_y(max_y), min_z(min_z), max_z(max_z) {}
365 double mid_x()
const {
366 return (min_x + max_x) / 2.0;
368 double mid_y()
const {
369 return (min_y + max_y) / 2.0;
371 double mid_z()
const {
372 return (min_z + max_z) / 2.0;
374 double volume()
const {
375 return (max_x - min_x) * (max_y - min_y) * (max_z - min_z);
377 void expand(
double delta) {
385 void print(std::ostream &out = std::cout)
const {
387 std::setprecision(std::numeric_limits<double>::max_digits10) <<
388 "min_x: " << min_x <<
"\n";
390 std::setprecision(std::numeric_limits<double>::max_digits10) <<
391 "max_x: " << max_x <<
"\n";
393 std::setprecision(std::numeric_limits<double>::max_digits10) <<
394 "min_y: " << min_y <<
"\n";
396 std::setprecision(std::numeric_limits<double>::max_digits10) <<
397 "max_y: " << max_y <<
"\n";
399 std::setprecision(std::numeric_limits<double>::max_digits10) <<
400 "min_z: " << min_z <<
"\n";
402 std::setprecision(std::numeric_limits<double>::max_digits10) <<
403 "max_z: " << max_z <<
"\n";
427 if (min3(a.
x, b.
x, c.
x) >= max_x ||
428 min3(a.
y, b.
y, c.
y) >= max_y ||
429 min3(a.
z, b.
z, c.
z) >= max_z ||
430 max3(a.
x, b.
x, c.
x) <= min_x ||
431 max3(a.
y, b.
y, c.
y) <= min_y ||
432 max3(a.
z, b.
z, c.
z) <= min_z) {
438 EGS_Float ex = (max_x - min_x) / 2.0;
439 EGS_Float ey = (max_y - min_y) / 2.0;
440 EGS_Float ez = (max_z - min_z) / 2.0;
448 const std::array<EGS_Vector, 3> edge_vecs { v1-v0, v2-v1, v0-v2 };
452 const EGS_Vector ux {1, 0, 0}, uy {0, 1, 0}, uz {0, 0, 1};
453 const std::array<EGS_Vector, 3> unit_vecs { ux, uy, uz};
464 const EGS_Float r = ex * std::abs(dot(ux, a)) +
465 ey * std::abs(dot(uy, a)) + ez * std::abs(dot(uz, a));
467 const EGS_Float p0 = dot(v0, a);
468 const EGS_Float p1 = dot(v1, a);
469 const EGS_Float p2 = dot(v2, a);
470 if (std::max(-max3(p0, p1, p2), min3(p0, p1, p2)) + eps > r) {
476 if (max3(v0.
x, v1.
x, v2.
x) <= -ex || min3(v0.
x, v1.
x, v2.
x) >= ex ||
477 max3(v0.
y, v1.
y, v2.
y) <= -ey || min3(v0.
y, v1.
y, v2.
y) >= ey ||
478 max3(v0.
z, v1.
z, v2.
z) <= -ez || min3(v0.
z, v1.
z, v2.
z) >= ez) {
487 const EGS_Vector n = cross(edge_vecs[0], edge_vecs[1]);
489 const EGS_Float r = ex * std::abs(n.
x) + ey * std::abs(n.
y) +
496 const EGS_Float s = dot(n, centre) - dot(n, a);
498 return std::abs(s) <= r;
502 return intersects_triangle(tet.A, tet.B, tet.C) ||
503 intersects_triangle(tet.A, tet.C, tet.D) ||
504 intersects_triangle(tet.A, tet.B, tet.D) ||
505 intersects_triangle(tet.B, tet.C, tet.D);
517 EGS_Float tmin = 0.0;
518 EGS_Float tmax = std::numeric_limits<EGS_Float>::max();
519 std::array<EGS_Float, 3> p_vec {p.
x, p.
y, p.
z};
520 std::array<EGS_Float, 3> v_vec {v.
x, v.
y, v.
z};
521 std::array<EGS_Float, 3> mins {min_x, min_y, min_z};
522 std::array<EGS_Float, 3> maxs {max_x, max_y, max_z};
523 for (std::size_t i = 0; i < 3; i++) {
526 if (std::abs(v_vec[i]) < eps) {
528 if (p_vec[i] < mins[i] || p_vec[i] > maxs[i]) {
534 EGS_Float inv_vel = 1.0 / v_vec[i];
535 EGS_Float t1 = (mins[i] - p_vec[i]) * inv_vel;
536 EGS_Float t2 = (maxs[i] - p_vec[i]) * inv_vel;
541 tmin = std::max(tmin, t1);
542 tmax = std::min(tmax, t2);
558 EGS_Float min_interior_distance(
const EGS_Vector &point)
const {
559 return std::min(point.
x - min_x, std::min(point.
y - min_y,
560 std::min(point.
z - min_z, std::min(max_x - point.
x,
561 std::min(max_y - point.
y, max_z - point.
z)))));
571 std::array<EGS_Float, 3> p = {point.
x, point.
y, point.
z};
572 std::array<EGS_Float, 3> mins = {min_x, min_y, min_z};
573 std::array<EGS_Float, 3> maxs = {max_x, max_y, max_z};
575 std::array<EGS_Float, 3> q = p;
576 for (
int i = 0; i < 3; i++) {
577 if (p[i] < mins[i]) {
580 if (p[i] > maxs[i]) {
587 bool contains(
const EGS_Vector &point)
const {
597 return point.
x >= min_x && point.
x < max_x &&
598 point.
y >= min_y && point.
y < max_y &&
599 point.
z >= min_z && point.
z < max_z;
602 bool is_indivisible()
const {
604 return approx_eq(min_x, mid_x()) ||
605 approx_eq(max_x, mid_x()) ||
606 approx_eq(min_y, mid_y()) ||
607 approx_eq(max_y, mid_y()) ||
608 approx_eq(min_z, mid_z()) ||
609 approx_eq(max_z, mid_z());
623 std::array<BoundingBox, 8> divide8()
const {
669 std::vector<int> elts_;
670 std::vector<Node> children_;
674 Node(
const std::vector<int> &elts,
const BoundingBox &bbox,
675 std::size_t n_max,
const EGS_Mesh &mesh,
676 egs_mesh::internal::PercentCounter &progress) : bbox_(bbox) {
678 if (bbox_.is_indivisible() || elts.size() < n_max) {
680 progress.step(bbox_.volume());
684 std::array<std::vector<int>, 8> octants;
685 std::array<BoundingBox, 8> bbs = bbox_.divide8();
688 for (
const auto &e : elts) {
689 for (
int i = 0; i < 8; i++) {
691 octants[i].push_back(e);
695 for (
int i = 0; i < 8; i++) {
696 children_.push_back(Node(
697 std::move(octants[i]), bbs[i], n_max, mesh, progress
702 bool isLeaf()
const {
703 return children_.empty();
706 void print(std::ostream &out,
int level)
const {
707 out <<
"Level " << level <<
"\n";
709 if (children_.empty()) {
710 out <<
"num_elts: " << elts_.size() <<
"\n";
711 for (
const auto &e: elts_) {
717 for (
int i = 0; i < 8; i++) {
718 children_.at(i).print(out, level + 1);
727 std::size_t octant = 0;
728 if (p.
x >= bbox_.mid_x()) {
731 if (p.
y >= bbox_.mid_y()) {
734 if (p.
z >= bbox_.mid_z()) {
741 std::vector<int> findOtherIntersectedOctants(
const EGS_Vector &p,
742 const EGS_Vector &v,
int exclude_octant)
const {
744 throw std::runtime_error(
745 "findOtherIntersectedOctants called on leaf node");
751 std::vector<std::pair<EGS_Float, int>> intersections;
752 for (
int i = 0; i < 8; i++) {
753 if (i == exclude_octant) {
758 if (children_[i].bbox_.ray_intersection(p, v, dist, intersection)) {
759 intersections.push_back({dist, i});
762 std::sort(intersections.begin(), intersections.end());
763 std::vector<int> octants;
764 for (
const auto &i : intersections) {
765 octants.push_back(i.second);
773 const EGS_Float best_dist = bbox_.min_interior_distance(p);
777 EGS_Float best_dist2 = best_dist * best_dist;
778 for (
const auto &e: elts_) {
780 best_dist2 = std::min(best_dist2, distance2(p,
781 closest_point_tetrahedron(p, n.A, n.B, n.C, n.D)));
783 return std::sqrt(best_dist2);
790 return hownear_leaf_search(p, mesh);
793 const auto octant = findOctant(p);
794 return children_[octant].hownear_exterior(p, mesh);
802 for (
const auto &e: elts_) {
811 return children_[findOctant(p)].isWhere(p, mesh);
816 const EGS_Float &max_dist, EGS_Float &t,
EGS_Mesh &mesh)
819 EGS_Float min_dist = std::numeric_limits<EGS_Float>::max();
822 for (
const auto &e: elts_) {
829 auto intersection = mesh.closest_boundary_face(e, p, v);
830 if (intersection.dist < min_dist) {
832 min_dist = intersection.dist;
841 auto hit = bbox_.ray_intersection(p, v, dist, intersection);
848 auto octant = findOctant(intersection);
849 auto elt = children_[octant].howfar_exterior(
850 p, v, max_dist, t, mesh
859 for (
const auto &o : findOtherIntersectedOctants(p, v, octant)) {
860 auto elt = children_[o].howfar_exterior(
861 p, v, max_dist, t, mesh
874 EGS_Mesh_Octree() =
default;
875 EGS_Mesh_Octree(
const std::vector<int> &elts, std::size_t n_max,
876 const EGS_Mesh &mesh, egs_mesh::internal::PercentCounter &progress) {
878 throw std::runtime_error(
"EGS_Mesh_Octree: empty elements vector");
880 if (elts.size() > std::numeric_limits<int>::max()) {
881 throw std::runtime_error(
"EGS_Mesh_Octree: num elts must fit into an int");
884 const EGS_Float INF = std::numeric_limits<EGS_Float>::infinity();
885 BoundingBox g_bounds(INF, -INF, INF, -INF, INF, -INF);
886 for (
const auto &e : elts) {
888 g_bounds.min_x = std::min(g_bounds.min_x, tet_min_x(nodes));
889 g_bounds.max_x = std::max(g_bounds.max_x, tet_max_x(nodes));
890 g_bounds.min_y = std::min(g_bounds.min_y, tet_min_y(nodes));
891 g_bounds.max_y = std::max(g_bounds.max_y, tet_max_y(nodes));
892 g_bounds.min_z = std::min(g_bounds.min_z, tet_min_z(nodes));
893 g_bounds.max_z = std::max(g_bounds.max_z, tet_max_z(nodes));
897 g_bounds.expand(1e-8);
900 progress.start(g_bounds.volume());
901 root_ = Node(elts, g_bounds, n_max, mesh, progress);
905 if (!root_.bbox_.contains(p)) {
908 return root_.isWhere(p, mesh);
911 void print(std::ostream &out)
const {
916 const EGS_Float &max_dist, EGS_Float &t,
EGS_Mesh &mesh)
const {
919 auto hit = root_.bbox_.ray_intersection(p, v, dist, intersection);
920 if (!hit || dist > max_dist) {
923 return root_.howfar_exterior(p, v, max_dist, t, mesh);
937 if (!root_.bbox_.contains(p)) {
938 return distance(root_.bbox_.closest_point(p), p);
941 return root_.hownear_exterior(p, mesh);
949 initializeElements(std::move(spec.
elements), std::move(spec.
nodes),
950 std::move(spec.
media));
951 initializeNeighbours();
956 void EGS_Mesh::initializeElements(
957 std::vector<EGS_MeshSpec::Tetrahedron> elements,
958 std::vector<EGS_MeshSpec::Node> nodes,
959 std::vector<EGS_MeshSpec::Medium> materials) {
962 elt_tags_.reserve(elements.size());
963 elt_node_indices_.reserve(elements.size());
964 nodes_.reserve(nodes.size());
966 std::unordered_map<int, int> node_map;
967 node_map.reserve(nodes.size());
968 for (
int i = 0; i < static_cast<int>(nodes.size()); i++) {
969 const auto &n = nodes[i];
970 node_map.insert({n.tag, i});
973 if (node_map.size() != nodes.size()) {
974 throw std::runtime_error(
"duplicate nodes in node list");
977 auto find_node = [&](
int node_tag) ->
int {
978 auto node_it = node_map.find(node_tag);
979 if (node_it == node_map.end()) {
980 throw std::runtime_error(
"No mesh node with tag: " + std::to_string(node_tag));
982 return node_it->second;
984 for (
int i = 0; i < static_cast<int>(elements.size()); i++) {
985 const auto &e = elements[i];
986 elt_tags_.push_back(e.tag);
987 elt_node_indices_.push_back({
988 find_node(e.a), find_node(e.b), find_node(e.c), find_node(e.d)
992 initializeMedia(std::move(elements), std::move(materials));
995 void EGS_Mesh::initializeMedia(std::vector<EGS_MeshSpec::Tetrahedron> elements,
996 std::vector<EGS_MeshSpec::Medium> materials) {
997 std::unordered_map<int, int> medium_offsets;
998 for (
const auto &m : materials) {
1002 bool inserted = medium_offsets.insert({m.tag, media_offset}).second;
1004 throw std::runtime_error(
"duplicate medium tag: "
1005 + std::to_string(m.tag));
1009 medium_indices_.reserve(elements.size());
1010 for (
const auto &e: elements) {
1011 medium_indices_.push_back(medium_offsets.at(e.medium_tag));
1015 void EGS_Mesh::initializeNeighbours() {
1016 std::vector<mesh_neighbours::Tetrahedron> neighbour_elts;
1018 for (
const auto &e: elt_node_indices_) {
1019 neighbour_elts.emplace_back(mesh_neighbours::Tetrahedron(e[0], e[1], e[2], e[3]));
1022 egs_mesh::internal::PercentCounter progress(get_logger(),
1023 "EGS_Mesh: finding element neighbours");
1025 neighbours_ = mesh_neighbours::tetrahedron_neighbours(
1026 neighbour_elts, progress);
1028 progress.finish(
"EGS_Mesh: found element neighbours");
1031 for (
const auto &ns: neighbours_) {
1032 for (
const auto &n: ns) {
1033 boundary_faces_.push_back(n == mesh_neighbours::NONE);
1038 void EGS_Mesh::initializeNormals() {
1040 for (
int i = 0; i < static_cast<int>(
num_elements()); i++) {
1045 if (dot(normal, d-a) < 0) {
1051 face_normals_.push_back({
1052 get_normal(n.B, n.C, n.D, n.A),
1053 get_normal(n.A, n.C, n.D, n.B),
1054 get_normal(n.A, n.B, n.D, n.C),
1055 get_normal(n.A, n.B, n.C, n.D)
1060 void EGS_Mesh::initializeOctrees() {
1061 std::vector<int> elts;
1062 std::vector<int> boundary_elts;
1067 boundary_elts.push_back(i);
1071 std::size_t n_vol = 200;
1072 egs_mesh::internal::PercentCounter vol_progress(get_logger(),
1073 "EGS_Mesh: building volume octree");
1074 volume_tree_ = std::unique_ptr<EGS_Mesh_Octree>(
1075 new EGS_Mesh_Octree(elts, n_vol, *
this, vol_progress)
1077 vol_progress.finish(
"EGS_Mesh: built volume octree");
1079 std::size_t n_surf = 100;
1080 egs_mesh::internal::PercentCounter surf_progress(get_logger(),
1081 "EGS_Mesh: building surface octree");
1082 surface_tree_ = std::unique_ptr<EGS_Mesh_Octree>(
1083 new EGS_Mesh_Octree(boundary_elts, n_surf, *
this, surf_progress)
1085 surf_progress.finish(
"EGS_Mesh: built surface octree");
1088 bool EGS_Mesh::isInside(
const EGS_Vector &x) {
1089 return isWhere(x) != -1;
1096 int EGS_Mesh::medium(
int ireg)
const {
1097 return medium_indices_.at(ireg);
1102 if (point_outside_of_plane(x, n.A, n.B, n.C, n.D)) {
1105 if (point_outside_of_plane(x, n.A, n.C, n.D, n.B)) {
1108 if (point_outside_of_plane(x, n.A, n.B, n.D, n.C)) {
1111 if (point_outside_of_plane(x, n.B, n.C, n.D, n.A)) {
1118 return volume_tree_->isWhere(x, *
this);
1121 EGS_Float EGS_Mesh::hownear(
int ireg,
const EGS_Vector &x) {
1124 return min_interior_face_dist(ireg, x);
1127 return min_exterior_face_dist(x);
1132 EGS_Float distance_to_plane(
const EGS_Vector &x,
1134 return std::abs(dot(unit_plane_normal, x - plane_point));
1137 EGS_Float EGS_Mesh::min_interior_face_dist(
int ireg,
const EGS_Vector &x) {
1141 EGS_Float min_dist = distance_to_plane(x, face_normals_[ireg][0], n.B);
1142 min_dist = std::min(min_dist,
1143 distance_to_plane(x, face_normals_[ireg][1], n.A));
1144 min_dist = std::min(min_dist,
1145 distance_to_plane(x, face_normals_[ireg][2], n.A));
1146 min_dist = std::min(min_dist,
1147 distance_to_plane(x, face_normals_[ireg][3], n.A));
1152 EGS_Float EGS_Mesh::min_exterior_face_dist(
const EGS_Vector &x) {
1153 return surface_tree_->hownear_exterior(x, *
this);
1157 EGS_Float &t,
int *newmed ,
EGS_Vector *normal ) {
1159 return howfar_exterior(x, u, t, newmed, normal);
1165 EGS_Float distance_to_boundary =
veryFar;
1166 auto new_reg = howfar_interior(
1167 ireg, x, u, distance_to_boundary, newmed, normal);
1169 if (distance_to_boundary < t) {
1170 t = distance_to_boundary;
1187 EGS_Float &t,
int *newmed,
EGS_Vector *normal) {
1224 std::array<EGS_Vector, 4> face_points {n.B, n.A, n.A, n.A};
1225 std::array<PointLocation, 4> intersect_tests {};
1226 for (
int i = 0; i < 4; ++i) {
1227 intersect_tests[i] = find_point_location(
1228 x, u, face_points[i], face_normals_[ireg][i]);
1232 if (intersect_tests[0].signed_distance < 0.0 ||
1233 intersect_tests[1].signed_distance < 0.0 ||
1234 intersect_tests[2].signed_distance < 0.0 ||
1235 intersect_tests[3].signed_distance < 0.0) {
1236 return howfar_interior_thick_plane(intersect_tests, ireg, x, u, t,
1242 auto ix = find_interior_intersection(intersect_tests);
1243 if (ix.dist < 0.0 || ix.face_index == -1) {
1244 egsWarning(
"\nEGS_Mesh warning: bad interior intersection t = %.17g, face_index = %d in region %d: "
1245 "x=(%.17g,%.17g,%.17g) u=(%.17g,%.17g,%.17g)\n", ix.dist, ix.face_index,
1246 ireg, x.
x, x.
y, x.
z, u.
x, u.
y, u.
z);
1252 if (ix.dist < EGS_Mesh::min_step_size) {
1253 ix.dist = EGS_Mesh::min_step_size;
1256 int new_reg = neighbours_[ireg].at(ix.face_index);
1257 update_medium(new_reg, newmed);
1258 update_normal(face_normals_[ireg].at(ix.face_index), u, normal);
1263 EGS_Mesh::PointLocation EGS_Mesh::find_point_location(
const EGS_Vector &x,
1272 EGS_Float direction_dot_normal = dot(u, plane_normal);
1286 EGS_Float signed_distance = dot(plane_normal, x - plane_point);
1287 return PointLocation(direction_dot_normal, signed_distance);
1294 EGS_Mesh::Intersection EGS_Mesh::find_interior_intersection(
1295 const std::array<PointLocation, 4> &ixs) {
1297 int min_face_index = -1;
1298 for (
int i = 0; i < 4; ++i) {
1301 if (ixs[i].direction_dot_normal >= 0.0) {
1304 EGS_Float t_i = -ixs[i].signed_distance / ixs[i].direction_dot_normal;
1311 return Intersection(t_min, min_face_index);
1315 int EGS_Mesh::howfar_interior_thick_plane(
const std::array<PointLocation, 4> &
1317 EGS_Float &t,
int *newmed,
EGS_Vector *normal) {
1333 EGS_Float max_neg_dist =
veryFar;
1334 int face_index = -1;
1335 for (
int i = 0; i < 4; ++i) {
1336 if (intersect_tests[i].signed_distance < max_neg_dist) {
1337 max_neg_dist = intersect_tests[i].signed_distance;
1341 if (face_index == -1) {
1342 egsWarning(
"\nEGS_Mesh warning: howfar_interior_thick_plane face_index %d in region %d: "
1343 "x=(%.17g,%.17g,%.17g) u=(%.17g,%.17g,%.17g)\n",
1344 face_index, ireg, x.
x, x.
y, x.
z, u.
x, u.
y, u.
z);
1355 constexpr EGS_Float thick_plane_bounds = EGS_Mesh::min_step_size;
1356 if (max_neg_dist < -thick_plane_bounds ||
1357 intersect_tests.at(face_index).direction_dot_normal < -thick_plane_bounds) {
1358 return howfar_interior_recover_lost_particle(ireg, x, u, t, newmed);
1372 int min_face_index = -1;
1373 for (
int i = 0; i < 4; ++i) {
1376 if (intersect_tests[i].direction_dot_normal >= 0.0) {
1380 EGS_Float t_i = -intersect_tests[i].signed_distance
1381 / intersect_tests[i].direction_dot_normal;
1392 if (t_min < EGS_Mesh::min_step_size) {
1393 return howfar_interior_recover_lost_particle(ireg, x, u, t, newmed);
1395 if (min_face_index == -1) {
1396 egsWarning(
"\nEGS_Mesh warning: face_index %d in region %d: "
1397 "x=(%.17g,%.17g,%.17g) u=(%.17g,%.17g,%.17g)\n",
1398 min_face_index, ireg, x.
x, x.
y, x.
z, u.
x, u.
y, u.
z);
1403 int new_reg = neighbours_[ireg].at(min_face_index);
1404 update_medium(new_reg, newmed);
1405 update_normal(face_normals_[ireg].at(min_face_index), u, normal);
1425 int EGS_Mesh::howfar_interior_recover_lost_particle(
int ireg,
1427 t = EGS_Mesh::min_step_size;
1431 for (
int i = 0; i < 4; ++i) {
1432 const auto neighbour = neighbours_[ireg][i];
1433 if (neighbour == -1) {
1437 update_medium(neighbour, newmed);
1444 auto actual_elt = isWhere(new_pos);
1445 update_medium(actual_elt, newmed);
1454 EGS_Mesh::Intersection EGS_Mesh::closest_boundary_face(
int ireg,
const EGS_Vector &x,
1457 EGS_Float min_dist = std::numeric_limits<EGS_Float>::max();
1459 auto dist = min_dist;
1460 auto closest_face = -1;
1464 if (boundary_faces_[4*ireg + face] &&
1467 point_outside_of_plane(x, A, B, C, D) &&
1468 dot(face_normals_[ireg][face], u) > 0.0 &&
1469 exterior_triangle_ray_intersection(x, u, A, B, C, dist) &&
1472 closest_face = face;
1478 check_face_intersection(0, n.B, n.C, n.D, n.A);
1479 check_face_intersection(1, n.A, n.C, n.D, n.B);
1480 check_face_intersection(2, n.A, n.B, n.D, n.C);
1481 check_face_intersection(3, n.A, n.B, n.C, n.D);
1483 return EGS_Mesh::Intersection(min_dist, closest_face);
1488 EGS_Float &t,
int *newmed,
EGS_Vector *normal) {
1489 EGS_Float min_dist = 1e30;
1490 auto min_reg = surface_tree_->howfar_exterior(x, u, t, min_dist, *
this);
1493 if (min_dist > t || min_reg == -1) {
1500 *newmed = medium(min_reg);
1503 auto intersection = closest_boundary_face(min_reg, x, u);
1504 EGS_Vector tmp_normal = face_normals_[min_reg]
1505 .at(intersection.face_index);
1507 if (dot(tmp_normal, u) > 0) {
1508 tmp_normal = -1.0 * tmp_normal;
1510 *normal = tmp_normal;
1515 const std::string EGS_Mesh::type =
"EGS_Mesh";
1517 void EGS_Mesh::printInfo()
const {
1519 std::ostringstream oss;
1531 static EGS_MeshSpec parse_mesh_file(
const std::string &mesh_file) {
1533 auto ends_with = [](
const std::string& str,
const std::string& suffix)
1535 if (suffix.size() > str.size()) {
1538 return str.compare(str.size() - suffix.size(), str.size(), suffix) == 0;
1541 if (ends_with(mesh_file,
".msh")) {
1542 std::ifstream file_stream(mesh_file);
1544 throw std::runtime_error(std::string(
"mesh file: `") + mesh_file
1545 +
"` does not exist or is not readable");
1550 catch (
const std::runtime_error &e) {
1551 throw std::runtime_error(std::string(
"Gmsh msh file parsing failed")
1552 +
"\nerror: " + e.what() +
"\n");
1556 if (ends_with(mesh_file,
".ele")) {
1557 return tetgen_parser::parse_tetgen_files(mesh_file,
1561 if (ends_with(mesh_file,
".node")) {
1562 return tetgen_parser::parse_tetgen_files(mesh_file,
1566 throw std::runtime_error(std::string(
"unknown extension for mesh file `")
1567 + mesh_file +
"`, supported extensions are msh, ele, node");
1571 static void setInputs() {
1574 setBaseGeometryInputs(
false);
1576 geomBlockInput->getSingleInput(
"library")->setValues({
"egs_mesh"});
1579 geomBlockInput->addSingleInput(
"file",
true,
"The full filepath to the .msh, .node or .ele file, including the extension.");
1580 geomBlockInput->addSingleInput(
"scale",
false,
"Apply a multiplicative scaling factor to positions. E.g. if your model is in mm, scale to cm using scale=0.1.");
1583 EGS_MESH_EXPORT
string getExample() {
1587 # Use Gmsh to convert CAD formats to .msh
1588 # Define materials by naming "physical volumes" with the medium name
1592 # Using the msh4.1 format:
1594 # or, using the TetGen format:
1595 # file = model.node # or model.ele
1596 scale = 0.1 # scale from mm to cm
1602 EGS_MESH_EXPORT shared_ptr<EGS_BlockInput> getInputs() {
1606 return geomBlockInput;
1611 egsWarning(
"createGeometry(EGS_Mesh): null input\n");
1614 std::string mesh_file;
1615 int err = input->
getInput(
"file", mesh_file);
1617 egsWarning(
"createGeometry(EGS_Mesh): no mesh file key `file` in input\n");
1623 mesh_spec = parse_mesh_file(mesh_file);
1625 catch (
const std::runtime_error &e) {
1626 std::string error_msg = std::string(
"createGeometry(EGS_Mesh): ") +
1632 EGS_Float scale = 0.0;
1633 err = input->
getInput(
"scale", scale);
1636 mesh_spec.
scale(scale);
1639 egsFatal(
"createGeometry(EGS_Mesh): invalid scale value (%g), "
1640 "expected a positive number\n", scale);
1646 mesh =
new EGS_Mesh(std::move(mesh_spec));
1648 catch (
const std::runtime_error &e) {
1649 std::string error_msg = std::string(
"createGeometry(EGS_Mesh): ") +
1650 "bad input to EGS_Mesh\nerror: " + e.what() +
"\n";
Base geometry class. Every geometry class must be derived from EGS_BaseGeometry.
int nreg
Number of local regions in this geometry.
void setName(EGS_Input *inp)
Set the name of the geometry from the input inp.
static int error_flag
Set to non-zero status if a geometry problem is encountered.
int setLabels(EGS_Input *input)
Set the labels from an input block.
virtual void printInfo() const
Print information about this geometry.
static int addMedium(const string &medname)
Add a medium or get the index of an existing medium.
void setBoundaryTolerance(EGS_Input *inp)
Set the value of the boundary tolerance from the input inp.
A container for raw unstructured tetrahedral mesh data.
std::vector< EGS_MeshSpec::Tetrahedron > elements
Unique mesh elements.
void scale(EGS_Float factor)
Multiply all node coordinates by a constant factor.
std::vector< EGS_MeshSpec::Node > nodes
Unique nodes.
std::vector< EGS_MeshSpec::Medium > media
Unique medium information.
A tetrahedral mesh geometry.
EGS_Mesh(EGS_MeshSpec spec)
bool insideElement(int i, const EGS_Vector &x)
Check if a point x is inside element i.
void printElement(int i, std::ostream &elt_info=std::cout) const
Print information about element i to the stream elt_info.
int num_elements() const
Returns the number of mesh elements.
Nodes element_nodes(int element) const
Given an element offset, return the element's node coordinates.
bool is_boundary(int reg) const
A class representing 3D vectors.
void(* EGS_InfoFunction)(const char *,...)
Defines a function printf-like prototype for functions to be used to report info, warnings,...
EGS_GLIB_EXPORT EGS_BaseGeometry * createGeometry(EGS_Input *input)
Tetrahedral mesh geometry: header.
EGS_Vector methods for the manipulation of 3D vectors in cartesian co-ordinates.
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.
EGS_InfoFunction EGS_EXPORT egsWarning
Always use this function for reporting warnings.
const EGS_Float veryFar
A very large float.