LCOV - code coverage report
Current view: top level - src/utils - GeometryUtils.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 145 235 61.7 %
Date: 2026-05-29 20:35:17 Functions: 15 19 78.9 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : 
      10             : #include "GeometryUtils.h"
      11             : #include "MooseUtils.h"
      12             : 
      13             : #include <algorithm>
      14             : #include <cmath>
      15             : #include <limits>
      16             : 
      17             : namespace geom_utils
      18             : {
      19             : 
      20             : bool
      21           4 : isPointZero(const Point & pt)
      22             : {
      23           4 :   const Point zero(0.0, 0.0, 0.0);
      24           8 :   return pt.absolute_fuzzy_equals(zero);
      25             : }
      26             : 
      27             : Point
      28           4 : unitVector(const Point & pt, const std::string & name)
      29             : {
      30           4 :   if (isPointZero(pt))
      31           6 :     mooseError("'" + name + "' cannot have zero norm!");
      32             : 
      33           2 :   return pt.unit();
      34             : }
      35             : 
      36             : Real
      37           0 : minDistanceToPoints(const Point & pt,
      38             :                     const std::vector<Point> & candidates,
      39             :                     const unsigned int axis)
      40             : {
      41           0 :   const auto idx = projectedIndices(axis);
      42             : 
      43           0 :   Real distance = std::numeric_limits<Real>::max();
      44           0 :   for (const auto & c : candidates)
      45             :   {
      46           0 :     const Real dx = c(idx.first) - pt(idx.first);
      47           0 :     const Real dy = c(idx.second) - pt(idx.second);
      48           0 :     const Real d = std::sqrt(dx * dx + dy * dy);
      49           0 :     distance = std::min(d, distance);
      50             :   }
      51             : 
      52           0 :   return distance;
      53             : }
      54             : 
      55             : Point
      56          24 : projectPoint(const Real x0, const Real x1, const unsigned int axis)
      57             : {
      58          24 :   const auto i = projectedIndices(axis);
      59          24 :   Point point;
      60          24 :   point(i.first) = x0;
      61          24 :   point(i.second) = x1;
      62          24 :   point(axis) = 0.0;
      63             : 
      64          48 :   return point;
      65             : }
      66             : 
      67             : Real
      68         102 : projectedLineHalfSpace(Point pt1, Point pt2, Point pt3, const unsigned int axis)
      69             : {
      70             :   // project points onto plane perpendicular to axis
      71         102 :   pt1(axis) = 0.0;
      72         102 :   pt2(axis) = 0.0;
      73         102 :   pt3(axis) = 0.0;
      74             : 
      75         102 :   const auto i = projectedIndices(axis);
      76             : 
      77         102 :   return (pt1(i.first) - pt3(i.first)) * (pt2(i.second) - pt3(i.second)) -
      78         204 :          (pt2(i.first) - pt3(i.first)) * (pt1(i.second) - pt3(i.second));
      79             : }
      80             : 
      81             : bool
      82          26 : pointInPolygon(const Point & point, const std::vector<Point> & corners, const unsigned int axis)
      83             : {
      84          26 :   const auto n_pts = corners.size();
      85             : 
      86          26 :   std::vector<bool> negative_half_space;
      87          26 :   std::vector<bool> positive_half_space;
      88         124 :   for (const auto i : index_range(corners))
      89             :   {
      90          98 :     const int next = (i == n_pts - 1) ? 0 : i + 1;
      91          98 :     const auto half = projectedLineHalfSpace(point, corners[i], corners[next], axis);
      92          98 :     negative_half_space.push_back(half < 0);
      93          98 :     positive_half_space.push_back(half > 0);
      94             :   }
      95             : 
      96          26 :   const bool negative = std::find(negative_half_space.begin(), negative_half_space.end(), true) !=
      97          26 :                         negative_half_space.end();
      98          26 :   const bool positive = std::find(positive_half_space.begin(), positive_half_space.end(), true) !=
      99          26 :                         positive_half_space.end();
     100             : 
     101          26 :   const bool in_polygon = !(negative && positive);
     102          26 :   if (in_polygon)
     103          16 :     return true;
     104             : 
     105          10 :   if (pointOnEdge(point, corners, axis))
     106           0 :     return true;
     107             : 
     108          10 :   return false;
     109          26 : }
     110             : 
     111             : bool
     112          10 : pointOnEdge(const Point & point, const std::vector<Point> & corners, const unsigned int axis)
     113             : {
     114          10 :   const auto n_pts = corners.size();
     115          10 :   const auto idx = projectedIndices(axis);
     116             : 
     117          10 :   constexpr Real tol = 1e-8;
     118          48 :   for (const auto i : index_range(corners))
     119             :   {
     120          38 :     const int next = (i == n_pts - 1) ? 0 : i + 1;
     121          38 :     const auto & pt1 = corners[i];
     122          38 :     const auto & pt2 = corners[next];
     123          38 :     const bool close_to_line = projectedDistanceFromLine(point, pt1, pt2, axis) < tol;
     124             : 
     125             :     // we can stop early if we know we're not close to the line
     126          38 :     if (!close_to_line)
     127          34 :       continue;
     128             : 
     129             :     // check that the point is "between" the two points; TODO: first pass
     130             :     // we can just compare x and y coordinates
     131           4 :     const bool between_points = (point(idx.first) >= std::min(pt1(idx.first), pt2(idx.first))) &&
     132           2 :                                 (point(idx.first) <= std::max(pt1(idx.first), pt2(idx.first))) &&
     133           6 :                                 (point(idx.second) >= std::min(pt1(idx.second), pt2(idx.second))) &&
     134           0 :                                 (point(idx.second) <= std::max(pt1(idx.second), pt2(idx.second)));
     135             : 
     136             :     // point needs to be close to the line AND "between" the two points
     137           4 :     if (close_to_line && between_points)
     138           0 :       return true;
     139             :   }
     140             : 
     141          10 :   return false;
     142             : }
     143             : 
     144             : std::pair<unsigned int, unsigned int>
     145         138 : projectedIndices(const unsigned int axis)
     146             : {
     147         138 :   std::pair<unsigned int, unsigned int> indices;
     148             : 
     149         138 :   if (axis == 0)
     150             :   {
     151           0 :     indices.first = 1;
     152           0 :     indices.second = 2;
     153             :   }
     154         138 :   else if (axis == 1)
     155             :   {
     156           0 :     indices.first = 0;
     157           0 :     indices.second = 2;
     158             :   }
     159             :   else
     160             :   {
     161         138 :     indices.first = 0;
     162         138 :     indices.second = 1;
     163             :   }
     164             : 
     165         138 :   return indices;
     166             : }
     167             : 
     168             : Point
     169           2 : projectedUnitNormal(Point pt1, Point pt2, const unsigned int axis)
     170             : {
     171             :   // project the points to the plane perpendicular to the axis
     172           2 :   pt1(axis) = 0.0;
     173           2 :   pt2(axis) = 0.0;
     174             : 
     175           2 :   const auto i = projectedIndices(axis);
     176             : 
     177           2 :   const Real dx = pt2(i.first) - pt1(i.first);
     178           2 :   const Real dy = pt2(i.second) - pt1(i.second);
     179             : 
     180           2 :   const Point normal = projectPoint(dy, -dx, axis);
     181           2 :   const Point gap_line = pt2 - pt1;
     182             : 
     183           2 :   const auto cross_product = gap_line.cross(normal);
     184             : 
     185           2 :   if (cross_product(axis) > 0)
     186           0 :     return normal.unit();
     187             :   else
     188           2 :     return projectPoint(-dy, dx, axis).unit();
     189             : }
     190             : 
     191             : Real
     192          56 : distanceFromLine(const Point & pt, const Point & line0, const Point & line1)
     193             : {
     194          56 :   const Point a = pt - line0;
     195          56 :   const Point b = pt - line1;
     196          56 :   const Point c = line1 - line0;
     197             : 
     198          56 :   return (a.cross(b).norm()) / c.norm();
     199             : }
     200             : 
     201             : Real
     202          44 : projectedDistanceFromLine(Point pt, Point line0, Point line1, const unsigned int axis)
     203             : {
     204             :   // project all the points to the plane perpendicular to the axis
     205          44 :   pt(axis) = 0.0;
     206          44 :   line0(axis) = 0.0;
     207          44 :   line1(axis) = 0.0;
     208             : 
     209          44 :   return distanceFromLine(pt, line0, line1);
     210             : }
     211             : 
     212             : std::vector<Point>
     213           4 : polygonCorners(const unsigned int num_sides, const Real radius, const unsigned int axis)
     214             : {
     215           4 :   std::vector<Point> corners;
     216           4 :   const Real theta = 2.0 * M_PI / num_sides;
     217           4 :   const Real first_angle = M_PI / 2.0 - theta / 2.0;
     218             : 
     219          24 :   for (const auto i : make_range(num_sides))
     220             :   {
     221          20 :     const Real angle = first_angle + i * theta;
     222          20 :     const Real x = radius * cos(angle);
     223          20 :     const Real y = radius * sin(angle);
     224             : 
     225          20 :     corners.push_back(projectPoint(x, y, axis));
     226             :   }
     227             : 
     228           4 :   return corners;
     229           0 : }
     230             : 
     231             : Point
     232         312 : rotatePointAboutAxis(const Point & p, const Real angle, const Point & axis)
     233             : {
     234         312 :   const Real cos_theta = cos(angle);
     235         312 :   const Real sin_theta = sin(angle);
     236             : 
     237         312 :   Point pt;
     238         312 :   const Real xy = axis(0) * axis(1);
     239         312 :   const Real xz = axis(0) * axis(2);
     240         312 :   const Real yz = axis(1) * axis(2);
     241             : 
     242         312 :   const Point x_op(cos_theta + axis(0) * axis(0) * (1.0 - cos_theta),
     243         624 :                    xy * (1.0 - cos_theta) - axis(2) * sin_theta,
     244         624 :                    xz * (1.0 - cos_theta) + axis(1) * sin_theta);
     245             : 
     246         624 :   const Point y_op(xy * (1.0 - cos_theta) + axis(2) * sin_theta,
     247         312 :                    cos_theta + axis(1) * axis(1) * (1.0 - cos_theta),
     248         624 :                    yz * (1.0 - cos_theta) - axis(0) * sin_theta);
     249             : 
     250         624 :   const Point z_op(xz * (1.0 - cos_theta) - axis(1) * sin_theta,
     251         624 :                    yz * (1.0 - cos_theta) + axis(0) * sin_theta,
     252         312 :                    cos_theta + axis(2) * axis(2) * (1.0 - cos_theta));
     253             : 
     254         312 :   pt(0) = x_op * p;
     255         312 :   pt(1) = y_op * p;
     256         312 :   pt(2) = z_op * p;
     257         624 :   return pt;
     258             : }
     259             : 
     260             : std::vector<Point>
     261           2 : boxCorners(const libMesh::BoundingBox & box, const Real factor)
     262             : {
     263           2 :   Point diff = (box.max() - box.min()) / 2.0;
     264           2 :   const Point origin = box.min() + diff;
     265             : 
     266             :   // Rescale side length of box by specified factor
     267           2 :   diff *= factor;
     268             : 
     269             :   // Vectors for sides of box
     270           2 :   const Point dx(2.0 * diff(0), 0.0, 0.0);
     271           2 :   const Point dy(0.0, 2.0 * diff(1), 0.0);
     272           2 :   const Point dz(0.0, 0.0, 2.0 * diff(2));
     273             : 
     274           2 :   std::vector<Point> verts(8, origin - diff);
     275           2 :   const unsigned int pts_per_dim = 2;
     276           6 :   for (const auto z : make_range(pts_per_dim))
     277          12 :     for (const auto y : make_range(pts_per_dim))
     278          24 :       for (const auto x : make_range(pts_per_dim))
     279          16 :         verts[pts_per_dim * pts_per_dim * z + pts_per_dim * y + x] += x * dx + y * dy + z * dz;
     280             : 
     281           4 :   return verts;
     282           0 : }
     283             : 
     284             : bool
     285           0 : arePointsColinear(const Point & p1, const Point & p2, const Point & p3)
     286             : {
     287           0 :   const Point v1 = p2 - p1;
     288           0 :   const Point v2 = p3 - p1;
     289           0 :   const Point cross_prod = v1.cross(v2);
     290             : 
     291           0 :   return MooseUtils::absoluteFuzzyEqual(cross_prod.norm(), 0.0);
     292             : }
     293             : 
     294             : bool
     295           0 : segmentsIntersect(const Point & p1, const Point & p2, const Point & p3, const Point & p4)
     296             : {
     297             :   mooseAssert(
     298             :       MooseUtils::absoluteFuzzyEqual(p1(2), 0.0) && MooseUtils::absoluteFuzzyEqual(p2(2), 0.0) &&
     299             :           MooseUtils::absoluteFuzzyEqual(p3(2), 0.0) && MooseUtils::absoluteFuzzyEqual(p4(2), 0.0),
     300             :       "segmentsIntersect only works in 2D (x-y plane)");
     301             : 
     302             :   mooseAssert(MooseUtils::absoluteFuzzyGreaterThan((p1 - p2).norm(), 0.0) &&
     303             :                   MooseUtils::absoluteFuzzyGreaterThan((p3 - p4).norm(), 0.0),
     304             :               "Zero length segments are not allowed in segmentsIntersect");
     305             : 
     306           0 :   const Real a1 = p2(1) - p1(1);
     307           0 :   const Real b1 = p1(0) - p2(0);
     308           0 :   const Real c1 = p2(0) * p1(1) - p1(0) * p2(1);
     309             : 
     310           0 :   const Real a2 = p4(1) - p3(1);
     311           0 :   const Real b2 = p3(0) - p4(0);
     312           0 :   const Real c2 = p4(0) * p3(1) - p3(0) * p4(1);
     313             : 
     314           0 :   const Real denom = a1 * b2 - a2 * b1;
     315           0 :   Point intersection_pt;
     316             :   // Parallel case
     317           0 :   if (MooseUtils::absoluteFuzzyEqual(denom, 0.0))
     318             :   {
     319           0 :     if (arePointsColinear(p1, p2, p3))
     320             :     {
     321             :       // for colinear segments, we construct a "virtual" intersection point using weighted average
     322             :       // In that case, the virtual point will always lie outside of both segments unless they
     323             :       // overlap
     324           0 :       const Point p12 = (p1 + p2) / 2.0;
     325           0 :       const Point p34 = (p3 + p4) / 2.0;
     326           0 :       const Real dist12 = (p1 - p2).norm();
     327           0 :       const Real dist34 = (p3 - p4).norm();
     328           0 :       intersection_pt = p12 + (p34 - p12) * (dist12 / (dist12 + dist34));
     329             :     }
     330             :     else
     331           0 :       return false;
     332             :   }
     333             :   else
     334             :   {
     335           0 :     intersection_pt = Point((b1 * c2 - b2 * c1) / denom, (a2 * c1 - a1 * c2) / denom, 0.0);
     336             :   }
     337             : 
     338           0 :   const Real ratio_p1p2 = (intersection_pt - p1) * (p2 - p1) / ((p2 - p1).norm_sq());
     339           0 :   const Real ratio_p3p4 = (intersection_pt - p3) * (p4 - p3) / ((p4 - p3).norm_sq());
     340             : 
     341           0 :   if (MooseUtils::absoluteFuzzyGreaterEqual(ratio_p1p2, 0.0) &&
     342           0 :       MooseUtils::absoluteFuzzyLessEqual(ratio_p1p2, 1.0) &&
     343           0 :       MooseUtils::absoluteFuzzyGreaterEqual(ratio_p3p4, 0.0) &&
     344           0 :       MooseUtils::absoluteFuzzyLessEqual(ratio_p3p4, 1.0))
     345           0 :     return true;
     346             :   else
     347           0 :     return false;
     348             : }
     349             : 
     350             : Real
     351       24470 : pointSegmentDistanceSq(const Point & point, const Point & a, const Point & b)
     352             : {
     353       24470 :   const Point ab = b - a;
     354       24470 :   const auto length_sq = ab.norm_sq();
     355       24470 :   if (length_sq <= std::numeric_limits<Real>::epsilon())
     356           0 :     return (point - a).norm_sq();
     357             : 
     358       24470 :   const auto t = std::clamp(((point - a) * ab) / length_sq, 0.0, 1.0);
     359       24470 :   const Point projection = a + t * ab;
     360       24470 :   return (point - projection).norm_sq();
     361             : }
     362             : 
     363             : Real
     364           0 : pointTriangleDistanceSq(const Point & point, const Point & v0, const Point & v1, const Point & v2)
     365             : {
     366           0 :   const Point ab = v1 - v0;
     367           0 :   const Point ac = v2 - v0;
     368           0 :   const Point ap = point - v0;
     369           0 :   const Real d1 = ab * ap;
     370           0 :   const Real d2 = ac * ap;
     371           0 :   if (d1 <= 0.0 && d2 <= 0.0)
     372           0 :     return (point - v0).norm_sq();
     373             : 
     374           0 :   const Point bp = point - v1;
     375           0 :   const Real d3 = ab * bp;
     376           0 :   const Real d4 = ac * bp;
     377           0 :   if (d3 >= 0.0 && d4 <= d3)
     378           0 :     return (point - v1).norm_sq();
     379             : 
     380           0 :   const Real vc = d1 * d4 - d3 * d2;
     381           0 :   if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0)
     382             :   {
     383           0 :     const Real v = d1 / (d1 - d3);
     384           0 :     const Point projection = v0 + v * ab;
     385           0 :     return (point - projection).norm_sq();
     386             :   }
     387             : 
     388           0 :   const Point cp = point - v2;
     389           0 :   const Real d5 = ab * cp;
     390           0 :   const Real d6 = ac * cp;
     391           0 :   if (d6 >= 0.0 && d5 <= d6)
     392           0 :     return (point - v2).norm_sq();
     393             : 
     394           0 :   const Real vb = d5 * d2 - d1 * d6;
     395           0 :   if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0)
     396             :   {
     397           0 :     const Real w = d2 / (d2 - d6);
     398           0 :     const Point projection = v0 + w * ac;
     399           0 :     return (point - projection).norm_sq();
     400             :   }
     401             : 
     402           0 :   const Real va = d3 * d6 - d5 * d4;
     403           0 :   if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0)
     404             :   {
     405           0 :     const Point bc = v2 - v1;
     406           0 :     const Real w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
     407           0 :     const Point projection = v1 + w * bc;
     408           0 :     return (point - projection).norm_sq();
     409             :   }
     410             : 
     411           0 :   const Real denom = 1.0 / (va + vb + vc);
     412           0 :   const Real v = vb * denom;
     413           0 :   const Real w = vc * denom;
     414           0 :   const Point projection = v0 + ab * v + ac * w;
     415           0 :   return (point - projection).norm_sq();
     416             : }
     417             : 
     418             : Real
     419        3360 : solidAngle(const Point & point, const Point & v0, const Point & v1, const Point & v2)
     420             : {
     421        3360 :   const Point a = v0 - point;
     422        3360 :   const Point b = v1 - point;
     423        3360 :   const Point c = v2 - point;
     424             : 
     425        3360 :   const Real la = a.norm();
     426        3360 :   const Real lb = b.norm();
     427        3360 :   const Real lc = c.norm();
     428             : 
     429        3360 :   const Real numerator = a * (b.cross(c));
     430        3360 :   const Real denominator = la * lb * lc + (a * b) * lc + (b * c) * la + (c * a) * lb;
     431             : 
     432        6720 :   return 2.0 * std::atan2(numerator, denominator);
     433             : }
     434             : } // end namespace geom_utils

Generated by: LCOV version 1.14