@@ -216,6 +216,114 @@ namespace diffCheck::geometry
216216 this ->Normals .push_back (normal);
217217 }
218218
219+ std::vector<Eigen::Vector3d> DFPointCloud::GetPrincipalAxes (int nComponents)
220+ {
221+ std::vector<Eigen::Vector3d> principalAxes;
222+
223+ if (! this ->HasNormals ())
224+ {
225+ DIFFCHECK_WARN (" The point cloud has no normals. Normals will be estimated with knn = 20." );
226+ this ->EstimateNormals (true , 20 );
227+ }
228+
229+ // Convert normals to Eigen matrix
230+ Eigen::Matrix<double , 3 , Eigen::Dynamic> normalMatrix (3 , this ->Normals .size ());
231+ for (size_t i = 0 ; i < this ->Normals .size (); ++i)
232+ {
233+ normalMatrix.col (i) = this ->Normals [i].cast <double >();
234+ }
235+
236+ cilantro::KMeans<double , 3 > kmeans (normalMatrix);
237+ kmeans.cluster (nComponents);
238+
239+ const cilantro::VectorSet3d& centroids = kmeans.getClusterCentroids ();
240+ const std::vector<size_t >& assignments = kmeans.getPointToClusterIndexMap ();
241+ std::vector<int > clusterSizes (nComponents, 0 );
242+ for (size_t i = 0 ; i < assignments.size (); ++i)
243+ {
244+ clusterSizes[assignments[i]]++;
245+ }
246+ // Sort clusters by size
247+ std::vector<std::pair<int , Eigen::Vector3d>> sortedClustersBySize (nComponents);
248+ for (size_t i = 0 ; i < nComponents; ++i)
249+ {
250+ sortedClustersBySize[i] = {clusterSizes[i], centroids.col (i)};
251+ }
252+ std::sort (sortedClustersBySize.begin (), sortedClustersBySize.end (), [](const auto & a, const auto & b)
253+ {
254+ return a.first > b.first ;
255+ });
256+
257+ for (size_t i = 0 ; i < nComponents; ++i)
258+ {
259+ if (principalAxes.size () == 0 )
260+ {
261+ principalAxes.push_back (sortedClustersBySize[i].second );
262+ }
263+ else
264+ {
265+ bool isAlreadyPresent = false ;
266+ for (const auto & axis : principalAxes)
267+ {
268+ double dotProduct = std::abs (axis.dot (sortedClustersBySize[i].second ));
269+ if (std::abs (dotProduct) > 0.7 ) // Threshold to consider as similar direction
270+ {
271+ isAlreadyPresent = true ;
272+ break ;
273+ }
274+ }
275+ if (!isAlreadyPresent)
276+ {
277+ principalAxes.push_back (sortedClustersBySize[i].second );
278+ }
279+ }
280+ }
281+ if (principalAxes.size () < 2 ) // Fallback to OBB if k-means fails to provide enough distinct axes
282+ {
283+ open3d::geometry::OrientedBoundingBox obb = this ->Cvt2O3DPointCloud ()->GetOrientedBoundingBox ();
284+ principalAxes = {obb.R_ .col (0 ), obb.R_ .col (1 ), obb.R_ .col (2 )};
285+ }
286+ return principalAxes;
287+ }
288+
289+ void DFPointCloud::Crop (const Eigen::Vector3d &minBound, const Eigen::Vector3d &maxBound)
290+ {
291+ auto O3DPointCloud = this ->Cvt2O3DPointCloud ();
292+ auto O3DPointCloudCropped = O3DPointCloud->Crop (open3d::geometry::AxisAlignedBoundingBox (minBound, maxBound));
293+ this ->Points .clear ();
294+ for (auto &point : O3DPointCloudCropped->points_ )
295+ this ->Points .push_back (point);
296+ this ->Colors .clear ();
297+ for (auto &color : O3DPointCloudCropped->colors_ )
298+ this ->Colors .push_back (color);
299+ this ->Normals .clear ();
300+ for (auto &normal : O3DPointCloudCropped->normals_ )
301+ this ->Normals .push_back (normal);
302+ }
303+
304+ void DFPointCloud::Crop (const std::vector<Eigen::Vector3d> &corners)
305+ {
306+ if (corners.size () != 8 )
307+ throw std::invalid_argument (" The corners vector must contain exactly 8 points." );
308+ open3d::geometry::OrientedBoundingBox obb = open3d::geometry::OrientedBoundingBox::CreateFromPoints (corners);
309+ auto O3DPointCloud = this ->Cvt2O3DPointCloud ();
310+ auto O3DPointCloudCropped = O3DPointCloud->Crop (obb);
311+ this ->Points .clear ();
312+ for (auto &point : O3DPointCloudCropped->points_ )
313+ this ->Points .push_back (point);
314+ this ->Colors .clear ();
315+ for (auto &color : O3DPointCloudCropped->colors_ )
316+ this ->Colors .push_back (color);
317+ this ->Normals .clear ();
318+ for (auto &normal : O3DPointCloudCropped->normals_ )
319+ this ->Normals .push_back (normal);
320+ }
321+
322+ DFPointCloud DFPointCloud::Duplicate () const
323+ {
324+ return DFPointCloud (this ->Points , this ->Colors , this ->Normals );
325+ }
326+
219327 void DFPointCloud::UniformDownsample (int everyKPoints)
220328 {
221329 auto O3DPointCloud = this ->Cvt2O3DPointCloud ();
@@ -258,6 +366,86 @@ namespace diffCheck::geometry
258366 return bboxPts;
259367 }
260368
369+ void DFPointCloud::SubtractPoints (const DFPointCloud &pointCloud, double distanceThreshold)
370+ {
371+ if (this ->Points .size () == 0 || pointCloud.Points .size () == 0 )
372+ throw std::invalid_argument (" One of the point clouds is empty." );
373+
374+ auto O3DSourcePointCloud = this ->Cvt2O3DPointCloud ();
375+ auto O3DTargetPointCloud = std::make_shared<DFPointCloud>(pointCloud)->Cvt2O3DPointCloud ();
376+ auto O3DResultPointCloud = std::make_shared<open3d::geometry::PointCloud>();
377+
378+ open3d::geometry::KDTreeFlann threeDTree;
379+ threeDTree.SetGeometry (*O3DTargetPointCloud);
380+ std::vector<int > indices;
381+ std::vector<double > distances;
382+ for (const auto &point : O3DSourcePointCloud->points_ )
383+ {
384+ threeDTree.SearchRadius (point, distanceThreshold, indices, distances);
385+ if (indices.empty ())
386+ {
387+ O3DResultPointCloud->points_ .push_back (point);
388+ if (O3DSourcePointCloud->HasColors ())
389+ {
390+ O3DResultPointCloud->colors_ .push_back (O3DSourcePointCloud->colors_ [&point - &O3DSourcePointCloud->points_ [0 ]]);
391+ }
392+ if (O3DSourcePointCloud->HasNormals ())
393+ {
394+ O3DResultPointCloud->normals_ .push_back (O3DSourcePointCloud->normals_ [&point - &O3DSourcePointCloud->points_ [0 ]]);
395+ }
396+ }
397+ }
398+ this ->Points .clear ();
399+ for (auto &point : O3DResultPointCloud->points_ )
400+ this ->Points .push_back (point);
401+ if (O3DResultPointCloud->HasColors ())
402+ {
403+ this ->Colors .clear ();
404+ for (auto &color : O3DResultPointCloud->colors_ ){this ->Colors .push_back (color);};
405+ }
406+ if (O3DResultPointCloud->HasNormals ())
407+ {
408+ this ->Normals .clear ();
409+ for (auto &normal : O3DResultPointCloud->normals_ ){this ->Normals .push_back (normal);};
410+ }
411+ }
412+
413+ diffCheck::geometry::DFPointCloud DFPointCloud::Intersect (const DFPointCloud &pointCloud, double distanceThreshold)
414+ {
415+ if (this ->Points .size () == 0 || pointCloud.Points .size () == 0 )
416+ throw std::invalid_argument (" One of the point clouds is empty." );
417+
418+ auto O3DSourcePointCloud = this ->Cvt2O3DPointCloud ();
419+ auto O3DTargetPointCloud = std::make_shared<DFPointCloud>(pointCloud)->Cvt2O3DPointCloud ();
420+ auto O3DResultPointCloud = std::make_shared<open3d::geometry::PointCloud>();
421+
422+ open3d::geometry::KDTreeFlann threeDTree;
423+ threeDTree.SetGeometry (*O3DTargetPointCloud);
424+ std::vector<int > indices;
425+ std::vector<double > distances;
426+ for (const auto &point : O3DSourcePointCloud->points_ )
427+ {
428+ threeDTree.SearchRadius (point, distanceThreshold, indices, distances);
429+ if (!indices.empty ())
430+ {
431+ O3DResultPointCloud->points_ .push_back (point);
432+ if (O3DSourcePointCloud->HasColors ())
433+ {
434+ O3DResultPointCloud->colors_ .push_back (O3DSourcePointCloud->colors_ [&point - &O3DSourcePointCloud->points_ [0 ]]);
435+ }
436+ if (O3DSourcePointCloud->HasNormals ())
437+ {
438+ O3DResultPointCloud->normals_ .push_back (O3DSourcePointCloud->normals_ [&point - &O3DSourcePointCloud->points_ [0 ]]);
439+ }
440+ }
441+ }
442+ diffCheck::geometry::DFPointCloud result;
443+ result.Points = O3DResultPointCloud->points_ ;
444+ result.Colors = O3DResultPointCloud->colors_ ;
445+ result.Normals = O3DResultPointCloud->normals_ ;
446+ return result;
447+ }
448+
261449 void DFPointCloud::ApplyTransformation (const diffCheck::transformation::DFTransformation &transformation)
262450 {
263451 auto O3DPointCloud = this ->Cvt2O3DPointCloud ();
0 commit comments