@@ -216,6 +216,76 @@ 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+
219289 void DFPointCloud::Crop (const Eigen::Vector3d &minBound, const Eigen::Vector3d &maxBound)
220290 {
221291 auto O3DPointCloud = this ->Cvt2O3DPointCloud ();
0 commit comments