35 #include "stl_parser.h"
44 static bool EGS_TRIANGLE_MESH_LOCAL inputSet =
false;
46 const EGS_Float eps = 1e-8;
57 return (x - y).length2();
61 return std::sqrt(distance2(x, y));
64 inline EGS_Float min3(EGS_Float a, EGS_Float b, EGS_Float c) {
65 return std::min(std::min(a, b), c);
68 inline EGS_Float max3(EGS_Float a, EGS_Float b, EGS_Float c) {
69 return std::max(std::max(a, b), c);
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));
77 return approx_eq(0.0, v.length(), eps);
87 return dot(n, (p - a)) >= 0.0;
97 EGS_Float d1 = dot(ab, ao);
98 EGS_Float d2 = dot(ac, ao);
99 if (d1 <= 0.0 && d2 <= 0.0) {
105 EGS_Float d3 = dot(ab, bo);
106 EGS_Float d4 = dot(ac, bo);
107 if (d3 >= 0.0 && d4 <= d3) {
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);
120 EGS_Float d5 = dot(ab, co);
121 EGS_Float d6 = dot(ac, co);
122 if (d6 >= 0.0 && d5 <= d6) {
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);
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);
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;
153 bool triangle_ray_intersection(
const EGS_Vector &p,
156 const EGS_Float eps = 1e-10;
161 EGS_Float det = dot(ab, pvec);
163 if (det > -eps && det < eps) {
166 EGS_Float inv_det = 1.0 / det;
168 EGS_Float u = dot(tvec, pvec) * inv_det;
169 if (u < 0.0 || u > 1.0) {
173 EGS_Float v = dot(v_norm, qvec) * inv_det;
174 if (v < 0.0 || u + v > 1.0) {
178 dist = dot(ac, qvec) * inv_det;
190 class EGS_TriangleMeshBbox {
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) {}
197 void expand(
double delta) {
206 double mid_x()
const {
207 return (min_x + max_x) / 2.0;
209 double mid_y()
const {
210 return (min_y + max_y) / 2.0;
212 double mid_z()
const {
213 return (min_z + max_z) / 2.0;
216 bool is_indivisible()
const {
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());
227 std::array<EGS_TriangleMeshBbox, 8> divide8()
const {
229 EGS_TriangleMeshBbox(
234 EGS_TriangleMeshBbox(
239 EGS_TriangleMeshBbox(
244 EGS_TriangleMeshBbox(
249 EGS_TriangleMeshBbox(
254 EGS_TriangleMeshBbox(
259 EGS_TriangleMeshBbox(
264 EGS_TriangleMeshBbox(
272 bool contains(
const EGS_Vector &point)
const {
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;
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};
295 std::array<EGS_Float, 3> q = p;
296 for (
int i = 0; i < 3; i++) {
297 if (p[i] < mins[i]) {
300 if (p[i] > maxs[i]) {
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)))));
321 EGS_Float tmin = 0.0;
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++) {
330 if (std::abs(v_vec[i]) < 1e-10) {
332 if (p_vec[i] < mins[i] || p_vec[i] > maxs[i]) {
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;
345 tmin = std::max(tmin, t1);
346 tmax = std::min(tmax, t2);
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) {
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;
398 const std::array<EGS_Vector, 3> edge_vecs { v1-v0, v2-v1, v0-v2 };
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};
407 if (is_zero(u_cross_f)) {
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));
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) {
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) {
436 const EGS_Vector n = cross(edge_vecs[0], edge_vecs[1]);
438 const EGS_Float r = ex * std::abs(n.
x) + ey * std::abs(n.
y) + ez * std::abs(n.
z);
444 const EGS_Float s = dot(n, centre) - dot(n, a);
446 return std::abs(s) <= r;
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;
460 std::vector<int> elts_;
461 std::vector<TriNode> children_;
462 EGS_TriangleMeshBbox bbox_;
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) {
472 std::array<std::vector<int>, 8> octants;
473 std::array<EGS_TriangleMeshBbox, 8> bbs = bbox_.divide8();
476 for (
const auto &e : elts) {
482 for (
int i = 0; i < 8; i++) {
486 octants[i].push_back(e);
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",
495 xs[2], ys[2], zs[2]);
498 for (
int i = 0; i < 8; i++) {
499 children_.push_back(TriNode(std::move(octants[i]), bbs[i], n_max, mesh));
503 bool isLeaf()
const {
504 return children_.empty();
512 std::size_t octant = 0;
513 if (p.
x >= bbox_.mid_x()) {
516 if (p.
y >= bbox_.mid_y()) {
519 if (p.
z >= bbox_.mid_z()) {
526 std::array<std::pair<EGS_Float, int>, 7> findOtherIntersectedOctants(
const EGS_Vector &p,
const EGS_Vector &v,
int exclude_octant,
int &n)
const {
528 throw std::runtime_error(
"findOtherIntersectedOctants called on leaf node");
530 std::array<std::pair<EGS_Float, int>, 7> intersections;
532 for (
int i = 0; i < 8; i++) {
533 if (i == exclude_octant) {
538 if (children_[i].bbox_.ray_intersection(p, v, dist, intersection)) {
539 intersections[n++] = {dist, i};
542 std::sort(intersections.begin(), intersections.begin() + n);
543 return intersections;
549 if (elts_.size()>0) {
550 for (
const auto &i: elts_) {
558 if (inside_mesh && outward_triangle) {
562 if (!inside_mesh && !outward_triangle) {
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)) {
573 if (dist>-1e-10 && dist<1e-10) {
580 if (dist > min_dist) {
628 auto hit = bbox_.ray_intersection(x, u, interdist, intersection);
635 auto octant = findOctant(intersection);
636 auto elt = children_[octant].howfar(x, u, min_dist, min_tri, inside_mesh, trimesh);
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);
661 if (elts_.size()>0) {
662 for (
const auto &i: elts_) {
669 trimesh.inctricheck_OIW();
671 if (!triangle_ray_intersection(x, arbitrary_unit_velocity,
682 min_dist_exterior = std::min(dist, min_dist_exterior);
685 min_dist_interior = std::min(dist, min_dist_interior);
696 auto hit = bbox_.ray_intersection(x, arbitrary_unit_velocity, interdist, intersection);
703 auto octant = findOctant(intersection);
704 auto elt = children_[octant].isWhere(x, arbitrary_unit_velocity, min_dist_interior, min_dist_exterior, trimesh);
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);
728 min_t=bbox_.min_interior_distance(x);
729 EGS_Float min_t2=min_t*min_t;
730 for (
const auto &i: elts_) {
735 trimesh.inctricheck_OHN();
737 EGS_Float dis2 = distance2(q, x);
744 min_t=std::sqrt(min_t2);
750 auto octant = findOctant(x);
751 children_[octant].hownear(x, min_t, min_point, trimesh);
756 class EGS_TriangleMesh_Octree {
760 EGS_TriangleMesh_Octree() =
default;
761 EGS_TriangleMesh_Octree(
const std::vector<int> &elts, std::size_t n_max,
764 throw std::runtime_error(
"EGS_Mesh_Octree: empty elements vector");
766 if (elts.size() > std::numeric_limits<int>::max()) {
767 throw std::runtime_error(
"EGS_Mesh_Octree: num elts must fit into an int");
769 root_ = TriNode(elts, basebox, n_max, mesh);
775 auto hit = root_.bbox_.ray_intersection(x, u, dist, intersection);
776 if (!hit || dist > max_dist) {
780 return root_.howfar(x, u, min_dist, min_tri, inside_mesh, trimesh);
784 if (!root_.bbox_.contains(x)) {
787 return root_.isWhere(x, arbitrary_unit_velocity, min_dist_interior, min_dist_exterior, trimesh);
791 if (!root_.bbox_.contains(x)) {
794 root_.hownear(x, min_t, min_point, trimesh);
799 EGS_TriangleMesh::EGS_TriangleMesh(
EGS_TriangleMeshSpec spec,
bool oct_set,
bool use_stored_normals) :
802 egsInformation(
"EGS_TriangleMesh: mesh contains %d triangles\n", n_tris);
808 if (this->n_tris == 0) {
809 throw std::runtime_error(
"empty triangles vector in EGS_TriangleMesh constructor");
812 xs.reserve(this->n_tris);
813 ys.reserve(this->n_tris);
814 zs.reserve(this->n_tris);
815 ns.reserve(this->n_tris);
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;
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});
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)));
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)));
844 EGS_Float normal_len2 = normal.length2();
845 EGS_Float edge_scale = std::max(ab.length2(), ac.length2());
852 EGS_Vector normal_unit = (1.0 / std::sqrt(normal_len2)) * normal;
853 if (use_stored_normals) {
856 if (tri.n * normal_unit < 0.0) {
865 if (tri.n * normal_unit < 0.0) {
868 ns.push_back(normal_unit);
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");
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
899 void EGS_TriangleMesh::initializeOctree() {
900 std::vector<int> elts;
907 std::size_t n_surf = 30;
908 surface_tree_ = std::unique_ptr<EGS_TriangleMesh_Octree>(
new EGS_TriangleMesh_Octree(elts, n_surf, *
this, *bbox));
912 EGS_TriangleMesh::~EGS_TriangleMesh() =
default;
917 bool EGS_TriangleMesh::isInside(
const EGS_Vector &x) {
918 return isWhere(x) != -1;
921 int EGS_TriangleMesh::inside(
const EGS_Vector &x) {
925 int EGS_TriangleMesh::isWhere(
const EGS_Vector &x) {
928 if (!bbox->contains(x)) {
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);
941 double min_dist_interior =
veryFar;
942 double min_dist_exterior =
veryFar;
944 surface_tree_->isWhere(x, arbitrary_unit_velocity, min_dist_interior, min_dist_exterior, *
this);
955 if (!triangle_ray_intersection(x, arbitrary_unit_velocity,
966 min_dist_exterior = std::min(dist, min_dist_exterior);
969 min_dist_interior = std::min(dist, min_dist_interior);
978 if (min_dist_interior ==
veryFar && min_dist_exterior ==
veryFar) {
985 if (min_dist_exterior <= min_dist_interior) {
992 EGS_Float EGS_TriangleMesh::hownear(
int ireg,
const EGS_Vector &x) {
998 if (ireg == -1 && !bbox->contains(x)) {
1001 return distance(bbox->closest_point(x), x);
1009 if (octree_acc_on) {
1010 surface_tree_->hownear(x, min_t, min_point, *
this);
1020 EGS_Float dis = distance(q, x);
1031 EGS_Float &t,
int *newmed,
EGS_Vector *normal) {
1035 if (!bbox->ray_intersection(x, u, dist, intersection)) {
1047 const bool inside_mesh = ireg != -1;
1049 if (octree_acc_on) {
1050 surface_tree_->howfar(x, u, min_dist, t, min_tri, inside_mesh, *
this);
1061 if (inside_mesh && outward_triangle) {
1065 if (!inside_mesh && !outward_triangle) {
1071 if (!triangle_ray_intersection(x, u,
EGS_Vector(xs[0], ys[0], zs[0]),
1077 if (dist>-1e-10 && dist<1e-10) {
1088 if (dist > min_dist) {
1096 if (min_dist >= t) {
1131 const std::string EGS_TriangleMesh::type =
"EGS_TriangleMesh";
1135 static void setInputs() {
1138 setBaseGeometryInputs(
true);
1140 geomBlockInput->getSingleInput(
"library")->setValues({
"egs_triangle_mesh"});
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.");
1147 EGS_TRIANGLE_MESH_EXPORT
string getExample() {
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
1158 # scale = 0.1 # optional scaling factor. Mesh files are assumed to be in `cm` for scale=1.
1164 EGS_TRIANGLE_MESH_EXPORT shared_ptr<EGS_BlockInput> getInputs() {
1168 return geomBlockInput;
1173 egsWarning(
"createGeometry(EGS_TriangleMesh): null input?\n");
1176 std::string mesh_file;
1177 int err = input->
getInput(
"file", mesh_file);
1179 egsWarning(
"createGeometry(EGS_TriangleMesh): no mesh file key `file` in input\n");
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");
1192 mesh_spec = stl_parser::parse_stl_file(mesh_file);
1194 catch (
const std::runtime_error &e) {
1195 std::string error_msg = std::string(
"createGeometry(EGS_TriangleMesh): ") +
1201 EGS_Float scale = 0.0;
1202 err = input->
getInput(
"scale", scale);
1205 mesh_spec.
scale(scale);
1208 egsFatal(
"createGeometry(EGS_TriangleMesh): invalid scale value (%g), "
1209 "expected a positive number\n", scale);
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);
1218 egsInformation(
"EGS_TriangleMesh: octree acceleration enabled\n");
1221 egsInformation(
"EGS_TriangleMesh: octree acceleration disabled\n");
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);
1228 bool use_stored_normals = (normal_opt == 1);
1232 result =
new EGS_TriangleMesh(std::move(mesh_spec), oct_set, use_stored_normals);
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";
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 setLabels(EGS_Input *input)
Set the labels from an input block.
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.
Global egspp functions header file.
EGS_GLIB_EXPORT EGS_BaseGeometry * createGeometry(EGS_Input *input)
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.
EGS_InfoFunction EGS_EXPORT egsWarning
Always use this function for reporting warnings.
const EGS_Float veryFar
A very large float.