13#include "libmesh/plane.h"
14#include "libmesh/utility.h"
20 const std::vector<std::unique_ptr<SurfaceElement>> & bd_elements,
21 const std::vector<Point> & centroids,
23 const Real eps_on_surface,
24 const int leaf_max_size,
25 const FileName & obb_file_name,
26 const FileName & ray_file_name,
28 : _bd_elements(bd_elements),
29 _centroids(centroids),
30 _ray_direction(ray_options.direction),
31 _eps_on_surface(eps_on_surface),
32 _leaf_max_size(leaf_max_size),
33 _obb_file_name(obb_file_name),
34 _ray_file_name(ray_file_name),
36 _plane_origin(Point(0.0, 0.0, 0.0))
43 "AdaptiveRayContainmentCheck: boundary elements must not be empty or uninitialized.");
54 for (
const auto i : make_range(3u))
57 "AdaptiveRayContainmentCheck: a user-selected ray_direction must be finite; got ",
61 mooseError(
"AdaptiveRayContainmentCheck: a user-selected ray_direction must be non-zero.");
63 mooseError(
"AdaptiveRayContainmentCheck: a user-selected ray_direction for a 2D surface must "
64 "lie in the mesh plane (its z component must be zero); got ",
88 const std::array<Point, 2> ray_starts =
100 std::ostringstream oss;
102 mooseError(
"AdaptiveRayContainmentCheck: the user-selected ray_direction ",
104 " gives an ambiguous (grazing or tangent) intersection at point ",
106 "; choose a different ray_direction or use the auto (pca_ray) method.");
114 for (
const auto obb_axis : make_range(
static_cast<unsigned int>(
_dim - 1)))
117 const std::array<Point, 2> probe_starts = {
121 for (
const auto & probe_start : probe_starts)
126 std::ostringstream oss;
128 mooseError(
"AdaptiveRayContainmentCheck: No decision could be made for point " + oss.str());
131std::optional<SurfaceGeometry::SurfaceSide>
133 const std::array<Point, 2> & ray_starts)
const
135 std::array<int, 2> counts = {0, 0};
139 for (
const auto i : make_range(2))
150 if ((counts[0] % 2) == (counts[1] % 2))
165template <
typename CrossingTest>
168 const Point & ray_end,
169 const bool use_primary_direction,
170 CrossingTest is_crossing)
const
172 const auto candidate_ids =
174 const auto num_candidates = use_primary_direction ? candidate_ids.size() :
_num_elements;
175 const Point segment_direction = ray_end - ray_start;
178 for (
const auto candidate : make_range(num_candidates))
180 const auto elem_id = use_primary_direction ? candidate_ids[candidate] : candidate;
182 const auto ball = surface->computeBoundingBall();
189 if (is_crossing(surface))
197 const Point & ray_end,
198 const bool use_primary_direction)
const
206 const auto ray_hits_surface = [
this, &ray_start, &ray_end](
const SurfaceElement * surface)
214 const Point & ray_end,
215 const bool use_primary_direction)
const
219 const Point & p = ray_end;
220 const Point dir = ray_end - ray_start;
222 const auto edge_crosses_ray = [&p, &dir](
const SurfaceElement * surface)
224 const Elem & e = surface->elem();
225 const Point a = e.point(0) - p;
226 const Point b = e.point(1) - p;
233 const Real side_a = dir.cross(a)(2);
234 const Real side_b = dir.cross(b)(2);
235 if ((side_a > 0.0) == (side_b > 0.0))
241 const Real t = side_a / (side_a - side_b);
242 const Point crossing = a + t * (b - a);
243 return dir * crossing < 0.0;
251 const Point & ray_end,
262 : !
_bounds.contains_point(query_point);
268 const Ball & ball)
const
274 for (
const auto i : make_range(
_dim))
276 lb(i) = std::min(orig(i), orig(i) + dir(i)) -
radius;
277 ub(i) = std::max(orig(i), orig(i) + dir(i)) +
radius;
280 for (
const auto i : make_range(
_dim))
292 const Ball & ball)
const
297 const auto w =
center - orig;
299 Real b = (w * dir) / (dir * dir);
300 Point Pb = orig + b * dir;
302 Real distance_squared = 0.0;
303 for (
const auto i : make_range(
_dim))
304 distance_squared += Utility::pow<2>(Pb(i) -
center(i));
313 BoundingBox bbox = first_elem.loose_bounding_box();
316 bbox.union_with(bd_elem->elem().loose_bounding_box());
319 Point min_pt = bbox.min();
320 Point max_pt = bbox.max();
322 for (
const auto d : make_range(3u))
328 return BoundingBox(min_pt, max_pt);
333 const Point & ray_direction,
334 const unsigned int obb_axis,
335 const bool inverted)
const
338 "AdaptiveRayContainmentCheck::rayStartOutsideOBB: OBB-based ray start is only used "
339 "by the auto (PCA) policy.");
340 mooseAssert(obb_axis <
static_cast<unsigned int>(
_dim),
341 "AdaptiveRayContainmentCheck::rayStartOutsideOBB: invalid OBB axis index.");
344 const Real half_axis_length = axis_length / 2.0;
353 Point projection_plane_corner;
354 Real direction_multiplier;
358 projection_plane_corner =
360 direction_multiplier = inverted ? -1.0 : 1.0;
364 projection_plane_corner =
366 direction_multiplier = inverted ? 1.0 : -1.0;
369 const Point projected_point =
371 return projected_point - padding * direction_multiplier * ray_direction;
392 const Point & unit_direction,
393 const bool inverted)
const
398 const Point & lo =
_bounds.min();
399 const Point & hi =
_bounds.max();
401 Real min_projection = std::numeric_limits<Real>::max();
402 Real max_projection = std::numeric_limits<Real>::lowest();
403 for (
const auto c : make_range(8u))
406 (c & 1u) ? hi(0) : lo(0), (c & 2u) ? hi(1) : lo(1), (c & 4u) ? hi(2) : lo(2));
407 const Real projection = corner * unit_direction;
408 min_projection = std::min(min_projection, projection);
409 max_projection = std::max(max_projection, projection);
413 const Real padding =
_eps_on_surface + 1e-2 * (max_projection - min_projection);
414 const Real target = inverted ? max_projection + padding : min_projection - padding;
415 return point + (target - point * unit_direction) * unit_direction;
420 const Point & plane_point,
421 const Point & plane_normal)
const
434 std::vector<Point> nodal_points;
437 const auto & e = elem->elem();
438 for (
const auto i : make_range(e.n_nodes()))
440 const Node * node = e.node_ptr(i);
441 mooseAssert(node,
"Node pointer is null!");
442 nodal_points.push_back(*node);
443 centroid_sum += *node;
447 const unsigned int N = nodal_points.size();
448 mooseAssert(N >= 3,
"At least 3 points required");
454 DenseMatrix<Real> X(N, 3);
455 for (
const auto i : make_range(N))
464 DenseVector<Real> sigma;
465 DenseMatrix<Real> U, VT;
485 auto canonicalize_sign = [](Point & v)
487 unsigned int i_max = 0;
488 for (
const auto i : make_range(1, 3))
489 if (std::abs(v(i)) > std::abs(v(i_max)))
502 "Principal directions are not orthogonal.");
509 mooseAssert(
_centroids.size() >= 3,
"Need at least three points.");
517 Real u_min = std::numeric_limits<Real>::max();
518 Real u_max = std::numeric_limits<Real>::lowest();
519 Real v_min = u_min, v_max = u_max;
520 Real w_min = u_min, w_max = u_max;
548 for (
const auto j : make_range(e.n_nodes()))
555 u_min = std::min(u_min, u);
556 u_max = std::max(u_max, u);
557 v_min = std::min(v_min, v);
558 v_max = std::max(v_max, v);
561 w_min = std::min(w_min, w);
562 w_max = std::max(w_max, w);
571 const Point min_corner =
577 std::vector<std::pair<Point, Point>> axis_pairs{
580 2 * expand_box_length *
588 axis_pairs.emplace_back(min_corner,
615std::vector<unsigned int>
618 std::vector<unsigned int> elem_ids;
625 std::vector<nanoflann::ResultItem<std::size_t, Real>> matches;
628 elem_ids.reserve(matches.size());
629 for (
const auto & m : matches)
630 elem_ids.push_back(
static_cast<unsigned int>(m.first));
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
std::vector< Point > _projected_centroids
Projected centroids of the elements in the boundary mesh.
int countFilteredCrossings(const Point &ray_start, const Point &ray_end, const bool use_primary_direction, CrossingTest is_crossing) const
Shared traversal for the 2D and 3D crossing counts: walk the candidate elements (KD-tree candidates f...
std::unique_ptr< KDTree > _kd_tree
The KDTree is constructed using the projected centroids of the elements in the boundary mesh.
Point _max_variance_vector
max variance vector
int countCrossings2D(const Point &ray_start, const Point &ray_end, const bool use_primary_direction) const
2D crossing count using a half-open side-based crossing rule: an edge is counted when its two endpoin...
Point rayStartOutsideAABB(const Point &point, const Point &unit_direction, const bool inverted) const
Ray start strictly outside the global AABB along unit_direction, for any direction.
Point projectPointOntoPlane(const Point &point_to_project, const Point &plane_point, const Point &plane_normal) const
Orthogonally project point_to_project onto the plane defined by plane_point and unit normal plane_nor...
AdaptiveRayContainmentCheck(const std::vector< std::unique_ptr< SurfaceElement > > &bd_elements, const std::vector< Point > ¢roids, const SurfaceGeometry::RayDirectionOptions &ray_options, const Real eps_on_surface=libMesh::TOLERANCE, const int leaf_max_size=10, const FileName &obb_file_name="", const FileName &ray_file_name="", const libMesh::Parallel::Communicator *comm=nullptr)
Point _ray_direction
Ray shooting direction.
bool isOutsideRayBBox(const Point &orig, const Point &dir, const Ball &ball) const
Check if element center is outside ray bounding box.
bool rayIntersectGeometry(const Point &ray_start, const Point &ray_end, const SurfaceElement *elem) const
Ray-element intersection (e.g., ray-line for 2D, ray-triangle for 3D)
FileName _obb_file_name
The file name for the OBB.
Point _second_variance_vector
second max variance vector
Point _plane_origin
The origin of the plane used to ensure that every projected point is correctly aligned and lies on th...
Point _centroid_nodal_points
The centroid of the boundary elements' node points (prepare inside this class).
bool isOutsideBoundingRegion(const Point &orig, const Point &dir, const Ball &ball) const
Check if element center is outside ray bounding circle/sphere.
int countCrossings(const Point &ray_start, const Point &ray_end, const bool use_primary_direction=true) const
Count how many times the segment from ray_start to ray_end crosses the surface.
SurfaceGeometry::SurfaceSide sideness(const Point &p) const
Main function: Determine if a point is inside the geometry.
Real _max_projected_diag_length
The maximum diagonal length of the projected bounding box from the boundary elements.
std::vector< unsigned int > collectCandidateElementIDs(const Point &query_point) const
Use the kd-tree to collect candidate element IDs to check intersections.
bool isOnSurface(const Point &p) const
True if p lies on the surface (within _eps_on_surface), i.e.
Point _min_variance_vector
min variance vector (only used for 3D)
BoundingBox computeGlobalBoundingBox()
Compute the global bounding box of all boundary elements.
Real _eps_on_surface
Epsilon value for checking if a point is on the surface of the geometry.
int _dim
The dimension of the embedding mesh.
const std::vector< std::unique_ptr< SurfaceElement > > & _bd_elements
pass into the constructor for the surface elements
OrientedBoundingBox _obb_bounds
The oriented bounding box (OBB).
FileName _ray_file_name
The file name for the ray.
Point rayStartOutsideOBB(const Point &point, const Point &ray_direction, const unsigned int obb_axis, const bool inverted=false) const
Computes the starting point of an OBB-based ray (auto/PCA policy) for a given query point.
int _leaf_max_size
Configures KDTree leaf node size for performance tuning.
const std::vector< Point > & _centroids
pass into the constructor for the surface element centroids
bool _build_obb
When the ray direction is auto-selected (PCA) we build an Oriented Bounding Box (OBB); a user-selecte...
std::size_t _num_elements
The number of elements in the boundary mesh.
bool isOutsideBoundingBox(const Point &query_point) const
Check if point is outside global bounding box.
std::optional< SurfaceGeometry::SurfaceSide > sidenessFromRayPair(const Point &p, const std::array< Point, 2 > &ray_starts) const
Determine sideness from a pair of opposite rays.
void buildObbKdtreeAndMaxProjectedDiagonal(const Real expand_box_length)
Constructs an oriented bounding box (OBB) using the results of PCA and the KD-tree.
void initializeRayDirection()
Finalizes the ray direction and the matching bounding box.
const libMesh::Parallel::Communicator * _comm
Communicator used only for writing the debug OBB/ray mesh files.
BoundingBox _bounds
The bounding box AABB.
bool _auto_ray_direction
Whether the ray direction is auto-selected via PCA (true) or user-selected (false).
Ball primitive: a circle in 2D or a sphere in 3D.
const libMesh::Point & center() const
libMesh::Real radius() const
The LineSegment class is used by the LineMaterialSamplerBase class and for some ray tracing stuff.
Oriented bounding box in 2 D or 3 D.
Real getAxisLength(unsigned int i) const
Point getMinimalCorner() const
bool contains(const Point &pt, const Real tolerance=libMesh::TOLERANCE) const
Test whether a point lies inside or on the box.
void writeRayAlongShortestAxis(const std::filesystem::path &ray_path, const libMesh::Parallel::Communicator &comm) const
Write a single-EDGE2 mesh representing a "ray" emanating from the box.
Point getAxisDirection(unsigned int i) const
Point getMaximalCorner() const
void writeMesh(const std::filesystem::path &path, const libMesh::Parallel::Communicator &comm) const
Write the oriented box as a single libMesh element to a mesh file.
Real getProjectedLength(const Point &pt, unsigned int i) const
Get the length of the projection of a point onto axis i.
Base class for a single surface (boundary) element of a closed surface mesh.
virtual bool intersect(const LineSegment &line_segment) const =0
Check if the given line segment intersects this surface element.
virtual bool contains_point(const Point &p, Real tol=TOLERANCE) const
virtual Point closest_point(const Point &p) const override
@ AUTO_PCA
The engine auto-selects a robust direction via PCA (may use a fallback).
SurfaceSide
The side of a closed surface where a query point is located.
@ INSIDE
The point lies strictly in the interior of the closed surface.
@ ON
The point lies on the surface itself, within tolerance.
@ OUTSIDE
The point lies strictly in the exterior of the closed surface.
Ray-direction intent for AdaptiveRayContainmentCheck: an explicit mode plus the direction to use when...