https://mooseframework.inl.gov
Loading...
Searching...
No Matches
OrientedBoundingBoxTest.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
10#include "gtest/gtest.h"
11#include "MooseMesh.h"
12#include "OrientedBoundingBox.h"
13
14#include "libmesh/replicated_mesh.h"
15#include "libmesh/elem.h"
16#include "libmesh/enum_elem_type.h"
17
18#include <cmath>
19#include <cstdio>
20#include <vector>
21
22namespace
23{
25bool
26containsPoint(const std::vector<Point> & pts, const Point & target, const Real tol)
27{
28 for (const auto & p : pts)
29 if ((p - target).norm() <= tol)
30 return true;
31 return false;
32}
33
35std::vector<Point>
36nodeLocations(const MeshBase & mesh)
37{
38 std::vector<Point> pts;
39 for (const auto * node : mesh.node_ptr_range())
40 pts.push_back(*node);
41 return pts;
42}
43}
44
45// Write an oriented 3D box, read it back, and verify all eight corners land where
46// the origin + subset-of-(len_i * dir_i) mapping predicts.
47TEST(OrientedBoundingBoxTest, WriteMeshBox3D)
48{
49 libMesh::Parallel::Communicator comm(MPI_COMM_SELF);
50 const Real tol = 1e-8;
51
52 // Orthonormal basis rotated 45 deg in the xy-plane, z unchanged.
53 const Real s = 1.0 / std::sqrt(2.0);
54 const Point origin(10.0, 20.0, 30.0);
55 const Point d0(s, s, 0.0), d1(-s, s, 0.0), d2(0.0, 0.0, 1.0);
56 const Real l0 = 2.0, l1 = 3.0, l2 = 1.0;
57
58 const std::vector<std::pair<Point, Point>> axis_pairs{
59 {origin, origin + l0 * d0}, {origin, origin + l1 * d1}, {origin, origin + l2 * d2}};
60 OrientedBoundingBox obb(axis_pairs);
61
62 const std::string file = "oriented_bounding_box_test_3d.e";
63 obb.writeMesh(file, comm);
64
65 ReplicatedMesh mesh(comm);
66 mesh.read(file);
67
68 EXPECT_EQ(mesh.n_nodes(), 8u);
69 EXPECT_EQ(mesh.n_elem(), 1u);
70 for (const auto * elem : mesh.active_element_ptr_range())
71 EXPECT_EQ(elem->type(), HEX8);
72
73 const std::vector<Point> nodes = nodeLocations(mesh);
74 for (unsigned int mask = 0; mask < 8u; ++mask)
75 {
76 Point corner = origin;
77 if (mask & 1u)
78 corner += l0 * d0;
79 if (mask & 2u)
80 corner += l1 * d1;
81 if (mask & 4u)
82 corner += l2 * d2;
83 EXPECT_TRUE(containsPoint(nodes, corner, tol)) << "missing corner, mask=" << mask;
84 }
85
86 std::remove(file.c_str());
87}
88
89// Write an oriented 2D box, read it back, and verify all four corners.
90TEST(OrientedBoundingBoxTest, WriteMeshBox2D)
91{
92 libMesh::Parallel::Communicator comm(MPI_COMM_SELF);
93 const Real tol = 1e-8;
94
95 const Real s = 1.0 / std::sqrt(2.0);
96 const Point origin(5.0, 6.0, 0.0);
97 const Point d0(s, s, 0.0), d1(-s, s, 0.0);
98 const Real l0 = 2.0, l1 = 3.0;
99
100 const std::vector<std::pair<Point, Point>> axis_pairs{{origin, origin + l0 * d0},
101 {origin, origin + l1 * d1}};
102 OrientedBoundingBox obb(axis_pairs);
103
104 const std::string file = "oriented_bounding_box_test_2d.e";
105 obb.writeMesh(file, comm);
106
107 ReplicatedMesh mesh(comm);
108 mesh.read(file);
109
110 EXPECT_EQ(mesh.n_nodes(), 4u);
111 EXPECT_EQ(mesh.n_elem(), 1u);
112 for (const auto * elem : mesh.active_element_ptr_range())
113 EXPECT_EQ(elem->type(), QUAD4);
114
115 const std::vector<Point> nodes = nodeLocations(mesh);
116 for (unsigned int mask = 0; mask < 4u; ++mask)
117 {
118 Point corner = origin;
119 if (mask & 1u)
120 corner += l0 * d0;
121 if (mask & 2u)
122 corner += l1 * d1;
123 EXPECT_TRUE(containsPoint(nodes, corner, tol)) << "missing corner, mask=" << mask;
124 }
125
126 std::remove(file.c_str());
127}
128
129// Write the ray along the shortest axis, read it back, and verify its endpoints.
130TEST(OrientedBoundingBoxTest, WriteRay)
131{
132 libMesh::Parallel::Communicator comm(MPI_COMM_SELF);
133 const Real tol = 1e-8;
134
135 const Real s = 1.0 / std::sqrt(2.0);
136 const Point origin(0.0, 0.0, 0.0);
137 const Point d0(s, s, 0.0), d1(-s, s, 0.0), d2(0.0, 0.0, 1.0);
138 const Real l0 = 2.0, l1 = 3.0, l2 = 1.0; // shortest axis is axis 2
139
140 const std::vector<std::pair<Point, Point>> axis_pairs{
141 {origin, origin + l0 * d0}, {origin, origin + l1 * d1}, {origin, origin + l2 * d2}};
142 OrientedBoundingBox obb(axis_pairs);
143
144 const std::string file = "oriented_bounding_box_test_ray.e";
145 obb.writeRayAlongShortestAxis(file, comm);
146
147 ReplicatedMesh mesh(comm);
148 mesh.read(file);
149
150 EXPECT_EQ(mesh.n_nodes(), 2u);
151 EXPECT_EQ(mesh.n_elem(), 1u);
152 for (const auto * elem : mesh.active_element_ptr_range())
153 EXPECT_EQ(elem->type(), EDGE2);
154
155 // Ray starts at the face centre orthogonal to the shortest axis and spans it.
156 const Point start = origin + 0.5 * l0 * d0 + 0.5 * l1 * d1;
157 const Point end = start + l2 * d2;
158
159 const std::vector<Point> nodes = nodeLocations(mesh);
160 EXPECT_TRUE(containsPoint(nodes, start, tol));
161 EXPECT_TRUE(containsPoint(nodes, end, tol));
162
163 std::remove(file.c_str());
164}
165
166// Query API on an axis-aligned box: corners, centroid, per-axis direction/length, containment,
167// and projected length.
168TEST(OrientedBoundingBoxTest, QueryApiAxisAligned)
169{
170 const Point origin(0.0, 0.0, 0.0);
171 const Point d0(1.0, 0.0, 0.0), d1(0.0, 1.0, 0.0), d2(0.0, 0.0, 1.0);
172 const Real l0 = 2.0, l1 = 3.0, l2 = 1.0;
173 const std::vector<std::pair<Point, Point>> axis_pairs{
174 {origin, origin + l0 * d0}, {origin, origin + l1 * d1}, {origin, origin + l2 * d2}};
175 OrientedBoundingBox obb(axis_pairs);
176
177 EXPECT_NEAR((obb.getMinimalCorner() - origin).norm(), 0.0, 1e-12);
178 EXPECT_NEAR((obb.getMaximalCorner() - Point(2.0, 3.0, 1.0)).norm(), 0.0, 1e-12);
179 EXPECT_NEAR((obb.centroid() - Point(1.0, 1.5, 0.5)).norm(), 0.0, 1e-12);
180
181 EXPECT_NEAR((obb.getAxisDirection(0) - d0).norm(), 0.0, 1e-12);
182 EXPECT_NEAR((obb.getAxisDirection(1) - d1).norm(), 0.0, 1e-12);
183 EXPECT_NEAR((obb.getAxisDirection(2) - d2).norm(), 0.0, 1e-12);
184 EXPECT_NEAR(obb.getAxisLength(0), l0, 1e-12);
185 EXPECT_NEAR(obb.getAxisLength(1), l1, 1e-12);
186 EXPECT_NEAR(obb.getAxisLength(2), l2, 1e-12);
187
188 EXPECT_TRUE(obb.contains(Point(1.0, 1.5, 0.5))); // interior
189 EXPECT_TRUE(obb.contains(origin)); // minimal corner (on boundary)
190 EXPECT_TRUE(obb.contains(Point(2.0, 3.0, 1.0))); // maximal corner
191 EXPECT_FALSE(obb.contains(Point(2.5, 1.5, 0.5))); // beyond axis-0 length
192 EXPECT_FALSE(obb.contains(Point(1.0, -0.1, 0.5))); // below axis-1 origin
193
194 EXPECT_NEAR(obb.getProjectedLength(Point(1.0, 1.5, 0.5), 0), 1.0, 1e-12);
195 EXPECT_NEAR(obb.getProjectedLength(Point(1.0, 1.5, 0.5), 1), 1.5, 1e-12);
196 EXPECT_NEAR(obb.getProjectedLength(Point(1.0, 1.5, 0.5), 2), 0.5, 1e-12);
197}
198
199// Query API on a box rotated 45 deg in the xy-plane: projected lengths recover the box-local
200// coordinates and drive containment.
201TEST(OrientedBoundingBoxTest, QueryApiRotated)
202{
203 const Real s = 1.0 / std::sqrt(2.0);
204 const Point origin(1.0, 2.0, 3.0);
205 const Point d0(s, s, 0.0), d1(-s, s, 0.0), d2(0.0, 0.0, 1.0);
206 const Real l0 = 2.0, l1 = 3.0, l2 = 1.0;
207 const std::vector<std::pair<Point, Point>> axis_pairs{
208 {origin, origin + l0 * d0}, {origin, origin + l1 * d1}, {origin, origin + l2 * d2}};
209 OrientedBoundingBox obb(axis_pairs);
210
211 EXPECT_NEAR((obb.getAxisDirection(0) - d0).norm(), 0.0, 1e-12);
212 EXPECT_NEAR((obb.getAxisDirection(1) - d1).norm(), 0.0, 1e-12);
213
214 // A point at box-local coordinates (a, b, c) maps to origin + a*d0 + b*d1 + c*d2.
215 const Point inside = origin + 1.5 * d0 + 2.0 * d1 + 0.5 * d2;
216 EXPECT_NEAR(obb.getProjectedLength(inside, 0), 1.5, 1e-12);
217 EXPECT_NEAR(obb.getProjectedLength(inside, 1), 2.0, 1e-12);
218 EXPECT_NEAR(obb.getProjectedLength(inside, 2), 0.5, 1e-12);
219 EXPECT_TRUE(obb.contains(inside));
220
221 const Point outside = origin + 2.5 * d0 + 1.0 * d1 + 0.5 * d2; // 2.5 > l0
222 EXPECT_NEAR(obb.getProjectedLength(outside, 0), 2.5, 1e-12);
223 EXPECT_FALSE(obb.contains(outside));
224}
225
226// A default-constructed box is the empty placeholder used as a member default; its corners and
227// centroid are the origin.
228TEST(OrientedBoundingBoxTest, DefaultConstructed)
229{
231 EXPECT_NEAR(empty.getMinimalCorner().norm(), 0.0, 1e-12);
232 EXPECT_NEAR(empty.getMaximalCorner().norm(), 0.0, 1e-12);
233 EXPECT_NEAR(empty.centroid().norm(), 0.0, 1e-12);
234}
const double tol
const Real p
TEST(OrientedBoundingBoxTest, WriteMeshBox3D)
Real getAxisLength(unsigned int i) const
Point getMinimalCorner() const
bool contains(const Point &pt, const Real tolerance=libMesh::TOLERANCE) const
void writeRayAlongShortestAxis(const std::filesystem::path &ray_path, const libMesh::Parallel::Communicator &comm) const
Point getAxisDirection(unsigned int i) const
Point getMaximalCorner() const
void writeMesh(const std::filesystem::path &path, const libMesh::Parallel::Communicator &comm) const
Real getProjectedLength(const Point &pt, unsigned int i) const
Point centroid() const
MeshBase & mesh
auto norm(const T &a)
if(subdm)