https://mooseframework.inl.gov
Loading...
Searching...
No Matches
AdaptiveRayContainmentCheck.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
11#include "LineSegment.h"
12#include "Ball.h"
13#include "libmesh/plane.h"
14#include "libmesh/utility.h"
15
16#include <cmath>
17#include <limits>
18
20 const std::vector<std::unique_ptr<SurfaceElement>> & bd_elements,
21 const std::vector<Point> & centroids,
22 const SurfaceGeometry::RayDirectionOptions & ray_options,
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),
35 _comm(comm),
36 _plane_origin(Point(0.0, 0.0, 0.0))
37{
38 // Runtime (not mooseAssert) check: this precondition depends on caller-supplied input and
39 // guards the _bd_elements[0] dereference below. mooseAssert is compiled out in opt builds,
40 // which would leave that dereference as out-of-bounds undefined behavior on an empty set.
41 if (_bd_elements.empty())
43 "AdaptiveRayContainmentCheck: boundary elements must not be empty or uninitialized.");
45 _dim = _bd_elements[0]->expectedEmbeddingMeshDim();
46
48
50 {
51 // USER_SPECIFIED policy: use the direction exactly. Validate only that it is usable, then
52 // normalize. Degeneracy (a ray grazing a vertex/edge or tangent to the surface) is the
53 // user's responsibility; the engine never auto-corrects or switches the direction.
54 for (const auto i : make_range(3u))
55 if (!std::isfinite(_ray_direction(i)))
57 "AdaptiveRayContainmentCheck: a user-selected ray_direction must be finite; got ",
59 ".");
60 if (MooseUtils::absoluteFuzzyEqual(_ray_direction.norm(), 0.0))
61 mooseError("AdaptiveRayContainmentCheck: a user-selected ray_direction must be non-zero.");
62 if (_dim == 2 && !MooseUtils::absoluteFuzzyEqual(_ray_direction(2), 0.0))
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 ",
66 ".");
68 }
69
70 // PCA is only used to auto-select the direction; a user-selected ray never runs it.
74 1e-2 /*safe protect: expanded box length in each direction and both sides*/);
75}
76
79{
82
83 // Whether p sits on the surface is a property of p alone, so decide it once here rather than
84 // re-testing it on every ray cast below.
85 if (isOnSurface(p))
87
88 const std::array<Point, 2> ray_starts =
90 ? std::array<Point, 2>{rayStartOutsideOBB(p, _ray_direction, _dim - 1, false),
92 : std::array<Point, 2>{rayStartOutsideAABB(p, _ray_direction, false),
94
95 if (const auto side = sidenessFromRayPair(p, ray_starts))
96 return *side;
97
99 {
100 std::ostringstream oss;
101 oss << p;
102 mooseError("AdaptiveRayContainmentCheck: the user-selected ray_direction ",
104 " gives an ambiguous (grazing or tangent) intersection at point ",
105 oss.str(),
106 "; choose a different ray_direction or use the auto (pca_ray) method.");
107 }
108
109 // Defensive fallback. With the half-open crossing count, the two opposite primary rays always
110 // agree in parity for a well-formed closed surface, so this path is not reached for valid input;
111 // it is kept as a safety net should a future or degenerate case ever produce a parity
112 // disagreement. Probe the remaining PCA variance directions: if any probe ray escapes without
113 // crossing the surface, the point is outside. Otherwise the query is undecidable.
114 for (const auto obb_axis : make_range(static_cast<unsigned int>(_dim - 1)))
115 {
116 const Point fallback_direction = _obb_bounds.getAxisDirection(obb_axis);
117 const std::array<Point, 2> probe_starts = {
118 rayStartOutsideOBB(p, fallback_direction, obb_axis, false),
119 rayStartOutsideOBB(p, fallback_direction, obb_axis, true)};
120
121 for (const auto & probe_start : probe_starts)
122 if (countCrossings(probe_start, p, false) == 0)
124 }
125
126 std::ostringstream oss;
127 oss << p;
128 mooseError("AdaptiveRayContainmentCheck: No decision could be made for point " + oss.str());
129}
130
131std::optional<SurfaceGeometry::SurfaceSide>
133 const std::array<Point, 2> & ray_starts) const
134{
135 std::array<int, 2> counts = {0, 0};
136
137 // Shoot the two (opposite) rays and count intersections with the boundary elements. p has
138 // already been ruled on-surface by sideness(), so the count is always meaningful here.
139 for (const auto i : make_range(2))
140 {
141 counts[i] = countCrossings(ray_starts[i], p);
142
143 // A ray that never crosses the closed surface proves the point is outside.
144 if (counts[i] == 0)
146 }
147
148 // Consistent parity gives a definite decision; conflicting parity is undecided (nullopt) and
149 // left to the caller's policy.
150 if ((counts[0] % 2) == (counts[1] % 2))
151 return (counts[0] % 2 == 1) ? SurfaceGeometry::SurfaceSide::INSIDE
153 return std::nullopt;
154}
155
156bool
158{
159 for (const auto elem_id : collectCandidateElementIDs(p))
160 if (_bd_elements[elem_id].get()->elem().contains_point(p, _eps_on_surface))
161 return true;
162 return false;
163}
164
165template <typename CrossingTest>
166int
168 const Point & ray_end,
169 const bool use_primary_direction,
170 CrossingTest is_crossing) const
171{
172 const auto candidate_ids =
173 use_primary_direction ? collectCandidateElementIDs(ray_end) : std::vector<unsigned int>{};
174 const auto num_candidates = use_primary_direction ? candidate_ids.size() : _num_elements;
175 const Point segment_direction = ray_end - ray_start;
176
177 int count = 0;
178 for (const auto candidate : make_range(num_candidates))
179 {
180 const auto elem_id = use_primary_direction ? candidate_ids[candidate] : candidate;
181 const auto & surface = _bd_elements[elem_id].get();
182 const auto ball = surface->computeBoundingBall();
183
184 if (isOutsideRayBBox(ray_start, segment_direction, ball))
185 continue;
186 if (isOutsideBoundingRegion(ray_start, segment_direction, ball))
187 continue;
188
189 if (is_crossing(surface))
190 ++count;
191 }
192 return count;
193}
194
195int
197 const Point & ray_end,
198 const bool use_primary_direction) const
199{
200 // The 2D count uses a half-open side-based crossing rule, which counts a ray passing exactly
201 // through a boundary vertex or along a collinear edge correctly without any tolerance. The 3D
202 // path is unchanged.
203 if (_dim == 2)
204 return countCrossings2D(ray_start, ray_end, use_primary_direction);
205
206 const auto ray_hits_surface = [this, &ray_start, &ray_end](const SurfaceElement * surface)
207 { return rayIntersectGeometry(ray_start, ray_end, surface); };
208
209 return countFilteredCrossings(ray_start, ray_end, use_primary_direction, ray_hits_surface);
210}
211
212int
214 const Point & ray_end,
215 const bool use_primary_direction) const
216{
217 // The ray goes from ray_start (placed outside the geometry) to the query point p = ray_end, so
218 // the crossings on the segment are exactly those of the half-line from p toward ray_start.
219 const Point & p = ray_end;
220 const Point dir = ray_end - ray_start; // start -> p
221
222 const auto edge_crosses_ray = [&p, &dir](const SurfaceElement * surface)
223 {
224 const Elem & e = surface->elem();
225 const Point a = e.point(0) - p;
226 const Point b = e.point(1) - p;
227
228 // Signed perpendicular position of each edge endpoint relative to the ray line. The strict ">"
229 // on both endpoints is the half-open ("one end closed, one end open") crossing convention: an
230 // endpoint exactly on the ray line is assigned one fixed side, so a vertex shared by two edges
231 // is counted by exactly one of them, an edge collinear with the ray (both sides zero) is
232 // skipped, and a tangential touch cancels to an even count. No tolerance is needed.
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))
236 return false;
237
238 // The edge crosses the ray line; keep it only if the crossing lies on the ray_start side of p
239 // (on the segment [ray_start, p]), i.e. opposite the +dir direction that points from start to
240 // p.
241 const Real t = side_a / (side_a - side_b);
242 const Point crossing = a + t * (b - a); // crossing point relative to p
243 return dir * crossing < 0.0;
244 };
245
246 return countFilteredCrossings(ray_start, ray_end, use_primary_direction, edge_crosses_ray);
247}
248
249bool
251 const Point & ray_end,
252 const SurfaceElement * elem) const
253{
254 LineSegment ray_segment(ray_start, ray_end);
255 return elem->intersect(ray_segment);
256}
257
258bool
260{
261 return (_build_obb) ? !_obb_bounds.contains(query_point, _eps_on_surface)
262 : !_bounds.contains_point(query_point);
263}
264
265bool
267 const Point & dir,
268 const Ball & ball) const
269{
270 Point lb, ub;
271 const auto & center = ball.center();
272 const Real radius = ball.radius();
273
274 for (const auto i : make_range(_dim))
275 {
276 lb(i) = std::min(orig(i), orig(i) + dir(i)) - radius;
277 ub(i) = std::max(orig(i), orig(i) + dir(i)) + radius;
278 }
279
280 for (const auto i : make_range(_dim))
281 {
282 if (center(i) < lb(i) || center(i) > ub(i))
283 return true;
284 }
285
286 return false;
287}
288
289bool
291 const Point & dir,
292 const Ball & ball) const
293{
294 const auto & center = ball.center();
295 const auto radius = ball.radius();
296
297 const auto w = center - orig;
298
299 Real b = (w * dir) / (dir * dir);
300 Point Pb = orig + b * dir;
301
302 Real distance_squared = 0.0;
303 for (const auto i : make_range(_dim))
304 distance_squared += Utility::pow<2>(Pb(i) - center(i));
305
306 return (distance_squared > radius * radius);
307}
308
309BoundingBox
311{
312 const auto & first_elem = _bd_elements[0]->elem();
313 BoundingBox bbox = first_elem.loose_bounding_box();
314
315 for (const auto & bd_elem : _bd_elements)
316 bbox.union_with(bd_elem->elem().loose_bounding_box());
317
318 const Real eps = _eps_on_surface;
319 Point min_pt = bbox.min();
320 Point max_pt = bbox.max();
321
322 for (const auto d : make_range(3u))
323 {
324 min_pt(d) -= eps;
325 max_pt(d) += eps;
326 }
327
328 return BoundingBox(min_pt, max_pt);
329}
330
331Point
333 const Point & ray_direction,
334 const unsigned int obb_axis,
335 const bool inverted) const
336{
337 mooseAssert(_build_obb,
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.");
342
343 const Real axis_length = _obb_bounds.getAxisLength(obb_axis);
344 const Real half_axis_length = axis_length / 2.0;
345
346 // The projection below lands the point exactly on the OBB face perpendicular to the ray
347 // (ray_direction == the OBB axis direction), so any positive outward step is provably
348 // outside the box. Use a scale-aware padding, consistent with rayStartOutsideAABB: an
349 // absolute floor (also guards a near-zero axis) plus a small fraction of the box extent
350 // to stay clear of floating-point noise at large coordinate scales.
351 const Real padding = _eps_on_surface + 1e-2 * axis_length;
352
353 Point projection_plane_corner;
354 Real direction_multiplier;
355
356 if (_obb_bounds.getProjectedLength(point, obb_axis) < half_axis_length)
357 {
358 projection_plane_corner =
360 direction_multiplier = inverted ? -1.0 : 1.0;
361 }
362 else
363 {
364 projection_plane_corner =
366 direction_multiplier = inverted ? 1.0 : -1.0;
367 }
368
369 const Point projected_point =
370 projectPointOntoPlane(point, projection_plane_corner, ray_direction);
371 return projected_point - padding * direction_multiplier * ray_direction;
372}
373
375void
377{
379 {
380 // Auto ray: adopt the PCA-selected direction and use the oriented bounding box.
382 _build_obb = true;
383 }
384 else
385 // User-selected axis-aligned ray: keep the user's (normalized) direction and use a
386 // global axis-aligned bounding box.
388}
389
390Point
392 const Point & unit_direction,
393 const bool inverted) const
394{
395 // Project the 8 AABB corners onto the direction to find the box's extent along it. The ray
396 // start is then placed just past the far side, so it is provably outside the box while moving
397 // only the distance needed (a full-diagonal displacement would make unnecessarily long rays).
398 const Point & lo = _bounds.min();
399 const Point & hi = _bounds.max();
400
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))
404 {
405 const Point corner(
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);
410 }
411
412 // Scale-aware padding so the start never lands exactly on the box boundary.
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;
416}
417
418Point
420 const Point & plane_point,
421 const Point & plane_normal) const
422{
423 // Delegate to libMesh::Plane::closest_point, which returns the orthogonal
424 // projection of the point onto the plane. `plane_normal` is assumed to be a
425 // unit vector (closest_point does not normalize it).
426 return libMesh::Plane(plane_point, plane_normal).closest_point(point_to_project);
427}
428
429void
431{
432 Point centroid_sum;
433
434 std::vector<Point> nodal_points;
435 for (const auto & elem : _bd_elements)
436 {
437 const auto & e = elem->elem();
438 for (const auto i : make_range(e.n_nodes()))
439 {
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;
444 }
445 }
446
447 const unsigned int N = nodal_points.size();
448 mooseAssert(N >= 3, "At least 3 points required");
449
450 // (a) Compute the centroid
451 _centroid_nodal_points = centroid_sum / static_cast<Real>(N);
452
453 // (b) Build the mean-centered matrix X (N x 3)
454 DenseMatrix<Real> X(N, 3);
455 for (const auto i : make_range(N))
456 {
457 const Point d = nodal_points[i] - _centroid_nodal_points;
458 X(i, 0) = d(0);
459 X(i, 1) = d(1);
460 X(i, 2) = d(2);
461 }
462
463 // (c) Perform SVD: X = U * sigma * V^T
464 DenseVector<Real> sigma;
465 DenseMatrix<Real> U, VT;
466 X.svd(sigma, U, VT); // VT is 3x3, each row is a principal direction
467
468 // (d) Extract principal directions
469 _max_variance_vector = Point(VT(0, 0), VT(0, 1), VT(0, 2)); // max variance
470 _second_variance_vector = Point(VT(1, 0), VT(1, 1), VT(1, 2)); // second largest variance
471 _min_variance_vector = Point(VT(2, 0), VT(2, 1), VT(2, 2)); // min variance
472
473 // (e) normalize them to be safe (unit() returns a normalized copy)
477
478 // (f) Canonicalize the sign of each principal direction. SVD singular vectors
479 // are only defined up to sign, and LAPACK can return opposite signs on
480 // different platforms or versions. Because the auto-selected ray direction is
481 // one of these vectors, an unstable sign makes the in-out classification of
482 // borderline elements non-reproducible across platforms. Fix a deterministic
483 // convention: make the largest-magnitude component positive (ties broken by
484 // the lowest index).
485 auto canonicalize_sign = [](Point & v)
486 {
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)))
490 i_max = i;
491 if (v(i_max) < 0.0)
492 v *= -1.0;
493 };
494 canonicalize_sign(_max_variance_vector);
495 canonicalize_sign(_second_variance_vector);
496 canonicalize_sign(_min_variance_vector);
497
498 mooseAssert(
499 MooseUtils::absoluteFuzzyEqual(_max_variance_vector * _second_variance_vector, 0.0) &&
500 MooseUtils::absoluteFuzzyEqual(_max_variance_vector * _min_variance_vector, 0.0) &&
501 MooseUtils::absoluteFuzzyEqual(_second_variance_vector * _min_variance_vector, 0.0),
502 "Principal directions are not orthogonal.");
503}
504
505void
507{
508 if (!_centroids.empty())
509 mooseAssert(_centroids.size() >= 3, "Need at least three points.");
510
511 // (a) Prepare KD-tree data (optional) and track PCA-space extents
512
513 // Initialize the ray direction if not set
515
516 // Global min / max along the three PCA axes
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;
521
523
525
526 for (const auto i : make_range(_num_elements))
527 {
528 // Per-element KD-tree data
529 {
530 const Point & pt =
531 (!_centroids.empty() ? _centroids[i] : _bd_elements[i]->elem().vertex_average());
532
534
535 if (_dim == 2) // flatten Z in 2-D mode
536 _projected_centroids[i](2) = 0.0;
537
540 _bd_elements[i]->getProjectedBoundingBoxDiagonal(_ray_direction));
541 }
542
543 if (_build_obb)
544 {
545 // Update PCA-space bounding box
546 const Elem & e = _bd_elements[i]->elem();
547
548 for (const auto j : make_range(e.n_nodes()))
549 {
550 const Point d = *(e.node_ptr(j)) - _centroid_nodal_points;
551 const Real u = d * _max_variance_vector;
552 const Real v = d * _second_variance_vector;
553 const Real w = (_dim == 3) ? d * _min_variance_vector : 0.0;
554
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);
559 if (_dim == 3)
560 {
561 w_min = std::min(w_min, w);
562 w_max = std::max(w_max, w);
563 }
564 }
565 }
566 }
567
568 if (_build_obb)
569 {
570 // (b) Build the oriented bounding box (OBB)
571 const Point min_corner =
573 ((_dim == 3) ? w_min * _min_variance_vector : Point()) -
574 expand_box_length * _max_variance_vector - expand_box_length * _second_variance_vector -
575 ((_dim == 3) ? expand_box_length * _min_variance_vector : Point());
576
577 std::vector<std::pair<Point, Point>> axis_pairs{
578 {min_corner,
579 min_corner + (u_max - u_min) * _max_variance_vector +
580 2 * expand_box_length *
581 _max_variance_vector /*2 because we subtract 1 in min_corner*/},
582
583 {min_corner,
584 min_corner + (v_max - v_min) * _second_variance_vector +
585 2 * expand_box_length * _second_variance_vector}};
586
587 if (_dim == 3)
588 axis_pairs.emplace_back(min_corner,
589 min_corner + (w_max - w_min) * _min_variance_vector +
590 2 * expand_box_length * _min_variance_vector);
591
592 _obb_bounds = OrientedBoundingBox(axis_pairs);
593
594 if (_obb_file_name != "")
595 {
596 if (!_comm)
597 mooseError("A communicator is required to write the OBB mesh file '", _obb_file_name, "'.");
598 std::filesystem::path obb_path(_obb_file_name.c_str());
599 _obb_bounds.writeMesh(obb_path, *_comm);
600 }
601
602 if (_ray_file_name != "")
603 {
604 if (!_comm)
605 mooseError("A communicator is required to write the ray mesh file '", _ray_file_name, "'.");
606 std::filesystem::path ray_path(_ray_file_name.c_str());
608 }
609 }
610
611 // (c) Finalise KD-tree
612 _kd_tree = std::make_unique<KDTree>(_projected_centroids, _leaf_max_size);
613}
614
615std::vector<unsigned int>
617{
618 std::vector<unsigned int> elem_ids;
619
620 // KD-tree radius search in projected PCA space
621 Point proj = projectPointOntoPlane(query_point, _plane_origin, _ray_direction);
622 if (_dim == 2)
623 proj(2) = 0.0; // flatten Z for 2-D
624
625 std::vector<nanoflann::ResultItem<std::size_t, Real>> matches;
626 _kd_tree->radiusSearch(proj, _max_projected_diag_length, matches);
627
628 elem_ids.reserve(matches.size());
629 for (const auto & m : matches)
630 elem_ids.push_back(static_cast<unsigned int>(m.first));
631
632 return elem_ids;
633}
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
unsigned int count
Definition MortarUtils.C:53
Point center
Definition MortarUtils.C:58
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 > &centroids, 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.
Definition Ball.h:35
const libMesh::Point & center() const
Definition Ball.h:41
libMesh::Real radius() const
Definition Ball.h:42
The LineSegment class is used by the LineMaterialSamplerBase class and for some ray tracing stuff.
Definition LineSegment.h:31
Oriented bounding box in 2 D or 3 D.
Real getAxisLength(unsigned int i) 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
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.
Definition SurfaceSide.h:21
@ 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...
const Real radius