//
// Declaration of the BoundaryGeometry class
//
// Copyright (c) 200x - 2020, Achim Gsell,
// Paul Scherrer Institut, Villigen PSI, Switzerland
// All rights reserved.
//
// This file is part of OPAL.
//
// OPAL is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// You should have received a copy of the GNU General Public License
// along with OPAL. If not, see .
//
//#define ENABLE_DEBUG
#include "Structure/BoundaryGeometry.h"
#include
#include
#include
#include
#include "H5hut.h"
#include
#include "AbstractObjects/OpalData.h"
#include "Algorithms/PartBunchBase.h"
#include "Expressions/SRefExpr.h"
#include "Elements/OpalBeamline.h"
#include "Utilities/Options.h"
#include "Utilities/OpalException.h"
#include
extern Inform* gmsg;
#define SQR(x) ((x)*(x))
#define PointID(triangle_id, vertex_id) Triangles_m[triangle_id][vertex_id]
#define Point(triangle_id, vertex_id) Points_m[Triangles_m[triangle_id][vertex_id]]
/*
In the following namespaces various approximately floating point
comparisons are implemented. The used implementation is selected
via
namespaces cmp = IMPLEMENTATION;
*/
/*
First we define some macros for function common in all namespaces.
*/
#define FUNC_EQ(x, y) inline bool eq(double x, double y) { \
return almost_eq(x, y); \
}
#define FUNC_EQ_ZERO(x) inline bool eq_zero(double x) { \
return almost_eq_zero(x); \
}
#define FUNC_LE(x, y) inline bool le(double x, double y) { \
if (almost_eq(x, y)) { \
return true; \
} \
return x < y; \
}
#define FUNC_LE_ZERO(x) inline bool le_zero(double x) { \
if (almost_eq_zero(x)) { \
return true; \
} \
return x < 0.0; \
}
#define FUNC_LT(x, y) inline bool lt(double x, double y) { \
if (almost_eq(x, y)) { \
return false; \
} \
return x < y; \
}
#define FUNC_LT_ZERO(x) inline bool lt_zero(double x) { \
if (almost_eq_zero(x)) { \
return false; \
} \
return x < 0.0; \
}
#define FUNC_GE(x, y) inline bool ge(double x, double y) { \
if (almost_eq(x, y)) { \
return true; \
} \
return x > y; \
}
#define FUNC_GE_ZERO(x) inline bool ge_zero(double x) { \
if (almost_eq_zero(x)) { \
return true; \
} \
return x > 0.0; \
}
#define FUNC_GT(x, y) inline bool gt(double x, double y) { \
if (almost_eq(x, y)) { \
return false; \
} \
return x > y; \
}
#define FUNC_GT_ZERO(x) inline bool gt_zero(double x) { \
if (almost_eq_zero(x)) { \
return false; \
} \
return x > 0.0; \
}
namespace cmp_diff {
/*
Link:
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
*/
inline bool almost_eq(double A, double B, double maxDiff = 1e-15, double maxRelDiff = DBL_EPSILON) {
// Check if the numbers are really close -- needed
// when comparing numbers near zero.
const double diff = std::abs(A - B);
if (diff <= maxDiff)
return true;
A = std::abs(A);
B = std::abs(B);
const double largest = (B > A) ? B : A;
if (diff <= largest * maxRelDiff)
return true;
return false;
}
inline bool almost_eq_zero(double A, double maxDiff = 1e-15) {
const double diff = std::abs(A);
return (diff <= maxDiff);
}
FUNC_EQ(x, y);
FUNC_EQ_ZERO(x);
FUNC_LE(x, y);
FUNC_LE_ZERO(x);
FUNC_LT(x, y);
FUNC_LT_ZERO(x);
FUNC_GE(x, y);
FUNC_GE_ZERO(x);
FUNC_GT(x, y);
FUNC_GT_ZERO(x);
}
namespace cmp_ulp_obsolete {
/*
See:
https://www.cygnus-software.com/papers/comparingfloats/comparing_floating_point_numbers_obsolete.htm
*/
inline bool almost_eq(double A, double B, double maxDiff = 1e-20, int maxUlps = 1000) {
// Make sure maxUlps is non-negative and small enough that the
// default NAN won't compare as equal to anything.
// assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);
// handle NaN's
// Note: comparing something with a NaN is always false!
if (A != A || B != B) {
return false;
}
if (std::abs(A - B) <= maxDiff) {
return true;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
auto aInt = *(int64_t*)&A;
#pragma GCC diagnostic pop
// Make aInt lexicographically ordered as a twos-complement int
if (aInt < 0) {
aInt = 0x8000000000000000 - aInt;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
auto bInt = *(int64_t*)&B;
#pragma GCC diagnostic pop
// Make bInt lexicographically ordered as a twos-complement int
if (bInt < 0) {
bInt = 0x8000000000000000 - bInt;
}
if (std::abs(aInt - bInt) <= maxUlps) {
return true;
}
return false;
}
inline bool almost_eq_zero(double A, double maxDiff = 1e-15) {
// no need to handle NaN's!
return (std::abs(A) <= maxDiff);
}
FUNC_EQ(x, y);
FUNC_EQ_ZERO(x);
FUNC_LE(x, y);
FUNC_LE_ZERO(x);
FUNC_LT(x, y);
FUNC_LT_ZERO(x);
FUNC_GE(x, y);
FUNC_GE_ZERO(x);
FUNC_GT(x, y);
FUNC_GT_ZERO(x);
}
namespace cmp_ulp {
/*
See:
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
*/
inline bool almost_eq(double A, double B, double maxDiff = 1e-20, int maxUlps = 1000) {
// handle NaN's
// Note: comparing something with a NaN is always false!
if (A != A || B != B) {
return false;
}
// Check if the numbers are really close -- needed
// when comparing numbers near zero.
if (std::abs(A - B) <= maxDiff)
return true;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
auto aInt = *(int64_t*)&A;
auto bInt = *(int64_t*)&B;
#pragma GCC diagnostic pop
// Different signs means they do not match.
// Note: a negative floating point number is also negative as integer.
if ( std::signbit(aInt) != std::signbit(bInt))
return false;
// Find the difference in ULPs.
return (std::abs(aInt - bInt) <= maxUlps);
}
inline bool almost_eq_zero(double A, double maxDiff = 1e-15) {
return (std::abs(A) <= maxDiff);
}
FUNC_EQ(x, y);
FUNC_EQ_ZERO(x);
FUNC_LE(x, y);
FUNC_LE_ZERO(x);
FUNC_LT(x, y);
FUNC_LT_ZERO(x);
FUNC_GE(x, y);
FUNC_GE_ZERO(x);
FUNC_GT(x, y);
FUNC_GT_ZERO(x);
}
namespace cmp = cmp_ulp;
/*
Some
_ _ _
| | | | ___| |_ __ ___ _ __
| |_| |/ _ \ | '_ \ / _ \ '__|
| _ | __/ | |_) | __/ |
|_| |_|\___|_| .__/ \___|_|
|_|
functions
*/
namespace {
struct VectorLessX {
bool operator() (Vector_t x1, Vector_t x2) {
return cmp::lt (x1(0), x2(0));
}
};
struct VectorLessY {
bool operator() (Vector_t x1, Vector_t x2) {
return cmp::lt(x1(1), x2 (1));
}
};
struct VectorLessZ {
bool operator() (Vector_t x1, Vector_t x2) {
return cmp::lt(x1(2), x2(2));
}
};
/**
Calculate the maximum of coordinates of geometry,i.e the maximum of X,Y,Z
*/
Vector_t get_max_extent (std::vector& coords) {
const Vector_t x = *max_element (
coords.begin (), coords.end (), VectorLessX ());
const Vector_t y = *max_element (
coords.begin (), coords.end (), VectorLessY ());
const Vector_t z = *max_element (
coords.begin (), coords.end (), VectorLessZ ());
return Vector_t (x(0), y(1), z(2));
}
/*
Compute the minimum of coordinates of geometry, i.e the minimum of X,Y,Z
*/
Vector_t get_min_extent (std::vector& coords) {
const Vector_t x = *min_element (
coords.begin (), coords.end (), VectorLessX ());
const Vector_t y = *min_element (
coords.begin (), coords.end (), VectorLessY ());
const Vector_t z = *min_element (
coords.begin (), coords.end (), VectorLessZ ());
return Vector_t (x(0), y(1), z(2));
}
/*
write legacy VTK file of voxel mesh
*/
static void write_voxel_mesh (
const std::unordered_map< int, std::unordered_set >& ids,
const Vector_t& hr_m,
const Vektor& nr,
const Vector_t& origin
) {
/*----------------------------------------------------------------------*/
const size_t numpoints = 8 * ids.size ();
std::ofstream of;
std::string fname = Util::combineFilePath({
OpalData::getInstance()->getAuxiliaryOutputDirectory(),
"testBBox.vtk"
});
of.open (fname);
assert (of.is_open ());
of.precision (6);
of << "# vtk DataFile Version 2.0" << std::endl;
of << "generated using BoundaryGeometry::computeMeshVoxelization"
<< std::endl;
of << "ASCII" << std::endl << std::endl;
of << "DATASET UNSTRUCTURED_GRID" << std::endl;
of << "POINTS " << numpoints << " float" << std::endl;
const auto nr0_times_nr1 = nr[0] * nr[1];
for (auto& elem: ids) {
auto id = elem.first;
int k = (id - 1) / nr0_times_nr1;
int rest = (id - 1) % nr0_times_nr1;
int j = rest / nr[0];
int i = rest % nr[0];
Vector_t P;
P[0] = i * hr_m[0] + origin[0];
P[1] = j * hr_m[1] + origin[1];
P[2] = k * hr_m[2] + origin[2];
of << P[0] << " " << P[1] << " " << P[2] << std::endl;
of << P[0] + hr_m[0] << " " << P[1] << " " << P[2] << std::endl;
of << P[0] << " " << P[1] + hr_m[1] << " " << P[2] << std::endl;
of << P[0] + hr_m[0] << " " << P[1] + hr_m[1] << " " << P[2] << std::endl;
of << P[0] << " " << P[1] << " " << P[2] + hr_m[2] << std::endl;
of << P[0] + hr_m[0] << " " << P[1] << " " << P[2] + hr_m[2] << std::endl;
of << P[0] << " " << P[1] + hr_m[1] << " " << P[2] + hr_m[2] << std::endl;
of << P[0] + hr_m[0] << " " << P[1] + hr_m[1] << " " << P[2] + hr_m[2] << std::endl;
}
of << std::endl;
const auto num_cells = ids.size ();
of << "CELLS " << num_cells << " " << 9 * num_cells << std::endl;
for (size_t i = 0; i < num_cells; i++)
of << "8 "
<< 8 * i << " " << 8 * i + 1 << " " << 8 * i + 2 << " " << 8 * i + 3 << " "
<< 8 * i + 4 << " " << 8 * i + 5 << " " << 8 * i + 6 << " " << 8 * i + 7 << std::endl;
of << "CELL_TYPES " << num_cells << std::endl;
for (size_t i = 0; i < num_cells; i++)
of << "11" << std::endl;
of << "CELL_DATA " << num_cells << std::endl;
of << "SCALARS " << "cell_attribute_data" << " float " << "1" << std::endl;
of << "LOOKUP_TABLE " << "default" << std::endl;
for (size_t i = 0; i < num_cells; i++)
of << (float)(i) << std::endl;
of << std::endl;
of << "COLOR_SCALARS " << "BBoxColor " << 4 << std::endl;
for (size_t i = 0; i < num_cells; i++) {
of << "1.0" << " 1.0 " << "0.0 " << "1.0" << std::endl;
}
of << std::endl;
}
}
/*___________________________________________________________________________
Triangle-cube intersection test.
See:
http://tog.acm.org/resources/GraphicsGems/gemsiii/triangleCube.c
*/
#define INSIDE 0
#define OUTSIDE 1
class Triangle {
public:
Triangle () { }
Triangle (const Vector_t& v1, const Vector_t& v2, const Vector_t& v3) {
pts[0] = v1;
pts[1] = v2;
pts[2] = v3;
}
inline const Vector_t& v1() const {
return pts[0];
}
inline double v1(int i) const {
return pts[0][i];
}
inline const Vector_t& v2() const {
return pts[1];
}
inline double v2(int i) const {
return pts[1][i];
}
inline const Vector_t& v3() const {
return pts[2];
}
inline double v3(int i) const {
return pts[2][i];
}
inline void scale (
const Vector_t& scaleby,
const Vector_t& shiftby
) {
pts[0][0] *= scaleby[0];
pts[0][1] *= scaleby[1];
pts[0][2] *= scaleby[2];
pts[1][0] *= scaleby[0];
pts[1][1] *= scaleby[1];
pts[1][2] *= scaleby[2];
pts[2][0] *= scaleby[0];
pts[2][1] *= scaleby[1];
pts[2][2] *= scaleby[2];
pts[0] -= shiftby;
pts[1] -= shiftby;
pts[2] -= shiftby;
}
Vector_t pts[3];
};
/*___________________________________________________________________________*/
/* Which of the six face-plane(s) is point P outside of? */
static inline int
face_plane (
const Vector_t& p
) {
int outcode_fcmp = 0;
if (cmp::gt(p[0], 0.5)) outcode_fcmp |= 0x01;
if (cmp::lt(p[0], -0.5)) outcode_fcmp |= 0x02;
if (cmp::gt(p[1], 0.5)) outcode_fcmp |= 0x04;
if (cmp::lt(p[1], -0.5)) outcode_fcmp |= 0x08;
if (cmp::gt(p[2], 0.5)) outcode_fcmp |= 0x10;
if (cmp::lt(p[2], -0.5)) outcode_fcmp |= 0x20;
return(outcode_fcmp);
}
/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/* Which of the twelve edge plane(s) is point P outside of? */
static inline int
bevel_2d (
const Vector_t& p
) {
int outcode_fcmp = 0;
if (cmp::gt( p[0] + p[1], 1.0)) outcode_fcmp |= 0x001;
if (cmp::gt( p[0] - p[1], 1.0)) outcode_fcmp |= 0x002;
if (cmp::gt(-p[0] + p[1], 1.0)) outcode_fcmp |= 0x004;
if (cmp::gt(-p[0] - p[1], 1.0)) outcode_fcmp |= 0x008;
if (cmp::gt( p[0] + p[2], 1.0)) outcode_fcmp |= 0x010;
if (cmp::gt( p[0] - p[2], 1.0)) outcode_fcmp |= 0x020;
if (cmp::gt(-p[0] + p[2], 1.0)) outcode_fcmp |= 0x040;
if (cmp::gt(-p[0] - p[2], 1.0)) outcode_fcmp |= 0x080;
if (cmp::gt( p[1] + p[2], 1.0)) outcode_fcmp |= 0x100;
if (cmp::gt( p[1] - p[2], 1.0)) outcode_fcmp |= 0x200;
if (cmp::gt(-p[1] + p[2], 1.0)) outcode_fcmp |= 0x400;
if (cmp::gt(-p[1] - p[2], 1.0)) outcode_fcmp |= 0x800;
return(outcode_fcmp);
}
/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Which of the eight corner plane(s) is point P outside of?
*/
static inline int
bevel_3d (
const Vector_t& p
) {
int outcode_fcmp = 0;
if (cmp::gt( p[0] + p[1] + p[2], 1.5)) outcode_fcmp |= 0x01;
if (cmp::gt( p[0] + p[1] - p[2], 1.5)) outcode_fcmp |= 0x02;
if (cmp::gt( p[0] - p[1] + p[2], 1.5)) outcode_fcmp |= 0x04;
if (cmp::gt( p[0] - p[1] - p[2], 1.5)) outcode_fcmp |= 0x08;
if (cmp::gt(-p[0] + p[1] + p[2], 1.5)) outcode_fcmp |= 0x10;
if (cmp::gt(-p[0] + p[1] - p[2], 1.5)) outcode_fcmp |= 0x20;
if (cmp::gt(-p[0] - p[1] + p[2], 1.5)) outcode_fcmp |= 0x40;
if (cmp::gt(-p[0] - p[1] - p[2], 1.5)) outcode_fcmp |= 0x80;
return(outcode_fcmp);
}
/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Test the point "alpha" of the way from P1 to P2
See if it is on a face of the cube
Consider only faces in "mask"
*/
static inline int
check_point (
const Vector_t& p1,
const Vector_t& p2,
const double alpha,
const int mask
) {
Vector_t plane_point;
#define LERP(a, b, t) (a + t * (b - a))
// with C++20 we can use: std::lerp(a, b, t)
plane_point[0] = LERP(p1[0], p2[0], alpha);
plane_point[1] = LERP(p1[1], p2[1], alpha);
plane_point[2] = LERP(p1[2], p2[2], alpha);
#undef LERP
return(face_plane(plane_point) & mask);
}
/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Compute intersection of P1 --> P2 line segment with face planes
Then test intersection point to see if it is on cube face
Consider only face planes in "outcode_diff"
Note: Zero bits in "outcode_diff" means face line is outside of
*/
static inline int
check_line (
const Vector_t& p1,
const Vector_t& p2,
const int outcode_diff
) {
if ((0x01 & outcode_diff) != 0)
if (check_point(p1,p2,( .5-p1[0])/(p2[0]-p1[0]),0x3e) == INSIDE) return(INSIDE);
if ((0x02 & outcode_diff) != 0)
if (check_point(p1,p2,(-.5-p1[0])/(p2[0]-p1[0]),0x3d) == INSIDE) return(INSIDE);
if ((0x04 & outcode_diff) != 0)
if (check_point(p1,p2,( .5-p1[1])/(p2[1]-p1[1]),0x3b) == INSIDE) return(INSIDE);
if ((0x08 & outcode_diff) != 0)
if (check_point(p1,p2,(-.5-p1[1])/(p2[1]-p1[1]),0x37) == INSIDE) return(INSIDE);
if ((0x10 & outcode_diff) != 0)
if (check_point(p1,p2,( .5-p1[2])/(p2[2]-p1[2]),0x2f) == INSIDE) return(INSIDE);
if ((0x20 & outcode_diff) != 0)
if (check_point(p1,p2,(-.5-p1[2])/(p2[2]-p1[2]),0x1f) == INSIDE) return(INSIDE);
return(OUTSIDE);
}
/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Test if 3D point is inside 3D triangle
*/
#define EPS 10e-10
static inline int
SIGN3 (
Vector_t A
) {
return ((A[0] < EPS) ? 4 : 0 | (A[0] > -EPS) ? 32 : 0 |
(A[1] < EPS) ? 2 : 0 | (A[1] > -EPS) ? 16 : 0 |
(A[2] < EPS) ? 1 : 0 | (A[2] > -EPS) ? 8 : 0);
}
#undef EPS
static int
point_triangle_intersection (
const Vector_t& p,
const Triangle& t
) {
/*
First, a quick bounding-box test:
If P is outside triangle bbox, there cannot be an intersection.
*/
if (cmp::gt(p[0], std::max({t.v1(0), t.v2(0), t.v3(0)}))) return(OUTSIDE);
if (cmp::gt(p[1], std::max({t.v1(1), t.v2(1), t.v3(1)}))) return(OUTSIDE);
if (cmp::gt(p[2], std::max({t.v1(2), t.v2(2), t.v3(2)}))) return(OUTSIDE);
if (cmp::lt(p[0], std::min({t.v1(0), t.v2(0), t.v3(0)}))) return(OUTSIDE);
if (cmp::lt(p[1], std::min({t.v1(1), t.v2(1), t.v3(1)}))) return(OUTSIDE);
if (cmp::lt(p[2], std::min({t.v1(2), t.v2(2), t.v3(2)}))) return(OUTSIDE);
/*
For each triangle side, make a vector out of it by subtracting vertexes;
make another vector from one vertex to point P.
The crossproduct of these two vectors is orthogonal to both and the
signs of its X,Y,Z components indicate whether P was to the inside or
to the outside of this triangle side.
*/
const Vector_t vect12 = t.v1() - t.v2();
const Vector_t vect1h = t.v1() - p;
const Vector_t cross12_1p = cross (vect12, vect1h);
const int sign12 = SIGN3(cross12_1p); /* Extract X,Y,Z signs as 0..7 or 0...63 integer */
const Vector_t vect23 = t.v2() - t.v3();
const Vector_t vect2h = t.v2() - p;
const Vector_t cross23_2p = cross (vect23, vect2h);
const int sign23 = SIGN3(cross23_2p);
const Vector_t vect31 = t.v3() - t.v1();
const Vector_t vect3h = t.v3() - p;
const Vector_t cross31_3p = cross (vect31, vect3h);
const int sign31 = SIGN3(cross31_3p);
/*
If all three crossproduct vectors agree in their component signs,
then the point must be inside all three.
P cannot be OUTSIDE all three sides simultaneously.
*/
return ((sign12 & sign23 & sign31) == 0) ? OUTSIDE : INSIDE;
}
/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
This is the main algorithm procedure.
Triangle t is compared with a unit cube,
centered on the origin.
It returns INSIDE (0) or OUTSIDE(1) if t
intersects or does not intersect the cube.
*/
static int
triangle_intersects_cube (
const Triangle& t
) {
int v1_test;
int v2_test;
int v3_test;
/*
First compare all three vertexes with all six face-planes
If any vertex is inside the cube, return immediately!
*/
if ((v1_test = face_plane(t.v1())) == INSIDE) return(INSIDE);
if ((v2_test = face_plane(t.v2())) == INSIDE) return(INSIDE);
if ((v3_test = face_plane(t.v3())) == INSIDE) return(INSIDE);
/*
If all three vertexes were outside of one or more face-planes,
return immediately with a trivial rejection!
*/
if ((v1_test & v2_test & v3_test) != 0) return(OUTSIDE);
/*
Now do the same trivial rejection test for the 12 edge planes
*/
v1_test |= bevel_2d(t.v1()) << 8;
v2_test |= bevel_2d(t.v2()) << 8;
v3_test |= bevel_2d(t.v3()) << 8;
if ((v1_test & v2_test & v3_test) != 0) return(OUTSIDE);
/*
Now do the same trivial rejection test for the 8 corner planes
*/
v1_test |= bevel_3d(t.v1()) << 24;
v2_test |= bevel_3d(t.v2()) << 24;
v3_test |= bevel_3d(t.v3()) << 24;
if ((v1_test & v2_test & v3_test) != 0) return(OUTSIDE);
/*
If vertex 1 and 2, as a pair, cannot be trivially rejected
by the above tests, then see if the v1-->v2 triangle edge
intersects the cube. Do the same for v1-->v3 and v2-->v3./
Pass to the intersection algorithm the "OR" of the outcode
bits, so that only those cube faces which are spanned by
each triangle edge need be tested.
*/
if ((v1_test & v2_test) == 0)
if (check_line (t.v1(), t.v2(), v1_test|v2_test) == INSIDE) return(INSIDE);
if ((v1_test & v3_test) == 0)
if (check_line (t.v1(), t.v3(), v1_test|v3_test) == INSIDE) return(INSIDE);
if ((v2_test & v3_test) == 0)
if (check_line (t.v2(), t.v3(), v2_test|v3_test) == INSIDE) return(INSIDE);
/*
By now, we know that the triangle is not off to any side,
and that its sides do not penetrate the cube. We must now
test for the cube intersecting the interior of the triangle.
We do this by looking for intersections between the cube
diagonals and the triangle...first finding the intersection
of the four diagonals with the plane of the triangle, and
then if that intersection is inside the cube, pursuing
whether the intersection point is inside the triangle itself.
To find plane of the triangle, first perform crossproduct on
two triangle side vectors to compute the normal vector.
*/
Vector_t vect12 = t.v1() - t.v2();
Vector_t vect13 = t.v1() - t.v3();
Vector_t norm = cross (vect12, vect13);
/*
The normal vector "norm" X,Y,Z components are the coefficients
of the triangles AX + BY + CZ + D = 0 plane equation. If we
solve the plane equation for X=Y=Z (a diagonal), we get
-D/(A+B+C) as a metric of the distance from cube center to the
diagonal/plane intersection. If this is between -0.5 and 0.5,
the intersection is inside the cube. If so, we continue by
doing a point/triangle intersection.
Do this for all four diagonals.
*/
double d = norm[0] * t.v1(0) + norm[1] * t.v1(1) + norm[2] * t.v1(2);
/*
if one of the diagonals is parallel to the plane, the other will
intersect the plane
*/
double denom = norm[0] + norm[1] + norm[2];
if (cmp::eq_zero(std::abs(denom)) == false) {
/* skip parallel diagonals to the plane; division by 0 can occure */
Vector_t hitpp = d / denom;
if (cmp::le(std::abs(hitpp[0]), 0.5))
if (point_triangle_intersection(hitpp,t) == INSIDE)
return(INSIDE);
}
denom = norm[0] + norm[1] - norm[2];
if (cmp::eq_zero(std::abs(denom)) == false) {
Vector_t hitpn;
hitpn[2] = -(hitpn[0] = hitpn[1] = d / denom);
if (cmp::le(std::abs(hitpn[0]), 0.5))
if (point_triangle_intersection(hitpn,t) == INSIDE)
return(INSIDE);
}
denom = norm[0] - norm[1] + norm[2];
if (cmp::eq_zero(std::abs(denom)) == false) {
Vector_t hitnp;
hitnp[1] = -(hitnp[0] = hitnp[2] = d / denom);
if (cmp::le(std::abs(hitnp[0]), 0.5))
if (point_triangle_intersection(hitnp,t) == INSIDE)
return(INSIDE);
}
denom = norm[0] - norm[1] - norm[2];
if (cmp::eq_zero(std::abs(denom)) == false) {
Vector_t hitnn;
hitnn[1] = hitnn[2] = -(hitnn[0] = d / denom);
if (cmp::le(std::abs(hitnn[0]), 0.5))
if (point_triangle_intersection(hitnn,t) == INSIDE)
return(INSIDE);
}
/*
No edge touched the cube; no cube diagonal touched the triangle.
We're done...there was no intersection.
*/
return(OUTSIDE);
}
/*
* Ray class, for use with the optimized ray-box intersection test
* described in:
*
* Amy Williams, Steve Barrus, R. Keith Morley, and Peter Shirley
* "An Efficient and Robust Ray-Box Intersection Algorithm"
* Journal of graphics tools, 10(1):49-54, 2005
*
*/
class Ray {
public:
Ray () { }
Ray (Vector_t o, Vector_t d) {
origin = o;
direction = d;
inv_direction = Vector_t (1/d[0], 1/d[1], 1/d[2]);
sign[0] = (inv_direction[0] < 0);
sign[1] = (inv_direction[1] < 0);
sign[2] = (inv_direction[2] < 0);
}
Ray(const Ray &r) {
origin = r.origin;
direction = r.direction;
inv_direction = r.inv_direction;
sign[0] = r.sign[0]; sign[1] = r.sign[1]; sign[2] = r.sign[2];
}
const Ray &operator=(const Ray& a) = delete;
Vector_t origin;
Vector_t direction;
Vector_t inv_direction;
int sign[3];
};
/*
* Axis-aligned bounding box class, for use with the optimized ray-box
* intersection test described in:
*
* Amy Williams, Steve Barrus, R. Keith Morley, and Peter Shirley
* "An Efficient and Robust Ray-Box Intersection Algorithm"
* Journal of graphics tools, 10(1):49-54, 2005
*
*/
class Voxel {
public:
Voxel () { }
Voxel (const Vector_t &min, const Vector_t &max) {
pts[0] = min;
pts[1] = max;
}
inline void scale (
const Vector_t& scale
) {
pts[0][0] *= scale[0];
pts[0][1] *= scale[1];
pts[0][2] *= scale[2];
pts[1][0] *= scale[0];
pts[1][1] *= scale[1];
pts[1][2] *= scale[2];
}
// (t0, t1) is the interval for valid hits
bool intersect (
const Ray& r,
double& tmin, // tmin and tmax are unchanged, if there is
double& tmax // no intersection
) const {
double tmin_ = (pts[r.sign[0]][0] - r.origin[0]) * r.inv_direction[0];
double tmax_ = (pts[1-r.sign[0]][0] - r.origin[0]) * r.inv_direction[0];
const double tymin = (pts[r.sign[1]][1] - r.origin[1]) * r.inv_direction[1];
const double tymax = (pts[1-r.sign[1]][1] - r.origin[1]) * r.inv_direction[1];
if ( cmp::gt(tmin_, tymax) || cmp::gt(tymin, tmax_) )
return 0; // no intersection
if (cmp::gt(tymin, tmin_))
tmin_ = tymin;
if (cmp::lt(tymax, tmax_))
tmax_ = tymax;
const double tzmin = (pts[r.sign[2]][2] - r.origin[2]) * r.inv_direction[2];
const double tzmax = (pts[1-r.sign[2]][2] - r.origin[2]) * r.inv_direction[2];
if ( cmp::gt(tmin_, tzmax) || cmp::gt(tzmin, tmax_) )
return 0; // no intersection
if (cmp::gt(tzmin, tmin_))
tmin_ = tzmin;
tmin = tmin_;
if (cmp::lt(tzmax, tmax_))
tmax_ = tzmax;
tmax = tmax_;
return cmp::ge_zero(tmax);
}
inline bool intersect (
const Ray& r
) const {
double tmin = 0.0;
double tmax = 0.0;
return intersect(r, tmin, tmax);
}
inline int intersect (
const Triangle& t
) const {
Voxel v_ = *this;
Triangle t_ = t;
const Vector_t scaleby = 1.0 / v_.extent();
v_.scale (scaleby);
t_.scale (scaleby , v_.pts[0] + 0.5);
return triangle_intersects_cube (t_);
}
inline Vector_t extent () const {
return (pts[1] - pts[0]);
}
inline bool isInside (
const Vector_t& P
) const {
return (
cmp::ge(P[0], pts[0][0])
&& cmp::ge(P[1], pts[0][1])
&& cmp::ge(P[2], pts[0][2])
&& cmp::le(P[0], pts[1][0])
&& cmp::le(P[1], pts[1][1])
&& cmp::le(P[2], pts[1][2]));
}
Vector_t pts[2];
};
static inline Vector_t normalVector (
const Vector_t& A,
const Vector_t& B,
const Vector_t& C
) {
const Vector_t N = cross (B - A, C - A);
const double magnitude = std::sqrt (SQR (N (0)) + SQR (N (1)) + SQR (N (2)));
PAssert (cmp::gt_zero(magnitude)); // in case we have degenerated triangles
return N / magnitude;
}
// Calculate the area of triangle given by id.
static inline double computeArea (
const Vector_t& A,
const Vector_t& B,
const Vector_t& C
) {
const Vector_t AB = A - B;
const Vector_t AC = C - A;
return(0.5 * std::sqrt (dot (AB, AB) * dot (AC, AC) - dot (AB, AC) * dot (AB, AC)));
}
/*
____ _
/ ___| ___ ___ _ __ ___ ___| |_ _ __ _ _
| | _ / _ \/ _ \| '_ ` _ \ / _ \ __| '__| | | |
| |_| | __/ (_) | | | | | | __/ |_| | | |_| |
\____|\___|\___/|_| |_| |_|\___|\__|_| \__, |
|___/
*/
BoundaryGeometry::BoundaryGeometry() :
Definition (
SIZE, "GEOMETRY", "The \"GEOMETRY\" statement defines the beam pipe geometry.") {
itsAttr[FGEOM] = Attributes::makeString
("FGEOM",
"Specifies the geometry file [H5hut]",
"");
itsAttr[TOPO] = Attributes::makeString
("TOPO",
"RECTANGULAR, BOXCORNER, ELLIPTIC if FGEOM is selected topo is over-written ",
"ELLIPTIC");
itsAttr[LENGTH] = Attributes::makeReal
("LENGTH",
"Specifies the length of a tube shaped elliptic beam pipe [m]",
1.0);
itsAttr[S] = Attributes::makeReal
("S",
"Specifies the start of a tube shaped elliptic beam pipe [m]",
0.0);
itsAttr[A] = Attributes::makeReal
("A",
"Specifies the major semi-axis of a tube shaped elliptic beam pipe [m]",
0.025);
itsAttr[B] = Attributes::makeReal
("B",
"Specifies the major semi-axis of a tube shaped elliptic beam pipe [m]",
0.025);
itsAttr[L1] = Attributes::makeReal
("L1",
"In case of BOXCORNER Specifies first part with height == B [m]",
0.5);
itsAttr[L2] = Attributes::makeReal
("L2",
"In case of BOXCORNER Specifies first second with height == B-C [m]",
0.2);
itsAttr[C] = Attributes::makeReal
("C",
"In case of BOXCORNER Specifies height of corner C [m]",
0.01);
itsAttr[XYZSCALE] = Attributes::makeReal
("XYZSCALE",
"Multiplicative scaling factor for coordinates ",
1.0);
itsAttr[XSCALE] = Attributes::makeReal
("XSCALE",
"Multiplicative scaling factor for X coordinates ",
1.0);
itsAttr[YSCALE] = Attributes::makeReal
("YSCALE",
"Multiplicative scaling factor for Y coordinates ",
1.0);
itsAttr[ZSCALE] = Attributes::makeReal
("ZSCALE",
"Multiplicative scaling factor for Z coordinates ",
1.0);
itsAttr[ZSHIFT] = Attributes::makeReal
("ZSHIFT",
"Shift in z direction",
0.0);
itsAttr[INSIDEPOINT] = Attributes::makeRealArray
("INSIDEPOINT", "A point inside the geometry");
registerOwnership(AttributeHandler::STATEMENT);
BoundaryGeometry* defGeometry = clone ("UNNAMED_GEOMETRY");
defGeometry->builtin = true;
Tinitialize_m = IpplTimings::getTimer ("Initialize geometry");
TisInside_m = IpplTimings::getTimer ("Inside test");
TfastIsInside_m = IpplTimings::getTimer ("Fast inside test");
TRayTrace_m = IpplTimings::getTimer ("Ray tracing");
TPartInside_m = IpplTimings::getTimer ("Particle Inside");
h5FileName_m = Attributes::getString (itsAttr[FGEOM]);
try {
defGeometry->update ();
OpalData::getInstance ()->define (defGeometry);
} catch (...) {
delete defGeometry;
}
gsl_rng_env_setup();
randGen_m = gsl_rng_alloc(gsl_rng_default);
if (!h5FileName_m.empty ())
initialize ();
}
BoundaryGeometry::BoundaryGeometry(
const std::string& name,
BoundaryGeometry* parent
) : Definition (name, parent) {
gsl_rng_env_setup();
randGen_m = gsl_rng_alloc(gsl_rng_default);
Tinitialize_m = IpplTimings::getTimer ("Initialize geometry");
TisInside_m = IpplTimings::getTimer ("Inside test");
TfastIsInside_m = IpplTimings::getTimer ("Fast inside test");
TRayTrace_m = IpplTimings::getTimer ("Ray tracing");
TPartInside_m = IpplTimings::getTimer ("Particle Inside");
h5FileName_m = Attributes::getString (itsAttr[FGEOM]);
if (!h5FileName_m.empty ())
initialize ();
}
BoundaryGeometry::~BoundaryGeometry() {
gsl_rng_free(randGen_m);
}
bool BoundaryGeometry::canReplaceBy (Object* object) {
// Can replace only by another GEOMETRY.
return dynamic_cast(object) != 0;
}
BoundaryGeometry* BoundaryGeometry::clone (const std::string& name) {
return new BoundaryGeometry (name, this);
}
void BoundaryGeometry::update () {
if (getOpalName ().empty ()) setOpalName ("UNNAMED_GEOMETRY");
}
void BoundaryGeometry::execute () {
update ();
Tinitialize_m = IpplTimings::getTimer ("Initialize geometry");
TisInside_m = IpplTimings::getTimer ("Inside test");
TfastIsInside_m = IpplTimings::getTimer ("Fast inside test");
TRayTrace_m = IpplTimings::getTimer ("Ray tracing");
TPartInside_m = IpplTimings::getTimer ("Particle Inside");
}
BoundaryGeometry* BoundaryGeometry::find (const std::string& name) {
BoundaryGeometry* geom = dynamic_cast(
OpalData::getInstance ()->find (name));
if (geom == 0)
throw OpalException ("BoundaryGeometry::find()", "Geometry \""
+ name + "\" not found.");
return geom;
}
void BoundaryGeometry::updateElement (ElementBase* /*element*/) {
}
int
BoundaryGeometry::intersectTriangleVoxel (
const int triangle_id,
const int i,
const int j,
const int k
) {
const Triangle t(
getPoint (triangle_id, 1),
getPoint (triangle_id, 2),
getPoint (triangle_id, 3)
);
const Vector_t P(
i * voxelMesh_m.sizeOfVoxel [0] + voxelMesh_m.minExtent[0],
j * voxelMesh_m.sizeOfVoxel [1] + voxelMesh_m.minExtent[1],
k * voxelMesh_m.sizeOfVoxel [2] + voxelMesh_m.minExtent[2]
);
Voxel v(P, P+voxelMesh_m.sizeOfVoxel);
return v.intersect (t);
}
/*
Find the 3D intersection of a line segment, ray or line with a triangle.
Input:
kind: type of test: SEGMENT, RAY or LINE
P0, P0: defining
a line segment from P0 to P1 or
a ray starting at P0 with directional vector P1-P0 or
a line through P0 and P1
V0, V1, V2: the triangle vertices
Output:
I: intersection point (when it exists)
Return values for line segment and ray test :
-1 = triangle is degenerated (a segment or point)
0 = disjoint (no intersect)
1 = are in the same plane
2 = intersect in unique point I1
Return values for line intersection test :
-1: triangle is degenerated (a segment or point)
0: disjoint (no intersect)
1: are in the same plane
2: intersect in unique point I1, with r < 0.0
3: intersect in unique point I1, with 0.0 <= r <= 1.0
4: intersect in unique point I1, with 1.0 < r
For algorithm and implementation see:
http://geomalgorithms.com/a06-_intersect-2.html
Copyright 2001 softSurfer, 2012 Dan Sunday
This code may be freely used and modified for any purpose
providing that this copyright notice is included with it.
SoftSurfer makes no warranty for this code, and cannot be held
liable for any real or imagined damage resulting from its use.
Users of this code must verify correctness for their application.
*/
int
BoundaryGeometry::intersectLineTriangle (
const enum INTERSECTION_TESTS kind,
const Vector_t& P0,
const Vector_t& P1,
const int triangle_id,
Vector_t& I
) {
const Vector_t V0 = getPoint (triangle_id, 1);
const Vector_t V1 = getPoint (triangle_id, 2);
const Vector_t V2 = getPoint (triangle_id, 3);
// get triangle edge vectors and plane normal
const Vector_t u = V1 - V0; // triangle vectors
const Vector_t v = V2 - V0;
const Vector_t n = cross (u, v);
if (n == (Vector_t)0) // triangle is degenerate
return -1; // do not deal with this case
const Vector_t dir = P1 - P0; // ray direction vector
const Vector_t w0 = P0 - V0;
const double a = -dot(n,w0);
const double b = dot(n,dir);
if (cmp::eq_zero(b)) { // ray is parallel to triangle plane
if (cmp::eq_zero(a)) { // ray lies in triangle plane
return 1;
} else { // ray disjoint from plane
return 0;
}
}
// get intersect point of ray with triangle plane
const double r = a / b;
switch (kind) {
case RAY:
if (cmp::lt_zero(r)) { // ray goes away from triangle
return 0; // => no intersect
}
break;
case SEGMENT:
if (cmp::lt_zero(r) || cmp::lt(1.0, r)) { // intersection on extended
return 0; // segment
}
break;
case LINE:
break;
};
I = P0 + r * dir; // intersect point of ray and plane
// is I inside T?
const double uu = dot(u,u);
const double uv = dot(u,v);
const double vv = dot(v,v);
const Vector_t w = I - V0;
const double wu = dot(w,u);
const double wv = dot(w,v);
const double D = uv * uv - uu * vv;
// get and test parametric coords
const double s = (uv * wv - vv * wu) / D;
if (cmp::lt_zero(s) || cmp::gt(s, 1.0)) { // I is outside T
return 0;
}
const double t = (uv * wu - uu * wv) / D;
if (cmp::lt_zero(t) || cmp::gt((s + t), 1.0)) { // I is outside T
return 0;
}
// intersection point is in triangle
if (cmp::lt_zero(r)) { // in extended segment in opposite
return 2; // direction of ray
} else if (cmp::ge_zero(r) && cmp::le(r, 1.0)) { // in segment
return 3;
} else { // in extended segment in
return 4; // direction of ray
}
}
static inline double magnitude (
const Vector_t& v
) {
return std::sqrt (dot (v,v));
}
bool
BoundaryGeometry::isInside (
const Vector_t& P // [in] pt to test
) {
/*
select a "close" reference pt outside the bounding box
*/
// right boundary of bounding box (x direction)
double x = minExtent_m[0] - 0.01;
double distance = P[0] - x;
Vector_t ref_pt {x, P[1], P[2]};
// left boundary of bounding box (x direction)
x = maxExtent_m[0] + 0.01;
if (cmp::lt(x - P[0], distance)) {
distance = x - P[0];
ref_pt = {x, P[1], P[2]};
}
// lower boundary of bounding box (y direction)
double y = minExtent_m[1] - 0.01;
if (cmp::lt(P[1] - y, distance)) {
distance = P[1] -y;
ref_pt = {P[0], y, P[1]};
}
// upper boundary of bounding box (y direction)
y = maxExtent_m[1] + 0.01;
if (cmp::lt(y - P[1], distance)) {
distance = y - P[1];
ref_pt = {P[0], y, P[2]};
}
// front boundary of bounding box (z direction)
double z = minExtent_m[2] - 0.01;
if (cmp::lt(P[2] - z, distance)) {
distance = P[2] - z;
ref_pt = {P[0], P[1], z};
}
// back boundary of bounding box (z direction)
z = maxExtent_m[2] + 0.01;
if (cmp::lt(z - P[2], distance)) {
ref_pt = {P[0], P[1], z};
}
/*
the test returns the number of intersections =>
since the reference point is outside, P is inside
if the result is odd.
*/
int k = fastIsInside (ref_pt, P);
return (k % 2) == 1;
}
/*
searching a point inside the geometry.
sketch of the algorithm:
In a first step, we try to find a line segment defined by one
point outside the bounding box and a point somewhere inside the
bounding box which has intersects with the geometry.
If the number of intersections is odd, the center point is inside
the geometry and we are already done.
If the number of intersections is even, there must be points on
this line segment which are inside the geometry. In the next step
we have to find one if these points.
A bit more in detail:
1. Finding a line segment intersecting the geometry
For the fast isInside test it is of advantage to choose line segments
parallel to the X, Y or Z axis. In this implementation we choose as
point outside the bounding box a point on an axis but close to the
bounding box and the center of the bounding box. This gives us six
line segments to test. This covers not all possible geometries but
most likely almost all. If not, it's easy to extend.
2. Searching for a point inside the geometry
In the first step we get a line segment from which we know, that one
point is ouside the geometry (P_out) and the other inside the bounding
box (Q). We also know the number of intersections n_i of this line
segment with the geometry.
If n_i is odd, Q is inside the boundary!
while (true); do
bisect the line segment [P_out, Q], let B the bisecting point.
compute number of intersections of the line segment [P_out, B]
and the geometry.
If the number of intersections is odd, then B is inside the geometry
and we are done. Set P_in = B and exit loop.
Otherwise we have either no or an even number of intersections.
In both cases this implies that B is a point outside the geometry.
If the number of intersection of [P_out, B] is even but not equal zero,
it might be that *all* intersections are in this line segment and none in
[B, Q].
In this case we continue with the line segment [P_out, Q] = [P_out, B],
otherwise with the line segment [P_out, Q] = [B, Q].
*/
bool
BoundaryGeometry::findInsidePoint (
void
) {
*gmsg << "* searching for a point inside the geometry" << endl;
/*
find line segment
*/
Vector_t Q {(maxExtent_m + minExtent_m) / 2};
std::vector P_outs {
{minExtent_m[0]-0.01, Q[1], Q[2]},
{maxExtent_m[0]+0.01, Q[1], Q[2]},
{Q[0], minExtent_m[1]-0.01, Q[2]},
{Q[0], maxExtent_m[1]+0.01, Q[2]},
{Q[0], Q[1], minExtent_m[2]-0.01},
{Q[0], Q[1], maxExtent_m[2]+0.01}
};
int n_i = 0;
Vector_t P_out;
for (const auto& P: P_outs) {
n_i = fastIsInside (P, Q);
if (n_i != 0) {
P_out = P;
break;
}
}
if (n_i == 0) {
// this is possible with some obscure geometries.
return false;
}
/*
if the number of intersections is odd, Q is inside the geometry
*/
if (n_i % 2 == 1) {
insidePoint_m = Q;
return true;
}
while (true) {
Vector_t B {(P_out + Q) / 2};
int n = fastIsInside (P_out, B);
if (n % 2 == 1) {
insidePoint_m = B;
return true;
} else if (n == n_i) {
Q = B;
} else {
P_out = B;
}
n_i = n;
}
// never reached
return false;
}
/*
Game plan:
Count number of intersection of the line segment defined by P and a reference
pt with the boundary. If the reference pt is inside the boundary and the number
of intersections is even, then P is inside the geometry. Otherwise P is outside.
To count the number of intersection, we divide the line segment in N segments
and run the line-segment boundary intersection test for all these segments.
N must be choosen carefully. It shouldn't be to large to avoid needless test.
*/
int
BoundaryGeometry::fastIsInside (
const Vector_t& reference_pt, // [in] reference pt inside the boundary
const Vector_t& P // [in] pt to test
) {
const Voxel c (minExtent_m, maxExtent_m);
if (!c.isInside (P)) return 1;
IpplTimings::startTimer (TfastIsInside_m);
#ifdef ENABLE_DEBUG
int saved_flags = debugFlags_m;
if (debugFlags_m & debug_fastIsInside) {
*gmsg << "* " << __func__ << ": "
<< "reference_pt=" << reference_pt
<< ", P=" << P << endl;
debugFlags_m |= debug_intersectTinyLineSegmentBoundary;
}
#endif
const Vector_t v = reference_pt - P;
const int N = std::ceil (magnitude (v) / std::min ({voxelMesh_m.sizeOfVoxel [0],
voxelMesh_m.sizeOfVoxel [1],
voxelMesh_m.sizeOfVoxel [2]}));
const Vector_t v_ = v / N;
Vector_t P0 = P;
Vector_t P1 = P + v_;
Vector_t I;
int triangle_id = -1;
int result = 0;
for (int i = 0; i < N; i++) {
result += intersectTinyLineSegmentBoundary (P0, P1, I, triangle_id);
P0 = P1;
P1 += v_;
}
#ifdef ENABLE_DEBUG
if (debugFlags_m & debug_fastIsInside) {
*gmsg << "* " << __func__ << ": "
<< "result: " << result << endl;
debugFlags_m = saved_flags;
}
#endif
IpplTimings::stopTimer (TfastIsInside_m);
return result;
}
/*
P must be *inside* the boundary geometry!
return value:
0 no intersection
1 intersection found, I is set to the first intersection coordinates in
ray direction
*/
int
BoundaryGeometry::intersectRayBoundary (
const Vector_t& P,
const Vector_t& v,
Vector_t& I
) {
IpplTimings::startTimer (TRayTrace_m);
#ifdef ENABLE_DEBUG
int saved_flags = debugFlags_m;
if (debugFlags_m & debug_intersectRayBoundary) {
*gmsg << "* " << __func__ << ": "
<< " ray: "
<< " origin=" << P
<< " dir=" << v
<< endl;
debugFlags_m |= debug_intersectLineSegmentBoundary;
}
#endif
/*
set P1 to intersection of ray with bbox of voxel mesh
run line segment boundary intersection test with P and P1
*/
Ray r = Ray (P, v);
Voxel c = Voxel (voxelMesh_m.minExtent+0.25*voxelMesh_m.sizeOfVoxel,
voxelMesh_m.maxExtent-0.25*voxelMesh_m.sizeOfVoxel);
double tmin = 0.0;
double tmax = 0.0;
c.intersect (r, tmin, tmax);
int triangle_id = -1;
int result = (intersectLineSegmentBoundary (
P, P + (tmax*v),
I, triangle_id) > 0) ? 1 : 0;
#ifdef ENABLE_DEBUG
if (debugFlags_m & debug_intersectRayBoundary) {
*gmsg << "* " << __func__ << ": "
<< " result=" << result
<< " I=" << I
<< endl;
debugFlags_m = saved_flags;
}
#endif
IpplTimings::stopTimer (TRayTrace_m);
return result;
}
/*
Map point to unique voxel ID.
Remember:
* hr_m: is the mesh size
* nr_m: number of mesh points
*/
inline int
BoundaryGeometry::mapVoxelIndices2ID (
const int i,
const int j,
const int k
) {
if (i < 0 || i >= voxelMesh_m.nr_m[0] ||
j < 0 || j >= voxelMesh_m.nr_m[1] ||
k < 0 || k >= voxelMesh_m.nr_m[2]) {
return 0;
}
return 1 + k * voxelMesh_m.nr_m[0] * voxelMesh_m.nr_m[1] + j * voxelMesh_m.nr_m[0] + i;
}
#define mapPoint2VoxelIndices(pt, i, j, k) { \
i = floor ((pt[0] - voxelMesh_m.minExtent [0]) / voxelMesh_m.sizeOfVoxel[0]); \
j = floor ((pt[1] - voxelMesh_m.minExtent [1]) / voxelMesh_m.sizeOfVoxel[1]); \
k = floor ((pt[2] - voxelMesh_m.minExtent [2]) / voxelMesh_m.sizeOfVoxel[2]); \
if (!(0 <= i && i < voxelMesh_m.nr_m[0] && \
0 <= j && j < voxelMesh_m.nr_m[1] && \
0 <= k && k < voxelMesh_m.nr_m[2])) { \
*gmsg << "* " << __func__ << ":" \
<< " WARNING: pt=" << pt \
<< " is outside the bbox" \
<< " i=" << i \
<< " j=" << j \
<< " k=" << k \
<< endl; \
} \
}
inline Vector_t
BoundaryGeometry::mapIndices2Voxel (
const int i,
const int j,
const int k
) {
return Vector_t (
i * voxelMesh_m.sizeOfVoxel [0] + voxelMesh_m.minExtent[0],
j * voxelMesh_m.sizeOfVoxel [1] + voxelMesh_m.minExtent[1],
k * voxelMesh_m.sizeOfVoxel [2] + voxelMesh_m.minExtent[2]);
}
inline Vector_t
BoundaryGeometry::mapPoint2Voxel (
const Vector_t& pt
) {
const int i = std::floor ((pt[0] - voxelMesh_m.minExtent [0]) / voxelMesh_m.sizeOfVoxel [0]);
const int j = std::floor ((pt[1] - voxelMesh_m.minExtent [1]) / voxelMesh_m.sizeOfVoxel [1]);
const int k = std::floor ((pt[2] - voxelMesh_m.minExtent [2]) / voxelMesh_m.sizeOfVoxel [2]);
return mapIndices2Voxel (i, j, k);
}
inline void
BoundaryGeometry::computeMeshVoxelization (void) {
for (unsigned int triangle_id = 0; triangle_id < Triangles_m.size(); triangle_id++) {
Vector_t v1 = getPoint (triangle_id, 1);
Vector_t v2 = getPoint (triangle_id, 2);
Vector_t v3 = getPoint (triangle_id, 3);
Vector_t bbox_min = {
std::min({v1[0], v2[0], v3[0]}),
std::min({v1[1], v2[1], v3[1]}),
std::min({v1[2], v2[2], v3[2]}) };
Vector_t bbox_max = {
std::max({v1[0], v2[0], v3[0]}),
std::max({v1[1], v2[1], v3[1]}),
std::max({v1[2], v2[2], v3[2]}) };
int i_min, j_min, k_min;
int i_max, j_max, k_max;
mapPoint2VoxelIndices (bbox_min, i_min, j_min, k_min);
mapPoint2VoxelIndices (bbox_max, i_max, j_max, k_max);
for (int i = i_min; i <= i_max; i++) {
for (int j = j_min; j <= j_max; j++) {
for (int k = k_min; k <= k_max; k++) {
// test if voxel (i,j,k) has an intersection with triangle
if (intersectTriangleVoxel (triangle_id, i, j, k) == INSIDE) {
auto id = mapVoxelIndices2ID (i, j, k);
voxelMesh_m.ids [id].insert (triangle_id);
}
}
}
}
} // for_each triangle
*gmsg << "* Mesh voxelization done." << endl;
if(Ippl::myNode() == 0) {
write_voxel_mesh (voxelMesh_m.ids,
voxelMesh_m.sizeOfVoxel,
voxelMesh_m.nr_m,
voxelMesh_m.minExtent);
}
}
void BoundaryGeometry::initialize () {
class Local {
public:
static void computeGeometryInterval (BoundaryGeometry* bg) {
bg->minExtent_m = get_min_extent (bg->Points_m);
bg->maxExtent_m = get_max_extent (bg->Points_m);
/*
Calculate the maximum size of triangles. This value will be used to
define the voxel size
*/
double longest_edge_max_m = 0.0;
for (unsigned int i = 0; i < bg->Triangles_m.size(); i++) {
// compute length of longest edge
const Vector_t x1 = bg->getPoint (i, 1);
const Vector_t x2 = bg->getPoint (i, 2);
const Vector_t x3 = bg->getPoint (i, 3);
const double length_edge1 = std::sqrt (
SQR (x1[0] - x2[0]) + SQR (x1[1] - x2[1]) + SQR (x1[2] - x2[2]));
const double length_edge2 = std::sqrt (
SQR (x3[0] - x2[0]) + SQR (x3[1] - x2[1]) + SQR (x3[2] - x2[2]));
const double length_edge3 = std::sqrt (
SQR (x3[0] - x1[0]) + SQR (x3[1] - x1[1]) + SQR (x3[2] - x1[2]));
double max = length_edge1;
if (length_edge2 > max) max = length_edge2;
if (length_edge3 > max) max = length_edge3;
// save min and max of length of longest edge
if (longest_edge_max_m < max) longest_edge_max_m = max;
}
/*
In principal the number of discretization nr_m is the extent of
the geometry divided by the extent of the largest triangle. Whereby
the extent of a triangle is defined as the lenght of its longest
edge. Thus the largest triangle is the triangle with the longest edge.
But if the hot spot, i.e., the multipacting/field emission zone is
too small that the normal bounding box covers the whole hot spot, the
expensive triangle-line intersection tests will be frequently called.
In these cases, we have to use smaller bounding box size to speed up
simulation.
Todo:
The relation between bounding box size and simulation time step &
geometry shape maybe need to be summarized and modeled in a more
flexible manner and could be adjusted in input file.
*/
Vector_t extent = bg->maxExtent_m - bg->minExtent_m;
bg->voxelMesh_m.nr_m (0) = 16 * (int)std::floor (extent [0] / longest_edge_max_m);
bg->voxelMesh_m.nr_m (1) = 16 * (int)std::floor (extent [1] / longest_edge_max_m);
bg->voxelMesh_m.nr_m (2) = 16 * (int)std::floor (extent [2] / longest_edge_max_m);
bg->voxelMesh_m.sizeOfVoxel = extent / bg->voxelMesh_m.nr_m;
bg->voxelMesh_m.minExtent = bg->minExtent_m - 0.5 * bg->voxelMesh_m.sizeOfVoxel;
bg->voxelMesh_m.maxExtent = bg->maxExtent_m + 0.5 * bg->voxelMesh_m.sizeOfVoxel;
bg->voxelMesh_m.nr_m += 1;
}
/*
To speed up ray-triangle intersection tests, the normal vector of
all triangles are pointing inward. Since this is clearly not
guaranteed for the triangles in the H5hut file, this must be checked
for each triangle and - if necessary changed - after reading the mesh.
To test whether the normal of a triangle is pointing inward or outward,
we choose a random point P close to the center of the triangle and test
whether this point is inside or outside the geometry. The way we choose
P guarantees that the vector spanned by P and a vertex of the triangle
points into the same direction as the normal vector. From this it
follows that if P is inside the geometry the normal vector is pointing
to the inside and vise versa.
Since the inside-test is computational expensive we perform this test
for one reference triangle T (per sub-mesh) only. Knowing the adjacent
triangles for all three edges of a triangle for all triangles of the
mesh facilitates another approach using the orientation of the
reference triangle T. Assuming that the normal vector of T points to
the inside of the geometry an adjacent triangle of T has an inward
pointing normal vector if and only if it has the same orientation as
T.
Starting with the reference triangle T we can change the orientation
of the adjancent triangle of T and so on.
NOTE: For the time being we do not make use of the inward pointing
normals.
FIXME: Describe the basic ideas behind the following comment! Without
it is completely unclear.
Following combinations are possible:
1,1 && 2,2 1,2 && 2,1 1,3 && 2,1
1,1 && 2,3 1,2 && 2,3 1,3 && 2,2
1,1 && 3,2 1,2 && 3,1 1,3 && 3,1
1,1 && 3,3 1,2 && 3,3 1,3 && 3,2
(2,1 && 1,2) (2,2 && 1,1) (2,3 && 1,1)
(2,1 && 1,3) (2,2 && 1,3) (2,3 && 1,2)
2,1 && 3,2 2,2 && 3,1 2,3 && 3,1
2,1 && 3,3 2,2 && 3,3 2,3 && 3,2
(3,1 && 1,2) (3,2 && 1,1) (3,3 && 1,1)
(3,1 && 1,3) (3,2 && 1,3) (3,3 && 1,2)
(3,1 && 2,2) (3,2 && 2,1) (3,3 && 2,1)
(3,1 && 2,3) (3,2 && 2,3) (3,3 && 2,2)
Note:
Since we find vertices with lower enumeration first, we
can ignore combinations in ()
2 2 2 3 3 2 3 3
* * * *
/|\ /|\ /|\ /|\
/ | \ / | \ / | \ / | \
/ | \ / | \ / | \ / | \
/ | \ / | \ / | \ / | \
*----*----* *----*----* *----*----* *----*----*
3 1 1 3 3 1 1 2 2 1 1 3 2 1 1 2
diff: (1,1) (1,2) (2,1) (2,2)
change orient.: yes no no yes
2 1 2 3 3 1 3 3
* * * *
/|\ /|\ /|\ /|\
/ | \ / | \ / | \ / | \
/ | \ / | \ / | \ / | \
/ | \ / | \ / | \ / | \
*----*----* *----*----* *----*----* *----*----*
3 1 2 3 3 1 2 1 2 1 2 3 2 1 2 1
diff: (1,-1) (1,1) (2,-1) (2,1)
change orient.: no yes yes no
2 1 2 2 3 1 3 2
* * * *
/|\ /|\ /|\ /|\
/ | \ / | \ / | \ / | \
/ | \ / | \ / | \ / | \
/ | \ / | \ / | \ / | \
*----*----* *----*----* *----*----* *----*----*
3 1 3 2 3 1 3 1 2 1 3 2 2 1 3 1
diff: (1,-2) (1,-1) (2,-2) (2,-1)
change orient.: yes no no yes
3 2 3 3
* *
/|\ /|\
/ | \ / | \
/ | \ / | \
/ | \ / | \
*----*----* *----*----*
1 2 1 3 1 2 1 2
diff: (1,1) (1,2)
change orient.: yes no
3 1 3 3
* *
/|\ /|\
/ | \ / | \
/ | \ / | \
/ | \ / | \
*----*----* *----*----*
1 2 2 3 1 2 2 1
diff: (1,-1) (1,1)
change orient.: no yes
3 1 3 2
* *
/|\ /|\
/ | \ / | \
/ | \ / | \
/ | \ / | \
*----*----* *----*----*
1 2 3 2 1 2 3 1
diff: (1,-2) (1,-1)
change orient.: yes no
Change orientation if diff is:
(1,1) || (1,-2) || (2,2) || (2,-1) || (2,-1)
*/
static void computeTriangleNeighbors (
BoundaryGeometry* bg,
std::vector>& neighbors
) {
std::vector> adjacencies_to_pt (bg->Points_m.size());
// for each triangles find adjacent triangles for each vertex
for (unsigned int triangle_id = 0; triangle_id < bg->Triangles_m.size(); triangle_id++) {
for (unsigned int j = 1; j <= 3; j++) {
auto pt_id = bg->PointID (triangle_id, j);
PAssert (pt_id < bg->Points_m.size ());
adjacencies_to_pt [pt_id].insert (triangle_id);
}
}
for (unsigned int triangle_id = 0; triangle_id < bg->Triangles_m.size(); triangle_id++) {
std::set to_A = adjacencies_to_pt [bg->PointID (triangle_id, 1)];
std::set to_B = adjacencies_to_pt [bg->PointID (triangle_id, 2)];
std::set to_C = adjacencies_to_pt [bg->PointID (triangle_id, 3)];
std::set intersect;
std::set_intersection (
to_A.begin(), to_A.end(),
to_B.begin(), to_B.end(),
std::inserter(intersect,intersect.begin()));
std::set_intersection(
to_B.begin(), to_B.end(),
to_C.begin(), to_C.end(),
std::inserter(intersect,intersect.begin()));
std::set_intersection(
to_C.begin(), to_C.end(),
to_A.begin(), to_A.end(),
std::inserter(intersect, intersect.begin()));
intersect.erase (triangle_id);
neighbors [triangle_id] = intersect;
}
*gmsg << "* " << __func__ << ": Computing neighbors done" << endl;
}
/*
Helper function for hasInwardPointingNormal()
Determine if a point x is outside or inside the geometry or just on
the boundary. Return true if point is inside geometry or on the
boundary, false otherwise
The basic idea here is:
If a line segment from the point to test to a random point outside
the geometry has has an even number of intersections with the
boundary, the point is outside the geometry.
Note:
If the point is on the boundary, the number of intersections is 1.
Points on the boundary are handled as inside.
A random selection of the reference point outside the boundary avoids
some specific issues, like line parallel to boundary.
*/
static inline bool isInside (BoundaryGeometry* bg, const Vector_t x) {
IpplTimings::startTimer (bg->TisInside_m);
Vector_t y = Vector_t (
bg->maxExtent_m[0] * (1.1 + gsl_rng_uniform(bg->randGen_m)),
bg->maxExtent_m[1] * (1.1 + gsl_rng_uniform(bg->randGen_m)),
bg->maxExtent_m[2] * (1.1 + gsl_rng_uniform(bg->randGen_m)));
std::vector intersection_points;
//int num_intersections = 0;
for (unsigned int triangle_id = 0; triangle_id < bg->Triangles_m.size(); triangle_id++) {
Vector_t result;
if (bg->intersectLineTriangle (SEGMENT, x, y, triangle_id, result)) {
intersection_points.push_back (result);
//num_intersections++;
}
}
IpplTimings::stopTimer (bg->TisInside_m);
return ((intersection_points.size () % 2) == 1);
}
// helper for function makeTriangleNormalInwardPointing()
static bool hasInwardPointingNormal (
BoundaryGeometry* const bg,
const int triangle_id
) {
const Vector_t& A = bg->getPoint (triangle_id, 1);
const Vector_t& B = bg->getPoint (triangle_id, 2);
const Vector_t& C = bg->getPoint (triangle_id, 3);
const Vector_t triNormal = normalVector (A, B, C);
// choose a point P close to the center of the triangle
//const Vector_t P = (A+B+C)/3 + triNormal * 0.1;
double minvoxelmesh = bg->voxelMesh_m.sizeOfVoxel[0];
if (minvoxelmesh > bg->voxelMesh_m.sizeOfVoxel[1])
minvoxelmesh = bg->voxelMesh_m.sizeOfVoxel[1];
if (minvoxelmesh > bg->voxelMesh_m.sizeOfVoxel[2])
minvoxelmesh = bg->voxelMesh_m.sizeOfVoxel[2];
const Vector_t P = (A+B+C)/3 + triNormal * minvoxelmesh;
/*
The triangle normal points inward, if P is
- outside the geometry and the dot product is negativ
- or inside the geometry and the dot product is positiv
Remember:
The dot product is positiv only if both vectors are
pointing in the same direction.
*/
const bool is_inside = isInside (bg, P);
const double dotPA_N = dot (P - A, triNormal);
return (is_inside && dotPA_N >= 0) || (!is_inside && dotPA_N < 0);
}
// helper for function makeTriangleNormalInwardPointing()
static void orientTriangle (BoundaryGeometry* bg, int ref_id, int triangle_id) {
// find pts of common edge
int ic[2];
int id[2];
int n = 0;
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (bg->PointID (triangle_id, j) == bg->PointID (ref_id, i)) {
id[n] = j;
ic[n] = i;
n++;
if (n == 2) goto edge_found;
}
}
}
PAssert (n == 2);
edge_found:
int diff = id[1] - id[0];
if ((((ic[1] - ic[0]) == 1) && ((diff == 1) || (diff == -2))) ||
(((ic[1] - ic[0]) == 2) && ((diff == -1) || (diff == 2)))) {
std::swap (bg->PointID (triangle_id, id[0]), bg->PointID (triangle_id, id[1]));
}
}
static void makeTriangleNormalInwardPointing (BoundaryGeometry* bg) {
std::vector> neighbors (bg->Triangles_m.size());
computeTriangleNeighbors (bg, neighbors);
// loop over all sub-meshes
int triangle_id = 0;
int parts = 0;
std::vector triangles (bg->Triangles_m.size());
std::vector::size_type queue_cursor = 0;
std::vector::size_type queue_end = 0;
std::vector isOriented (bg->Triangles_m.size(), false);
do {
parts++;
/*
Find next untested triangle, trivial for the first sub-mesh.
There is a least one not yet tested triangle!
*/
while (isOriented [triangle_id])
triangle_id++;
// ensure that normal of this triangle is inward pointing
if (!hasInwardPointingNormal (bg, triangle_id)) {
std::swap (bg->PointID (triangle_id, 2), bg->PointID (triangle_id, 3));
}
isOriented [triangle_id] = true;
// loop over all triangles in sub-mesh
triangles [queue_end++] = triangle_id;
do {
for (auto neighbor_id: neighbors [triangle_id]) {
if (isOriented [neighbor_id]) continue;
orientTriangle (bg, triangle_id, neighbor_id);
isOriented [neighbor_id] = true;
triangles[queue_end++] = neighbor_id;
}
triangle_id = triangles [++queue_cursor];
} while (queue_cursor < queue_end);
} while (queue_end < bg->Triangles_m.size());
if (parts == 1) {
*gmsg << "* " << __func__ << ": mesh is contiguous." << endl;
} else {
*gmsg << "* " << __func__ << ": mesh is discontiguous (" << parts << ") parts." << endl;
}
*gmsg << "* Triangle Normal built done." << endl;
}
};
debugFlags_m = 0;
*gmsg << "* Initializing Boundary Geometry..." << endl;
IpplTimings::startTimer (Tinitialize_m);
*gmsg << "* Filename: " << h5FileName_m.c_str() << endl;
double xscale = Attributes::getReal(itsAttr[XSCALE]);
double yscale = Attributes::getReal(itsAttr[YSCALE]);
double zscale = Attributes::getReal(itsAttr[ZSCALE]);
double xyzscale = Attributes::getReal(itsAttr[XYZSCALE]);
double zshift = (double)(Attributes::getReal (itsAttr[ZSHIFT]));
*gmsg << "* X-scale all points of geometry by " << xscale << endl;
*gmsg << "* Y-scale all points of geometry by " << yscale << endl;
*gmsg << "* Z-scale all points of geometry by " << zscale << endl;
*gmsg << "* Scale all points of geometry by " << xyzscale << endl;
h5_int64_t rc;
#if defined (NDEBUG)
(void)rc;
#endif
rc = H5SetErrorHandler (H5AbortErrorhandler);
PAssert (rc != H5_ERR);
H5SetVerbosityLevel (1);
h5_prop_t props = H5CreateFileProp ();
MPI_Comm comm = Ippl::getComm();
H5SetPropFileMPIOCollective (props, &comm);
h5_file_t f = H5OpenFile (h5FileName_m.c_str(), H5_O_RDONLY, props);
H5CloseProp (props);
h5t_mesh_t* m = NULL;
H5FedOpenTriangleMesh (f, "0", &m);
H5FedSetLevel (m, 0);
auto numTriangles = H5FedGetNumElementsTotal (m);
Triangles_m.resize (numTriangles);
// iterate over all co-dim 0 entities, i.e. elements
h5_loc_id_t local_id;
int i = 0;
h5t_iterator_t* iter = H5FedBeginTraverseEntities (m, 0);
while ((local_id = H5FedTraverseEntities (iter)) >= 0) {
h5_loc_id_t local_vids[4];
H5FedGetVertexIndicesOfEntity (m, local_id, local_vids);
PointID (i, 0) = 0;
PointID (i, 1) = local_vids[0];
PointID (i, 2) = local_vids[1];
PointID (i, 3) = local_vids[2];
i++;
}
H5FedEndTraverseEntities (iter);
// loop over all vertices
int num_points = H5FedGetNumVerticesTotal (m);
Points_m.reserve (num_points);
for (i = 0; i < num_points; i++) {
h5_float64_t P[3];
H5FedGetVertexCoordsByIndex (m, i, P);
Points_m.push_back (Vector_t (
P[0] * xyzscale * xscale,
P[1] * xyzscale * yscale,
P[2] * xyzscale * zscale + zshift));
}
H5FedCloseMesh (m);
H5CloseFile (f);
*gmsg << "* Reading mesh done." << endl;
Local::computeGeometryInterval (this);
computeMeshVoxelization ();
haveInsidePoint_m = false;
std::vector pt = Attributes::getRealArray (itsAttr[INSIDEPOINT]);
if (pt.size() != 0) {
if (pt.size () != 3) {
throw OpalException (
"BoundaryGeometry::initialize()",
"Dimension of INSIDEPOINT must be 3");
}
/* test whether this point is inside */
insidePoint_m = {pt[0], pt[1], pt[2]};
bool is_inside = isInside (insidePoint_m);
if (is_inside == false) {
throw OpalException (
"BoundaryGeometry::initialize()",
"INSIDEPOINT is not inside the geometry");
}
haveInsidePoint_m = true;
} else {
haveInsidePoint_m = findInsidePoint();
}
if (haveInsidePoint_m == true) {
*gmsg << "* using as point inside the geometry: ("
<< insidePoint_m[0] << ", "
<< insidePoint_m[1] << ", "
<< insidePoint_m[2] << ")"
<< endl;
} else {
*gmsg << "* no point inside the geometry found!"
<< endl;
}
Local::makeTriangleNormalInwardPointing (this);
TriNormals_m.resize (Triangles_m.size());
TriAreas_m.resize (Triangles_m.size());
for (size_t i = 0; i < Triangles_m.size(); i++) {
const Vector_t& A = getPoint (i, 1);
const Vector_t& B = getPoint (i, 2);
const Vector_t& C = getPoint (i, 3);
TriAreas_m[i] = computeArea (A, B, C);
TriNormals_m[i] = normalVector (A, B, C);
}
*gmsg << "* Triangle barycent built done." << endl;
*gmsg << *this << endl;
Ippl::Comm->barrier();
IpplTimings::stopTimer (Tinitialize_m);
}
/*
Line segment triangle intersection test. This method should be used only
for "tiny" line segments or, to be more exact, if the number of
voxels covering the bounding box of the line segment is small (<<100).
Actually the method can be used for any line segment, but may not perform
well. Performace depends on the size of the bounding box of the line
segment.
The method returns the number of intersections of the line segment defined
by the points P and Q with the boundary. If there are multiple intersections,
the nearest intersection point with respect to P wil be returned.
*/
int
BoundaryGeometry::intersectTinyLineSegmentBoundary (
const Vector_t& P, // [i] starting point of ray
const Vector_t& Q, // [i] end point of ray
Vector_t& intersect_pt, // [o] intersection with boundary
int& triangle_id // [o] intersected triangle
) {
#ifdef ENABLE_DEBUG
if (debugFlags_m & debug_intersectTinyLineSegmentBoundary) {
*gmsg << "* " << __func__ << ": "
<< " P = " << P
<< ", Q = " << Q
<< endl;
}
#endif
const Vector_t v_ = Q - P;
const Ray r = Ray (P, v_);
const Vector_t bbox_min = {
std::min(P[0], Q[0]),
std::min(P[1], Q[1]),
std::min(P[2], Q[2]) };
const Vector_t bbox_max = {
std::max(P[0], Q[0]),
std::max(P[1], Q[1]),
std::max(P[2], Q[2]) };
int i_min, i_max;
int j_min, j_max;
int k_min, k_max;
mapPoint2VoxelIndices (bbox_min, i_min, j_min, k_min);
mapPoint2VoxelIndices (bbox_max, i_max, j_max, k_max);
Vector_t tmp_intersect_pt = Q;
double tmin = 1.1;
/*
Triangles can - and in many cases do - intersect with more than one
voxel. If we loop over all voxels intersecting with the line segment
spaned by the points P and Q, we might perform the same line-triangle
intersection test more than once. We must this into account when
counting the intersections with the boundary.
To avoid multiple counting we can either
- build a set of all relevant triangles and loop over this set
- or we loop over all voxels and remember the intersecting triangles.
The first solution is implemented here.
*/
std::unordered_set triangle_ids;
for (int i = i_min; i <= i_max; i++) {
for (int j = j_min; j <= j_max; j++) {
for (int k = k_min; k <= k_max; k++) {
const Vector_t bmin = mapIndices2Voxel(i, j, k);
const Voxel v(bmin, bmin + voxelMesh_m.sizeOfVoxel);
#ifdef ENABLE_DEBUG
if (debugFlags_m & debug_intersectTinyLineSegmentBoundary) {
*gmsg << "* " << __func__ << ": "
<< " Test voxel: (" << i << ", " << j << ", " << k << "), "
<< v.pts[0] << v.pts[1]
<< endl;
}
#endif
/*
do line segment and voxel intersect? continue if not
*/
if (!v.intersect (r)) {
continue;
}
/*
get triangles intersecting with this voxel and add them to
the to be tested triangles.
*/
const int voxel_id = mapVoxelIndices2ID (i, j, k);
const auto triangles_intersecting_voxel =
voxelMesh_m.ids.find (voxel_id);
if (triangles_intersecting_voxel != voxelMesh_m.ids.end()) {
triangle_ids.insert (
triangles_intersecting_voxel->second.begin(),
triangles_intersecting_voxel->second.end());
}
}
}
}
/*
test all triangles intersecting with one of the above voxels
if there is more than one intersection, return closest
*/
int num_intersections = 0;
int tmp_intersect_result = 0;
for (auto it = triangle_ids.begin ();
it != triangle_ids.end ();
it++) {
tmp_intersect_result = intersectLineTriangle (
LINE,
P, Q,
*it,
tmp_intersect_pt);
#ifdef ENABLE_DEBUG
if (debugFlags_m & debug_intersectTinyLineSegmentBoundary) {
*gmsg << "* " << __func__ << ": "
<< " Test triangle: " << *it
<< " intersect: " << tmp_intersect_result
<< getPoint(*it,1)
<< getPoint(*it,2)
<< getPoint(*it,3)
<< endl;
}
#endif
switch (tmp_intersect_result) {
case 0: // no intersection
case 2: // both points are outside
case 4: // both points are inside
break;
case 1: // line and triangle are in same plane
case 3: // unique intersection in segment
double t;
if (cmp::eq_zero(Q[0] - P[0]) == false) {
t = (tmp_intersect_pt[0] - P[0]) / (Q[0] - P[0]);
} else if (cmp::eq_zero(Q[1] - P[1]) == false) {
t = (tmp_intersect_pt[1] - P[1]) / (Q[1] - P[1]);
} else {
t = (tmp_intersect_pt[2] - P[2]) / (Q[2] - P[2]);
}
num_intersections++;
if (t < tmin) {
#ifdef ENABLE_DEBUG
if (debugFlags_m & debug_intersectTinyLineSegmentBoundary) {
*gmsg << "* " << __func__ << ": "
<< " set triangle"
<< endl;
}
#endif
tmin = t;
intersect_pt = tmp_intersect_pt;
triangle_id = (*it);
}
break;
case -1: // triangle is degenerated
PAssert (tmp_intersect_result != -1);
exit (42); // terminate even if NDEBUG is set
}
} // end for all triangles
return num_intersections;
}
/*
General purpose line segment boundary intersection test.
The method returns with a value > 0 if an intersection was found.
*/
int
BoundaryGeometry::intersectLineSegmentBoundary (
const Vector_t& P0, // [in] starting point of ray
const Vector_t& P1, // [in] end point of ray
Vector_t& intersect_pt, // [out] intersection with boundary
int& triangle_id // [out] triangle the line segment intersects with
) {
#ifdef ENABLE_DEBUG
int saved_flags = debugFlags_m;
if (debugFlags_m & debug_intersectLineSegmentBoundary) {
*gmsg << "* " << __func__ << ": "
<< " P0 = " << P0
<< " P1 = " << P1
<< endl;
debugFlags_m |= debug_intersectTinyLineSegmentBoundary;
}
#endif
triangle_id = -1;
const Vector_t v = P1 - P0;
int intersect_result = 0;
int n = 0;
int i_min, j_min, k_min;
int i_max, j_max, k_max;
do {
n++;
Vector_t Q = P0 + v / n;
Vector_t bbox_min = {
std::min(P0[0], Q[0]),
std::min(P0[1], Q[1]),
std::min(P0[2], Q[2]) };
Vector_t bbox_max = {
std::max(P0[0], Q[0]),
std::max(P0[1], Q[1]),
std::max(P0[2], Q[2]) };
mapPoint2VoxelIndices (bbox_min, i_min, j_min, k_min);
mapPoint2VoxelIndices (bbox_max, i_max, j_max, k_max);
} while (( (i_max-i_min+1) * (j_max-j_min+1) * (k_max-k_min+1)) > 27);
Vector_t P = P0;
Vector_t Q;
const Vector_t v_ = v / n;
for (int l = 1; l <= n; l++, P = Q) {
Q = P0 + l*v_;
intersect_result = intersectTinyLineSegmentBoundary (
P, Q, intersect_pt, triangle_id);
if (triangle_id != -1) {
break;
}
}
#ifdef ENABLE_DEBUG
if (debugFlags_m & debug_intersectLineSegmentBoundary) {
*gmsg << "* " << __func__ << ": "
<< " result=" << intersect_result
<< " intersection pt: " << intersect_pt
<< endl;
debugFlags_m = saved_flags;
}
#endif
return intersect_result;
}
/**
Determine whether a particle with position @param r, momenta @param v,
and time step @param dt will hit the boundary.
return value:
-1 no collison with boundary
0 particle will collide with boundary in next time step
*/
int
BoundaryGeometry::partInside (
const Vector_t& r, // [in] particle position
const Vector_t& v, // [in] momentum
const double dt, // [in]
Vector_t& intersect_pt, // [out] intersection with boundary
int& triangle_id // [out] intersected triangle
) {
#ifdef ENABLE_DEBUG
int saved_flags = debugFlags_m;
if (debugFlags_m & debug_PartInside) {
*gmsg << "* " << __func__ << ": "
<< " r=" << r
<< " v=" << v
<< " dt=" << dt
<< endl;
debugFlags_m |= debug_intersectTinyLineSegmentBoundary;
}
#endif
int ret = -1; // result defaults to no collision
// nothing to do if momenta == 0
if (v == (Vector_t)0)
return ret;
IpplTimings::startTimer (TPartInside_m);
// P0, P1: particle position in time steps n and n+1
const Vector_t P0 = r;
const Vector_t P1 = r + (Physics::c * v * dt / std::sqrt (1.0 + dot(v,v)));
Vector_t tmp_intersect_pt = 0.0;
int tmp_triangle_id = -1;
intersectTinyLineSegmentBoundary (P0, P1, tmp_intersect_pt, tmp_triangle_id);
if (tmp_triangle_id >= 0) {
intersect_pt = tmp_intersect_pt;
triangle_id = tmp_triangle_id;
ret = 0;
}
#ifdef ENABLE_DEBUG
if (debugFlags_m & debug_PartInside) {
*gmsg << "* " << __func__ << ":"
<< " result=" << ret;
if (ret == 0) {
*gmsg << " intersetion=" << intersect_pt;
}
*gmsg << endl;
debugFlags_m = saved_flags;
}
#endif
IpplTimings::stopTimer (TPartInside_m);
return ret;
}
void
BoundaryGeometry::writeGeomToVtk (std::string fn) {
std::ofstream of;
of.open (fn.c_str ());
PAssert (of.is_open ());
of.precision (6);
of << "# vtk DataFile Version 2.0" << std::endl;
of << "generated using DataSink::writeGeoToVtk" << std::endl;
of << "ASCII" << std::endl << std::endl;
of << "DATASET UNSTRUCTURED_GRID" << std::endl;
of << "POINTS " << Points_m.size () << " float" << std::endl;
for (unsigned int i = 0; i < Points_m.size (); i++)
of << Points_m[i](0) << " "
<< Points_m[i](1) << " "
<< Points_m[i](2) << std::endl;
of << std::endl;
of << "CELLS "
<< Triangles_m.size() << " "
<< 4 * Triangles_m.size() << std::endl;
for (size_t i = 0; i < Triangles_m.size(); i++)
of << "3 "
<< PointID (i, 1) << " "
<< PointID (i, 2) << " "
<< PointID (i, 3) << std::endl;
of << "CELL_TYPES " << Triangles_m.size() << std::endl;
for (size_t i = 0; i < Triangles_m.size(); i++)
of << "5" << std::endl;
of << "CELL_DATA " << Triangles_m.size() << std::endl;
of << "SCALARS " << "cell_attribute_data" << " float " << "1" << std::endl;
of << "LOOKUP_TABLE " << "default" << std::endl;
for (size_t i = 0; i < Triangles_m.size(); i++)
of << (float)(i) << std::endl;
of << std::endl;
}
Inform&
BoundaryGeometry::printInfo (Inform& os) const {
os << endl;
os << "* ************* B O U N D A R Y G E O M E T R Y *********************************** " << endl;
os << "* GEOMETRY " << getOpalName () << '\n'
<< "* FGEOM " << Attributes::getString (itsAttr[FGEOM]) << '\n'
<< "* TOPO " << Attributes::getString (itsAttr[TOPO]) << '\n'
<< "* LENGTH " << Attributes::getReal (itsAttr[LENGTH]) << '\n'
<< "* S " << Attributes::getReal (itsAttr[S]) << '\n'
<< "* A " << Attributes::getReal (itsAttr[A]) << '\n'
<< "* B " << Attributes::getReal (itsAttr[B]) << '\n';
if (getTopology () == std::string ("BOXCORNER")) {
os << "* C " << Attributes::getReal (itsAttr[C]) << '\n'
<< "* L1 " << Attributes::getReal (itsAttr[L1]) << '\n'
<< "* L1 " << Attributes::getReal (itsAttr[L2]) << '\n';
}
os << "* Total triangle num " << Triangles_m.size() << '\n'
<< "* Total points num " << Points_m.size () << '\n'
<< "* Geometry bounds(m) Max= " << maxExtent_m << '\n'
<< "* Min= " << minExtent_m << '\n'
<< "* Geometry length(m) " << maxExtent_m - minExtent_m << '\n'
<< "* Resolution of voxel mesh " << voxelMesh_m.nr_m << '\n'
<< "* Size of voxel " << voxelMesh_m.sizeOfVoxel << '\n'
<< "* Number of voxels in mesh " << voxelMesh_m.ids.size () << '\n'
<< endl;
os << "* ********************************************************************************** " << endl;
return os;
}