ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
Line3D.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "Line3D.h"
9 
10 #include <cmath>
11 
12 namespace cloudViewer {
13 namespace geometry {
14 
15 using namespace cloudViewer;
16 
17 // Line3D Implementations
18 // ===========================================================================
19 Line3D::Line3D(const Eigen::Vector3d& origin, const Eigen::Vector3d& direction)
20  : Line3D(origin, direction, LineType::Line) {}
21 
22 Line3D::Line3D(const Eigen::Vector3d& origin,
23  const Eigen::Vector3d& direction,
25  : Eigen::ParametrizedLine<double, 3>(origin, direction), line_type_(type) {
26  // Here we pre-compute the inverses of the direction vector for use during
27  // the slab AABB intersection algorithm. The choice to do this regardless
28  // of the purpose of the line's creation isn't optimal, but has to do with
29  // trying to keep both the outer API clean and avoid the overhead of having
30  // to check that the values have been computed before running any of the
31  // slab intersection algorithms, since the slab algorithm is the most
32  // performance critical operation this construct is used for.
33  x_inv_ = 1. / direction.x();
34  y_inv_ = 1. / direction.y();
35  z_inv_ = 1. / direction.z();
36 }
37 
38 void Line3D::Transform(const Eigen::Transform<double, 3, Eigen::Affine>& t) {
39  this->Transform(t);
40 }
41 
42 std::pair<double, double> Line3D::SlabAABBBase(const ccBBox& box) const {
43  /* This code is based off of Tavian Barnes' branchless implementation of
44  * the slab method for determining ray/AABB intersections. It treats the
45  * space inside the bounding box as three sets of parallel planes and clips
46  * the line where it passes between these planes to produce an
47  * ever-shrinking line segment.
48  *
49  * The line segment is represented by two scalar parameters, one for the
50  * clipped end on the far plane and one for the clipped end on the near
51  * plane. When the line does not pass through the bounding box the t_max
52  * and t_min distances will invert.
53  *
54  * https://tavianator.com/2011/ray_box.html */
55  double t_x0 =
56  x_inv_ * (static_cast<double>(box.minCorner().x) - origin().x());
57  double t_x1 =
58  x_inv_ * (static_cast<double>(box.maxCorner().x) - origin().x());
59  double t_min = std::min(t_x0, t_x1);
60  double t_max = std::max(t_x0, t_x1);
61 
62  double t_y0 =
63  y_inv_ * (static_cast<double>(box.minCorner().y) - origin().y());
64  double t_y1 =
65  y_inv_ * (static_cast<double>(box.maxCorner().y) - origin().y());
66  t_min = std::max(t_min, std::min(t_y0, t_y1));
67  t_max = std::min(t_max, std::max(t_y0, t_y1));
68 
69  double t_z0 =
70  z_inv_ * (static_cast<double>(box.minCorner().z) - origin().z());
71  double t_z1 =
72  z_inv_ * (static_cast<double>(box.maxCorner().z) - origin().z());
73  t_min = std::max(t_min, std::min(t_z0, t_z1));
74  t_max = std::min(t_max, std::max(t_z0, t_z1));
75 
76  return {t_min, t_max};
77 }
78 
80  /* This is a naive, exact method of computing the intersection with a
81  * bounding box. It is much slower than the highly optimized slab method,
82  * but will perform correctly in the one case where the slab method
83  * degenerates: when a ray lies exactly within one of the bounding planes.
84  * If your problem is structured such that the slab method is likely to
85  * encounter a degenerate scenario, AND you need an exact solution that can
86  * not allow the occasional non-intersection, AND you care about maximal
87  * performance, consider implementing a special check which takes advantage
88  * of the reduced dimensionality of your problem.
89  */
90  using namespace Eigen;
91 
92  // When running a stress test on randomly generated rays and boxes about 1%
93  // to 2% of the randomly generated cases will fail when using this method
94  // due to the round-trip vector coming back from the ParameterizedLine's
95  // intersectionParameter method being off in the 11th or greater decimal
96  // position from the original plane point. This tolerance seems to
97  // eliminate the issue.
98  double tol = 1e-10;
99  ccBBox b_tol{box.GetMinBound() - Vector3d(tol, tol, tol),
100  box.GetMaxBound() + Vector3d(tol, tol, tol)};
101 
102  using plane_t = Eigen::Hyperplane<double, 3>;
103  std::array<plane_t, 6> planes{{{{-1, 0, 0}, box.GetMinBound()},
104  {{1, 0, 0}, box.GetMaxBound()},
105  {{0, -1, 0}, box.GetMinBound()},
106  {{0, 1, 0}, box.GetMaxBound()},
107  {{0, 0, -1}, box.GetMinBound()},
108  {{0, 0, 1}, box.GetMaxBound()}}};
109 
110  // Get the intersections
111  std::vector<double> parameters;
112  std::vector<Eigen::Vector3d> points;
113  parameters.reserve(7);
114  points.reserve(7);
115 
116  if (line_type_ == LineType::Ray || line_type_ == LineType::Segment) {
117  parameters.push_back(0);
118  points.push_back(origin());
119  }
120 
121  for (std::size_t i = 0; i < 6; ++i) {
122  auto t = IntersectionParameter(planes[i]);
123  if (t.has_value()) {
124  parameters.push_back(t.value());
125  auto p = pointAt(t.value());
126  points.push_back(p);
127  }
128  }
129 
130  // Find the ones which are contained
131  auto contained_indices = b_tol.GetPointIndicesWithinBoundingBox(points);
132  if (contained_indices.empty()) return {};
133 
134  // Return the lowest parameter
135  double minimum = parameters[contained_indices[0]];
136  for (auto i : contained_indices) {
137  minimum = std::min(minimum, parameters[i]);
138  }
139  return minimum;
140 }
141 
143  /* The base case of the Line/AABB intersection allows for any intersection
144  * along the direction of the line at any distance, in accordance with the
145  * semantic meaning of a line.
146  *
147  * In such a case the only test is to determine if t_min and t_max have
148  * inverted, indicating that the line does not pass through the box at all.
149  */
150  const auto& t = SlabAABBBase(box);
151  double t_min = std::get<0>(t);
152  double t_max = std::get<1>(t);
153 
154  if (t_max >= t_min) return t_min;
155  return {};
156 }
157 
159  const Eigen::Hyperplane<double, 3>& plane) const {
160  double value = intersectionParameter(plane);
161  if (std::isinf(value)) {
162  return {};
163  } else {
164  return value;
165  }
166 }
167 
168 Eigen::Vector3d Line3D::Projection(const Eigen::Vector3d& point) const {
169  return Line().pointAt(ProjectionParameter(point));
170 }
171 
172 double Line3D::ProjectionParameter(const Eigen::Vector3d& point) const {
173  return ClampParameter(direction().dot(point - origin()));
174 }
175 
176 std::pair<double, double> Line3D::ClosestParameters(const Line3D& other) const {
177  /* The book "Real-Time Collision Detection" by Christer Ericson discusses
178  * in section 5.1.9 (p148) finding the closest point between two line
179  * segments.
180  *
181  * * The line-line solution is valid for segments when the parameters it
182  * produces are valid on both segments
183  *
184  * * If just one of the closest points between lines is outside of its
185  * segment, the point can be clamped to the segment and the result will
186  * be valid
187  *
188  * * If both points are outside of their respective segments, the clamping
189  * procedure ust be repeated twice. First, one of the points is clamped
190  * to its segment. Then it is projected onto the second segment, which
191  * may return an out-of-bound value as well. If it does, it must be
192  * clamped to its (the second) segment, then projected back onto the
193  * first segment. At that point the projection should be valid, and the
194  * closest distance is spanned by the two final parameters.
195  *
196  * Lines and rays in this case can be seen as a special case of a segment
197  * in which the validity of parameters are unbounded in one or more
198  * directions. Thus the same algorithm will account for lines and rays as
199  * well as segments.
200  *
201  * The last edge case is the case of parallel lines. In such a case an
202  * arbitrary parameter on one or the other line may be selected and the
203  * corresponding parameter on the other line computed. However, in the non-
204  * infinite case of rays and segments, this will not necessarily return a
205  * valid result.
206  *
207  * As such, the project/clamp + project/clamp procedure will work starting
208  * from this arbitrary point.
209  */
210 
211  // This first portion of the code performs the general computation of
212  // line parameters at the closest span between two infinite parameterized
213  // lines, and is adapted from the implementation found at
214  // http://geomalgorithms.com/a07-_distance.html
215  const auto& u = Direction();
216  const auto& v = other.Direction();
217  auto w = Origin() - other.Origin();
218  double a = u.dot(u);
219  double b = u.dot(v);
220  double c = v.dot(v);
221  double d = u.dot(w);
222  double e = v.dot(w);
223  double D = a * c - b * b;
224 
225  double sc, tc;
226  if (D < 1e-10) {
227  // In the case of almost parallel lines, we arbitrarily pick the first
228  // parameter to be 0 and then compute the other line's corresponding
229  // parameter.
230  sc = 0.;
231  tc = b > c ? d / b : e / c;
232  } else {
233  sc = (b * e - c * d) / D;
234  tc = (a * e - b * d) / D;
235  }
236 
237  // At this point, sc is the parameter of the closest point on *this line's
238  // infinite representation and tc is the corresponding point on the other
239  // line's infinite representation. If they are both valid, they can be
240  // returned as-is, if not a clamp/project round-trip must be executed
241  if (IsParameterValid(sc) && other.IsParameterValid(tc)) {
242  return {sc, tc};
243  }
244 
245  /* To avoid an edge case when two segments are parallel and disjoint and
246  * the origin of the first segment is further from `other` than its
247  * endpoint, we cannot take advantage of a single valid parameter. Instead
248  * we must clamp, project to the other line, clamp, and project back to
249  * this line.
250  *
251  * The .Project() method on Ray3D and Segment3D takes care of clamping, so
252  * it's a straightforward procedure.
253  */
254 
255  // Clamp sc to this line, then find tc by projecting onto the other line.
256  sc = ClampParameter(sc);
257  tc = other.ProjectionParameter(Line().pointAt(sc));
258 
259  // tc will already be clamped to other, so now we recompute sc
260  sc = ProjectionParameter(other.Line().pointAt(tc));
261 
262  return {sc, tc};
263 }
264 
265 std::pair<Eigen::Vector3d, Eigen::Vector3d> Line3D::ClosestPoints(
266  const Line3D& other) const {
267  /* The two closest points between two line entities is easily found by
268  * using the ClosestParameters method and rendering the parameters into
269  * points. */
270  auto result = ClosestParameters(other);
271  return {Line().pointAt(std::get<0>(result)),
272  other.Line().pointAt(std::get<1>(result))};
273 }
274 
275 double Line3D::DistanceTo(const Line3D& other) const {
276  /* The distance between two line entities is the distance between the
277  * closest points */
278  auto pair = ClosestPoints(other);
279 
280  return (std::get<0>(pair) - std::get<1>(pair)).norm();
281 }
282 
283 // Ray3D Implementations
284 // ===========================================================================
285 
286 Ray3D::Ray3D(const Eigen::Vector3d& origin, const Eigen::Vector3d& direction)
287  : Line3D(origin, direction, LineType::Ray) {}
288 
290  /* The ray case of the Line/AABB intersection allows for any intersection
291  * along the positive direction of the line at any distance, in accordance
292  * with the semantic meaning of a ray.
293  *
294  * In this case there are two conditions which must be met: t_max must be
295  * greater than t_min (the check for inversion), but it must also be greater
296  * than zero, as a negative t_max would indicate that the most positive
297  * intersection along the ray direction still lies behind the ray origin.
298  */
299  const auto& t = SlabAABBBase(box);
300  double t_min = std::get<0>(t);
301  double t_max = std::get<1>(t);
302 
303  t_min = std::max(0., t_min);
304 
305  if (t_max >= t_min) return t_min;
306  return {};
307 }
308 
310  const Eigen::Hyperplane<double, 3>& plane) const {
311  // On a ray, the intersection parameter cannot be negative as the ray does
312  // not exist in that direction.
313  auto result = Line().intersectionParameter(plane);
314  if (!std::isinf(result) && result >= 0) {
315  return result;
316  }
317  return {};
318 }
319 
320 // Segment3D Implementations
321 // ===========================================================================
322 
323 Segment3D::Segment3D(const Eigen::Vector3d& start_point,
324  const Eigen::Vector3d& end_point)
325  : Line3D(start_point,
326  (end_point - start_point).normalized(),
327  LineType::Segment),
328  end_point_(end_point),
329  length_((start_point - end_point_).norm()) {}
330 
331 Segment3D::Segment3D(const std::pair<Eigen::Vector3d, Eigen::Vector3d>& pair)
332  : Segment3D(std::get<0>(pair), std::get<1>(pair)) {}
333 
334 void Segment3D::Transform(const Eigen::Transform<double, 3, Eigen::Affine>& t) {
335  this->Transform(t);
336  end_point_ = t * end_point_;
337 }
338 
340  /* The segment case of the Line/AABB intersection only allows intersections
341  * along the positive direction of the line which are at a distance less
342  * than the segment length, in accordance with the semantic meaning of a
343  * line segment which is finite.
344  *
345  * In this case the same conditions apply as with the ray case, but with one
346  * additional requirement: t_min must be less than the length of the
347  * segment. If t_min were greater than the length of the segment it would
348  * indicate that the very earliest intersection along the line direction
349  * occurs beyond the endpoint of the segment.
350  */
351  const auto& t = SlabAABBBase(box);
352  double t_min = std::get<0>(t);
353  double t_max = std::get<1>(t);
354 
355  t_min = std::max(0., t_min);
356 
357  if (t_max >= t_min && t_min <= length_) return t_min;
358  return {};
359 }
360 
362  // For a line segment, the result must additionally be less than the
363  // overall length of the segment
364  auto result = Line3D::ExactAABB(box);
365  if (!result.has_value() || result.value() <= length_) return result;
366 
367  return {};
368 }
369 
371  Eigen::Vector3d min{std::min(origin().x(), end_point_.x()),
372  std::min(origin().y(), end_point_.y()),
373  std::min(origin().z(), end_point_.z())};
374 
375  Eigen::Vector3d max{std::max(origin().x(), end_point_.x()),
376  std::max(origin().y(), end_point_.y()),
377  std::max(origin().z(), end_point_.z())};
378  return {min, max};
379 }
380 
382  const Eigen::Hyperplane<double, 3>& plane) const {
383  // On a segment, the intersection parameter must be between zero and the
384  // length of the segment
385  auto result = Line().intersectionParameter(plane);
386  if (!std::isinf(result) && result >= 0 && result <= length_) {
387  return result;
388  }
389  return {};
390 }
391 
392 } // namespace geometry
393 } // namespace cloudViewer
int points
char type
Eigen::Vector3d origin
Definition: VoxelGridIO.cpp:26
core::Tensor result
Definition: VtkUtils.cpp:76
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Bounding box structure.
Definition: ecvBBox.h:25
virtual Eigen::Vector3d GetMaxBound() const override
Returns max bounds for geometry coordinates.
Definition: ecvBBox.h:84
virtual Eigen::Vector3d GetMinBound() const override
Returns min bounds for geometry coordinates.
Definition: ecvBBox.h:81
const Vector3Tpl< T > & maxCorner() const
Returns max corner (const)
Definition: BoundingBox.h:156
const Vector3Tpl< T > & minCorner() const
Returns min corner (const)
Definition: BoundingBox.h:154
Line3D is a class which derives from Eigen::ParametrizedLine<double, 3> in order to capture the seman...
Definition: Line3D.h:49
virtual void Transform(const Eigen::Transform< double, 3, Eigen::Affine > &t)
Transform the Line3D by the given matrix.
Definition: Line3D.cpp:38
virtual Eigen::Vector3d Projection(const Eigen::Vector3d &point) const
Calculates a point projected onto the line, taking into account special semantics.
Definition: Line3D.cpp:168
const Eigen::Vector3d & Direction() const
Gets the line's direction vector.
Definition: Line3D.h:87
double DistanceTo(const Line3D &other) const
Gets the closest distance between two Line3D objects, including derived types Ray3D and Segment3D,...
Definition: Line3D.cpp:275
LineType
Specifies different semantic interpretations of 3d lines.
Definition: Line3D.h:62
std::pair< Eigen::Vector3d, Eigen::Vector3d > ClosestPoints(const Line3D &other) const
Computes the two closest points between this Line3D object and the other, including of derived types ...
Definition: Line3D.cpp:265
std::pair< double, double > ClosestParameters(const Line3D &other) const
Computes the two corresponding parameters of the closest distance between two Line3D objects,...
Definition: Line3D.cpp:176
virtual cloudViewer::utility::optional< double > SlabAABB(const ccBBox &box) const
Returns the lower intersection parameter for a line with an axis aligned bounding box or empty if no ...
Definition: Line3D.cpp:142
std::pair< double, double > SlabAABBBase(const ccBBox &box) const
Calculates the common t_min and t_max values of the slab AABB intersection method....
Definition: Line3D.cpp:42
double ProjectionParameter(const Eigen::Vector3d &point) const
Calculates the parameter of a point projected onto the line taking into account special semantics.
Definition: Line3D.cpp:172
const Eigen::ParametrizedLine< double, 3 > & Line() const
Returns a const reference to the underlying Eigen::ParametrizedLine object.
Definition: Line3D.h:100
virtual bool IsParameterValid(double parameter) const
Verifies that a given parameter value is valid for the semantics of the line object....
Definition: Line3D.h:208
virtual cloudViewer::utility::optional< double > IntersectionParameter(const Eigen::Hyperplane< double, 3 > &plane) const
Calculates the intersection parameter between the line and a plane taking into account line semantics...
Definition: Line3D.cpp:158
const Eigen::Vector3d & Origin() const
Gets the line's origin point.
Definition: Line3D.h:84
virtual double ClampParameter(double parameter) const
Clamps/bounds a parameter value to the closest valid place where the entity exists....
Definition: Line3D.h:202
Line3D(const Eigen::Vector3d &origin, const Eigen::Vector3d &direction)
Default user constructor.
Definition: Line3D.cpp:19
virtual cloudViewer::utility::optional< double > ExactAABB(const ccBBox &box) const
Returns the lower intersection parameter for a line with an axis aligned bounding box or empty if no ...
Definition: Line3D.cpp:79
cloudViewer::utility::optional< double > IntersectionParameter(const Eigen::Hyperplane< double, 3 > &plane) const override
Calculates the intersection parameter between the line and a plane taking into account ray semantics....
Definition: Line3D.cpp:309
cloudViewer::utility::optional< double > SlabAABB(const ccBBox &box) const override
Returns the lower intersection parameter for a ray with an axis aligned bounding box or empty if no i...
Definition: Line3D.cpp:289
Ray3D(const Eigen::Vector3d &origin, const Eigen::Vector3d &direction)
Default constructor, requires point and direction.
Definition: Line3D.cpp:286
A segment is a semantic interpretation of Eigen::ParametrizedLine which has an origin and an endpoint...
Definition: Line3D.h:319
cloudViewer::utility::optional< double > SlabAABB(const ccBBox &box) const override
Returns the lower intersection parameter for a segment with an axis aligned bounding box or empty if ...
Definition: Line3D.cpp:339
cloudViewer::utility::optional< double > IntersectionParameter(const Eigen::Hyperplane< double, 3 > &plane) const override
Calculates the intersection parameter between the line and a plane taking into account segment semant...
Definition: Line3D.cpp:381
Segment3D(const Eigen::Vector3d &start_point, const Eigen::Vector3d &end_point)
Default constructor for Segment3D takes the start and end points of the segment.
Definition: Line3D.cpp:323
void Transform(const Eigen::Transform< double, 3, Eigen::Affine > &t) override
Transform the segment by the given matrix.
Definition: Line3D.cpp:334
cloudViewer::utility::optional< double > ExactAABB(const ccBBox &box) const override
Returns the lower intersection parameter for a segment with an axis aligned bounding box or empty if ...
Definition: Line3D.cpp:361
ccBBox GetBoundingBox() const
Get an axis-aligned bounding box representing the enclosed volume of the line segment.
Definition: Line3D.cpp:370
a[190]
const double * e
normal_z y
normal_z x
normal_z z
Definition: Eigen.h:103
Generic file read and write utility for python interface.
Definition: Eigen.h:85
Simple Ray structure.
Definition: RayAndBox.h:14