Point Cloud Library (PCL)  1.10.1-dev
transforms.hpp
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Point Cloud Library (PCL) - www.pointclouds.org
5  * Copyright (c) 2010, Willow Garage, Inc.
6  * Copyright (c) 2012-, Open Perception, Inc.
7  *
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
14  * * Redistributions of source code must retain the above copyright
15  * notice, this list of conditions and the following disclaimer.
16  * * Redistributions in binary form must reproduce the above
17  * copyright notice, this list of conditions and the following
18  * disclaimer in the documentation and/or other materials provided
19  * with the distribution.
20  * * Neither the name of the copyright holder(s) nor the names of its
21  * contributors may be used to endorse or promote products derived
22  * from this software without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  *
37  *
38  */
39 
40 #if defined(__SSE2__)
41 #include <xmmintrin.h>
42 #endif
43 
44 #if defined(__AVX__)
45 #include <immintrin.h>
46 #endif
47 
48 #include <algorithm>
49 #include <cmath>
50 #include <cstddef>
51 #include <vector>
52 
53 namespace pcl
54 {
55 
56  namespace detail
57  {
58 
59  /** A helper struct to apply an SO3 or SE3 transform to a 3D point.
60  * Supports single and double precision transform matrices. */
61  template<typename Scalar>
62  struct Transformer
63  {
64  const Eigen::Matrix<Scalar, 4, 4>& tf;
65 
66  /** Construct a transformer object.
67  * The transform matrix is captured by const reference. Make sure that it does not go out of scope before this
68  * object does. */
69  Transformer (const Eigen::Matrix<Scalar, 4, 4>& transform) : tf (transform) { };
70 
71  /** Apply SO3 transform (top-left corner of the transform matrix).
72  * \param[in] src input 3D point (pointer to 3 floats)
73  * \param[out] tgt output 3D point (pointer to 4 floats), can be the same as input. The fourth element is set to 0. */
74  void so3 (const float* src, float* tgt) const
75  {
76  const Scalar p[3] = { src[0], src[1], src[2] }; // need this when src == tgt
77  tgt[0] = static_cast<float> (tf (0, 0) * p[0] + tf (0, 1) * p[1] + tf (0, 2) * p[2]);
78  tgt[1] = static_cast<float> (tf (1, 0) * p[0] + tf (1, 1) * p[1] + tf (1, 2) * p[2]);
79  tgt[2] = static_cast<float> (tf (2, 0) * p[0] + tf (2, 1) * p[1] + tf (2, 2) * p[2]);
80  tgt[3] = 0;
81  }
82 
83  /** Apply SE3 transform.
84  * \param[in] src input 3D point (pointer to 3 floats)
85  * \param[out] tgt output 3D point (pointer to 4 floats), can be the same as input. The fourth element is set to 1. */
86  void se3 (const float* src, float* tgt) const
87  {
88  const Scalar p[3] = { src[0], src[1], src[2] }; // need this when src == tgt
89  tgt[0] = static_cast<float> (tf (0, 0) * p[0] + tf (0, 1) * p[1] + tf (0, 2) * p[2] + tf (0, 3));
90  tgt[1] = static_cast<float> (tf (1, 0) * p[0] + tf (1, 1) * p[1] + tf (1, 2) * p[2] + tf (1, 3));
91  tgt[2] = static_cast<float> (tf (2, 0) * p[0] + tf (2, 1) * p[1] + tf (2, 2) * p[2] + tf (2, 3));
92  tgt[3] = 1;
93  }
94  };
95 
96 #if defined(__SSE2__)
97 
98  /** Optimized version for single-precision transforms using SSE2 intrinsics. */
99  template<>
100  struct Transformer<float>
101  {
102  /// Columns of the transform matrix stored in XMM registers.
103  __m128 c[4];
104 
105  Transformer(const Eigen::Matrix4f& tf)
106  {
107  for (std::size_t i = 0; i < 4; ++i)
108  c[i] = _mm_load_ps (tf.col (i).data ());
109  }
110 
111  void so3 (const float* src, float* tgt) const
112  {
113  __m128 p0 = _mm_mul_ps (_mm_load_ps1 (&src[0]), c[0]);
114  __m128 p1 = _mm_mul_ps (_mm_load_ps1 (&src[1]), c[1]);
115  __m128 p2 = _mm_mul_ps (_mm_load_ps1 (&src[2]), c[2]);
116  _mm_store_ps (tgt, _mm_add_ps(p0, _mm_add_ps(p1, p2)));
117  }
118 
119  void se3 (const float* src, float* tgt) const
120  {
121  __m128 p0 = _mm_mul_ps (_mm_load_ps1 (&src[0]), c[0]);
122  __m128 p1 = _mm_mul_ps (_mm_load_ps1 (&src[1]), c[1]);
123  __m128 p2 = _mm_mul_ps (_mm_load_ps1 (&src[2]), c[2]);
124  _mm_store_ps (tgt, _mm_add_ps(p0, _mm_add_ps(p1, _mm_add_ps(p2, c[3]))));
125  }
126  };
127 
128 #if !defined(__AVX__)
129 
130  /** Optimized version for double-precision transform using SSE2 intrinsics. */
131  template<>
132  struct Transformer<double>
133  {
134  /// Columns of the transform matrix stored in XMM registers.
135  __m128d c[4][2];
136 
137  Transformer(const Eigen::Matrix4d& tf)
138  {
139  for (std::size_t i = 0; i < 4; ++i)
140  {
141  c[i][0] = _mm_load_pd (tf.col (i).data () + 0);
142  c[i][1] = _mm_load_pd (tf.col (i).data () + 2);
143  }
144  }
145 
146  void so3 (const float* src, float* tgt) const
147  {
148  __m128d xx = _mm_cvtps_pd (_mm_load_ps1 (&src[0]));
149  __m128d p0 = _mm_mul_pd (xx, c[0][0]);
150  __m128d p1 = _mm_mul_pd (xx, c[0][1]);
151 
152  for (std::size_t i = 1; i < 3; ++i)
153  {
154  __m128d vv = _mm_cvtps_pd (_mm_load_ps1 (&src[i]));
155  p0 = _mm_add_pd (_mm_mul_pd (vv, c[i][0]), p0);
156  p1 = _mm_add_pd (_mm_mul_pd (vv, c[i][1]), p1);
157  }
158 
159  _mm_store_ps (tgt, _mm_movelh_ps (_mm_cvtpd_ps (p0), _mm_cvtpd_ps (p1)));
160  }
161 
162  void se3 (const float* src, float* tgt) const
163  {
164  __m128d p0 = c[3][0];
165  __m128d p1 = c[3][1];
166 
167  for (std::size_t i = 0; i < 3; ++i)
168  {
169  __m128d vv = _mm_cvtps_pd (_mm_load_ps1 (&src[i]));
170  p0 = _mm_add_pd (_mm_mul_pd (vv, c[i][0]), p0);
171  p1 = _mm_add_pd (_mm_mul_pd (vv, c[i][1]), p1);
172  }
173 
174  _mm_store_ps (tgt, _mm_movelh_ps (_mm_cvtpd_ps (p0), _mm_cvtpd_ps (p1)));
175  }
176 
177  };
178 
179 #else
180 
181  /** Optimized version for double-precision transform using AVX intrinsics. */
182  template<>
183  struct Transformer<double>
184  {
185  __m256d c[4];
186 
187  Transformer(const Eigen::Matrix4d& tf)
188  {
189  for (std::size_t i = 0; i < 4; ++i)
190  c[i] = _mm256_load_pd (tf.col (i).data ());
191  }
192 
193  void so3 (const float* src, float* tgt) const
194  {
195  __m256d p0 = _mm256_mul_pd (_mm256_cvtps_pd (_mm_load_ps1 (&src[0])), c[0]);
196  __m256d p1 = _mm256_mul_pd (_mm256_cvtps_pd (_mm_load_ps1 (&src[1])), c[1]);
197  __m256d p2 = _mm256_mul_pd (_mm256_cvtps_pd (_mm_load_ps1 (&src[2])), c[2]);
198  _mm_store_ps (tgt, _mm256_cvtpd_ps (_mm256_add_pd(p0, _mm256_add_pd(p1, p2))));
199  }
200 
201  void se3 (const float* src, float* tgt) const
202  {
203  __m256d p0 = _mm256_mul_pd (_mm256_cvtps_pd (_mm_load_ps1 (&src[0])), c[0]);
204  __m256d p1 = _mm256_mul_pd (_mm256_cvtps_pd (_mm_load_ps1 (&src[1])), c[1]);
205  __m256d p2 = _mm256_mul_pd (_mm256_cvtps_pd (_mm_load_ps1 (&src[2])), c[2]);
206  _mm_store_ps (tgt, _mm256_cvtpd_ps (_mm256_add_pd(p0, _mm256_add_pd(p1, _mm256_add_pd(p2, c[3])))));
207  }
208 
209  };
210 
211 #endif
212 #endif
213 
214  }
215 
216 }
217 
218 ///////////////////////////////////////////////////////////////////////////////////////////
219 template <typename PointT, typename Scalar> void
221  pcl::PointCloud<PointT> &cloud_out,
222  const Eigen::Transform<Scalar, 3, Eigen::Affine> &transform,
223  bool copy_all_fields)
224 {
225  if (&cloud_in != &cloud_out)
226  {
227  cloud_out.header = cloud_in.header;
228  cloud_out.is_dense = cloud_in.is_dense;
229  cloud_out.width = cloud_in.width;
230  cloud_out.height = cloud_in.height;
231  cloud_out.points.reserve (cloud_in.points.size ());
232  if (copy_all_fields)
233  cloud_out.points.assign (cloud_in.points.begin (), cloud_in.points.end ());
234  else
235  cloud_out.points.resize (cloud_in.points.size ());
236  cloud_out.sensor_orientation_ = cloud_in.sensor_orientation_;
237  cloud_out.sensor_origin_ = cloud_in.sensor_origin_;
238  }
239 
240  pcl::detail::Transformer<Scalar> tf (transform.matrix ());
241  if (cloud_in.is_dense)
242  {
243  // If the dataset is dense, simply transform it!
244  for (std::size_t i = 0; i < cloud_out.points.size (); ++i)
245  tf.se3 (cloud_in[i].data, cloud_out[i].data);
246  }
247  else
248  {
249  // Dataset might contain NaNs and Infs, so check for them first,
250  // otherwise we get errors during the multiplication (?)
251  for (std::size_t i = 0; i < cloud_out.points.size (); ++i)
252  {
253  if (!std::isfinite (cloud_in.points[i].x) ||
254  !std::isfinite (cloud_in.points[i].y) ||
255  !std::isfinite (cloud_in.points[i].z))
256  continue;
257  tf.se3 (cloud_in[i].data, cloud_out[i].data);
258  }
259  }
260 }
261 
262 ///////////////////////////////////////////////////////////////////////////////////////////
263 template <typename PointT, typename Scalar> void
265  const std::vector<int> &indices,
266  pcl::PointCloud<PointT> &cloud_out,
267  const Eigen::Transform<Scalar, 3, Eigen::Affine> &transform,
268  bool copy_all_fields)
269 {
270  std::size_t npts = indices.size ();
271  // In order to transform the data, we need to remove NaNs
272  cloud_out.is_dense = cloud_in.is_dense;
273  cloud_out.header = cloud_in.header;
274  cloud_out.width = static_cast<int> (npts);
275  cloud_out.height = 1;
276  cloud_out.points.resize (npts);
277  cloud_out.sensor_orientation_ = cloud_in.sensor_orientation_;
278  cloud_out.sensor_origin_ = cloud_in.sensor_origin_;
279 
280  pcl::detail::Transformer<Scalar> tf (transform.matrix ());
281  if (cloud_in.is_dense)
282  {
283  // If the dataset is dense, simply transform it!
284  for (std::size_t i = 0; i < npts; ++i)
285  {
286  // Copy fields first, then transform xyz data
287  if (copy_all_fields)
288  cloud_out.points[i] = cloud_in.points[indices[i]];
289  tf.se3 (cloud_in[indices[i]].data, cloud_out[i].data);
290  }
291  }
292  else
293  {
294  // Dataset might contain NaNs and Infs, so check for them first,
295  // otherwise we get errors during the multiplication (?)
296  for (std::size_t i = 0; i < npts; ++i)
297  {
298  if (copy_all_fields)
299  cloud_out.points[i] = cloud_in.points[indices[i]];
300  if (!std::isfinite (cloud_in.points[indices[i]].x) ||
301  !std::isfinite (cloud_in.points[indices[i]].y) ||
302  !std::isfinite (cloud_in.points[indices[i]].z))
303  continue;
304  tf.se3 (cloud_in[indices[i]].data, cloud_out[i].data);
305  }
306  }
307 }
308 
309 ///////////////////////////////////////////////////////////////////////////////////////////
310 template <typename PointT, typename Scalar> void
312  pcl::PointCloud<PointT> &cloud_out,
313  const Eigen::Transform<Scalar, 3, Eigen::Affine> &transform,
314  bool copy_all_fields)
315 {
316  if (&cloud_in != &cloud_out)
317  {
318  // Note: could be replaced by cloud_out = cloud_in
319  cloud_out.header = cloud_in.header;
320  cloud_out.width = cloud_in.width;
321  cloud_out.height = cloud_in.height;
322  cloud_out.is_dense = cloud_in.is_dense;
323  cloud_out.points.reserve (cloud_out.points.size ());
324  if (copy_all_fields)
325  cloud_out.points.assign (cloud_in.points.begin (), cloud_in.points.end ());
326  else
327  cloud_out.points.resize (cloud_in.points.size ());
328  cloud_out.sensor_orientation_ = cloud_in.sensor_orientation_;
329  cloud_out.sensor_origin_ = cloud_in.sensor_origin_;
330  }
331 
332  pcl::detail::Transformer<Scalar> tf (transform.matrix ());
333  // If the data is dense, we don't need to check for NaN
334  if (cloud_in.is_dense)
335  {
336  for (std::size_t i = 0; i < cloud_out.points.size (); ++i)
337  {
338  tf.se3 (cloud_in[i].data, cloud_out[i].data);
339  tf.so3 (cloud_in[i].data_n, cloud_out[i].data_n);
340  }
341  }
342  // Dataset might contain NaNs and Infs, so check for them first.
343  else
344  {
345  for (std::size_t i = 0; i < cloud_out.points.size (); ++i)
346  {
347  if (!std::isfinite (cloud_in.points[i].x) ||
348  !std::isfinite (cloud_in.points[i].y) ||
349  !std::isfinite (cloud_in.points[i].z))
350  continue;
351  tf.se3 (cloud_in[i].data, cloud_out[i].data);
352  tf.so3 (cloud_in[i].data_n, cloud_out[i].data_n);
353  }
354  }
355 }
356 
357 ///////////////////////////////////////////////////////////////////////////////////////////
358 template <typename PointT, typename Scalar> void
360  const std::vector<int> &indices,
361  pcl::PointCloud<PointT> &cloud_out,
362  const Eigen::Transform<Scalar, 3, Eigen::Affine> &transform,
363  bool copy_all_fields)
364 {
365  std::size_t npts = indices.size ();
366  // In order to transform the data, we need to remove NaNs
367  cloud_out.is_dense = cloud_in.is_dense;
368  cloud_out.header = cloud_in.header;
369  cloud_out.width = static_cast<int> (npts);
370  cloud_out.height = 1;
371  cloud_out.points.resize (npts);
372  cloud_out.sensor_orientation_ = cloud_in.sensor_orientation_;
373  cloud_out.sensor_origin_ = cloud_in.sensor_origin_;
374 
375  pcl::detail::Transformer<Scalar> tf (transform.matrix ());
376  // If the data is dense, we don't need to check for NaN
377  if (cloud_in.is_dense)
378  {
379  for (std::size_t i = 0; i < cloud_out.points.size (); ++i)
380  {
381  // Copy fields first, then transform
382  if (copy_all_fields)
383  cloud_out.points[i] = cloud_in.points[indices[i]];
384  tf.se3 (cloud_in[indices[i]].data, cloud_out[i].data);
385  tf.so3 (cloud_in[indices[i]].data_n, cloud_out[i].data_n);
386  }
387  }
388  // Dataset might contain NaNs and Infs, so check for them first.
389  else
390  {
391  for (std::size_t i = 0; i < cloud_out.points.size (); ++i)
392  {
393  // Copy fields first, then transform
394  if (copy_all_fields)
395  cloud_out.points[i] = cloud_in.points[indices[i]];
396 
397  if (!std::isfinite (cloud_in.points[indices[i]].x) ||
398  !std::isfinite (cloud_in.points[indices[i]].y) ||
399  !std::isfinite (cloud_in.points[indices[i]].z))
400  continue;
401 
402  tf.se3 (cloud_in[indices[i]].data, cloud_out[i].data);
403  tf.so3 (cloud_in[indices[i]].data_n, cloud_out[i].data_n);
404  }
405  }
406 }
407 
408 ///////////////////////////////////////////////////////////////////////////////////////////
409 template <typename PointT, typename Scalar> inline void
411  pcl::PointCloud<PointT> &cloud_out,
412  const Eigen::Matrix<Scalar, 3, 1> &offset,
413  const Eigen::Quaternion<Scalar> &rotation,
414  bool copy_all_fields)
415 {
416  Eigen::Translation<Scalar, 3> translation (offset);
417  // Assemble an Eigen Transform
418  Eigen::Transform<Scalar, 3, Eigen::Affine> t (translation * rotation);
419  transformPointCloud (cloud_in, cloud_out, t, copy_all_fields);
420 }
421 
422 ///////////////////////////////////////////////////////////////////////////////////////////
423 template <typename PointT, typename Scalar> inline void
425  pcl::PointCloud<PointT> &cloud_out,
426  const Eigen::Matrix<Scalar, 3, 1> &offset,
427  const Eigen::Quaternion<Scalar> &rotation,
428  bool copy_all_fields)
429 {
430  Eigen::Translation<Scalar, 3> translation (offset);
431  // Assemble an Eigen Transform
432  Eigen::Transform<Scalar, 3, Eigen::Affine> t (translation * rotation);
433  transformPointCloudWithNormals (cloud_in, cloud_out, t, copy_all_fields);
434 }
435 
436 ///////////////////////////////////////////////////////////////////////////////////////////
437 template <typename PointT, typename Scalar> inline PointT
439  const Eigen::Transform<Scalar, 3, Eigen::Affine> &transform)
440 {
441  PointT ret = point;
442  pcl::detail::Transformer<Scalar> tf (transform.matrix ());
443  tf.se3 (point.data, ret.data);
444  return (ret);
445 }
446 
447 ///////////////////////////////////////////////////////////////////////////////////////////
448 template <typename PointT, typename Scalar> inline PointT
450  const Eigen::Transform<Scalar, 3, Eigen::Affine> &transform)
451 {
452  PointT ret = point;
453  pcl::detail::Transformer<Scalar> tf (transform.matrix ());
454  tf.se3 (point.data, ret.data);
455  tf.so3 (point.data_n, ret.data_n);
456  return (ret);
457 }
458 
459 ///////////////////////////////////////////////////////////////////////////////////////////
460 template <typename PointT, typename Scalar> double
462  Eigen::Transform<Scalar, 3, Eigen::Affine> &transform)
463 {
464  EIGEN_ALIGN16 Eigen::Matrix<Scalar, 3, 3> covariance_matrix;
465  Eigen::Matrix<Scalar, 4, 1> centroid;
466 
467  pcl::computeMeanAndCovarianceMatrix (cloud, covariance_matrix, centroid);
468 
469  EIGEN_ALIGN16 Eigen::Matrix<Scalar, 3, 3> eigen_vects;
470  Eigen::Matrix<Scalar, 3, 1> eigen_vals;
471  pcl::eigen33 (covariance_matrix, eigen_vects, eigen_vals);
472 
473  double rel1 = eigen_vals.coeff (0) / eigen_vals.coeff (1);
474  double rel2 = eigen_vals.coeff (1) / eigen_vals.coeff (2);
475 
476  transform.translation () = centroid.head (3);
477  transform.linear () = eigen_vects;
478 
479  return (std::min (rel1, rel2));
480 }
481 
void transformPoint(const Eigen::Matrix< Scalar, 3, 1 > &point_in, Eigen::Matrix< Scalar, 3, 1 > &point_out, const Eigen::Transform< Scalar, 3, Eigen::Affine > &transformation)
Transform a point using an affine matrix.
Definition: eigen.h:423
double getPrincipalTransformation(const pcl::PointCloud< PointT > &cloud, Eigen::Transform< Scalar, 3, Eigen::Affine > &transform)
Calculates the principal (PCA-based) alignment of the point cloud.
Definition: transforms.hpp:461
std::vector< PointT, Eigen::aligned_allocator< PointT > > points
The point data.
Definition: point_cloud.h:396
unsigned int computeMeanAndCovarianceMatrix(const pcl::PointCloud< PointT > &cloud, Eigen::Matrix< Scalar, 3, 3 > &covariance_matrix, Eigen::Matrix< Scalar, 4, 1 > &centroid)
Compute the normalized 3x3 covariance matrix and the centroid of a given set of points in a single lo...
Definition: centroid.hpp:483
void transformPointCloudWithNormals(const pcl::PointCloud< PointT > &cloud_in, pcl::PointCloud< PointT > &cloud_out, const Eigen::Transform< Scalar, 3, Eigen::Affine > &transform, bool copy_all_fields=true)
Transform a point cloud and rotate its normals using an Eigen transform.
Definition: transforms.hpp:311
std::uint32_t width
The point cloud width (if organized as an image-structure).
Definition: point_cloud.h:399
Eigen::Vector4f sensor_origin_
Sensor acquisition pose (origin/translation).
Definition: point_cloud.h:407
Transformer(const Eigen::Matrix< Scalar, 4, 4 > &transform)
Construct a transformer object.
Definition: transforms.hpp:69
const Eigen::Matrix< Scalar, 4, 4 > & tf
Definition: transforms.hpp:64
void so3(const float *src, float *tgt) const
Apply SO3 transform (top-left corner of the transform matrix).
Definition: transforms.hpp:74
void transformPointCloud(const pcl::PointCloud< PointT > &cloud_in, pcl::PointCloud< PointT > &cloud_out, const Eigen::Transform< Scalar, 3, Eigen::Affine > &transform, bool copy_all_fields=true)
Apply an affine transform defined by an Eigen Transform.
Definition: transforms.hpp:220
Eigen::Quaternionf sensor_orientation_
Sensor acquisition pose (rotation).
Definition: point_cloud.h:409
std::uint32_t height
The point cloud height (if organized as an image-structure).
Definition: point_cloud.h:401
PointCloud represents the base class in PCL for storing collections of 3D points. ...
pcl::PCLHeader header
The point cloud header.
Definition: point_cloud.h:393
void eigen33(const Matrix &mat, typename Matrix::Scalar &eigenvalue, Vector &eigenvector)
determines the eigenvector and eigenvalue of the smallest eigenvalue of the symmetric positive semi d...
Definition: eigen.hpp:291
void se3(const float *src, float *tgt) const
Apply SE3 transform.
Definition: transforms.hpp:86
PointT transformPointWithNormal(const PointT &point, const Eigen::Transform< Scalar, 3, Eigen::Affine > &transform)
Transform a point with members x,y,z,normal_x,normal_y,normal_z.
Definition: transforms.hpp:449
bool is_dense
True if no points are invalid (e.g., have NaN or Inf values in any of their floating point fields)...
Definition: point_cloud.h:404
A point structure representing Euclidean xyz coordinates, and the RGB color.
A helper struct to apply an SO3 or SE3 transform to a 3D point.
Definition: transforms.hpp:62