I have a PCL Point Cloud (in 3D) which I would like to turn into a ground surface TIN (2.5D) and then sample points (in 2D) to find their elevation when projected onto the TIN. To do this I've been using the CGAL Delaunay Triangulation classes, which has mostly been working pretty well!
I was able to implement this using a Delaunay_triangulation_2 built on a Triangulation_vertex_base_with_info_2 and create a nice-looking TIN. I also wrote a function which extracts the face and vertices for an arbitrary point in 2D space using the CGAL locate() function so that I can interpolate the point's height if it were projected onto the TIN. I need the info field to hold an index that lets me associate vertices within the triangulation back to points in the PCL point cloud structure.
However, when using the base Triangulation class the locate() function is slow (stochastic walk starting from an arbitrary vertex in the triangulation), and because I have to call this for every query point in the cloud to be interpolated (to estimate projected heights) this is currently the slowest part of my whole pipeline. So I looked into using the Triangulation Hierarchy class to make this more efficient.
I can't figure out how to make the Triangulation_hierarchy class work with a vertex base with info, and I think I'm just doing something silly wrong. Here is a miminal example showing my slow solution with a simple triangulation structure (no hierarchy), which does work:
#include <chrono>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/Delaunay_triangulation_2.h>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Triangulation_vertex_base_with_info_2<unsigned int, K> Vb;
typedef CGAL::Triangulation_data_structure_2<Vb> Tds;
typedef CGAL::Delaunay_triangulation_2<K, Tds> Delaunay;
typedef Delaunay::Point_2 CGALPoint;
typedef Delaunay::Face_handle Face_handle;
// This is templated on a PCL PointCloud Ptr - input cloud is basically a 3D vector of XYZ points
template <typename CloudType>
void delaunayTriangulation(CloudType input_cloud, Delaunay& triangulation)
{
std::cout << "Performing Delaunay triangulation on cloud of size " << input_cloud->points.size() << std::endl;
// Convert ground minima cloud to CGAL vector of points
std::vector< std::pair<CGALPoint, unsigned> > minima_vec;
for(std::size_t i=0; i<input_cloud->points.size(); i++)
{
minima_vec.push_back(std::make_pair(CGALPoint(input_cloud->points[i].x,input_cloud->points[i].y), i));
}
// Generate Delaunay Triangulation for ground minima
triangulation = Delaunay(minima_vec.begin(), minima_vec.end());
std::cout << " Number of vertices in Delaunay: " << triangulation.number_of_vertices() << std::endl;
std::cout << " Number of faces in Delaunay: " << triangulation.number_of_faces() << std::endl;
}
int main()
{
// Generate a starting point cloud with random points
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
for(int i=0; i<500; i++)
for(int j=0; j<500; j++)
{
// Generate points which are gridded + a bit of noise in XY, and random Z
pcl::PointXYZ point;
point.x = i + (std::rand()%100)/100.0;
point.y = j + (std::rand()%100)/100.0;
point.z = std::rand();
cloud->points.push_back(point);
}
// Get the ground triangulation
Delaunay triangulation;
delaunayTriangulation(cloud, triangulation);
// Locate the containing face for a bunch of random points
std::cout << "Starting to search for faces..." << std::endl;
auto start_time = std::chrono::high_resolution_clock::now();
for(int i=0; i<3000000; i++)
{
// Random point with X and Y between 0 and 500
CGALPoint test_point((std::rand() % 5000)/10.0, (std::rand() % 5000)/10.0);
Face_handle face = triangulation.locate(test_point);
// here we would do some math using the vertices located above
}
auto stop_time = std::chrono::high_resolution_clock::now();
float duration = std::chrono::duration_cast<std::chrono::microseconds>( stop_time - start_time ).count();
std::cout << "Finished searching for faces - took " << duration/1000000 << std::endl;
}
If I switch to using a Triangulation_hierarchy_2 object instead, built on top of my Delaunay_Triangulation_2 type, it won't let me insert point pairs which contain the info field - it will only compile if I build the object using a vector of points by themselves, instead:
#include <chrono>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_hierarchy_2.h>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Triangulation_vertex_base_with_info_2<unsigned int, K> Vbb;
typedef CGAL::Triangulation_hierarchy_vertex_base_2<Vbb> Vb;
typedef CGAL::Triangulation_data_structure_2<Vb> Tds;
typedef CGAL::Delaunay_triangulation_2<K, Tds> Delaunay;
typedef Delaunay::Point_2 CGALPoint;
typedef Delaunay::Face_handle Face_handle;
typedef CGAL::Triangulation_hierarchy_2<Delaunay> Delaunay_hierarchy;
// This is templated on a PCL PointCloud Ptr - input cloud is basically a 3D vector of XYZ points
template <typename CloudType>
void delaunayTriangulation(CloudType input_cloud, Delaunay_hierarchy& triangulation)
{
std::cout << "Performing Delaunay triangulation on cloud of size " << input_cloud->points.size() << std::endl;
// Convert ground minima cloud to CGAL vector of points
std::vector<CGALPoint> minima_vec_simple;
for(std::size_t i=0; i<input_cloud->points.size(); i++)
{
minima_vec_simple.push_back(CGALPoint(input_cloud->points[i].x,input_cloud->points[i].y));
}
// Generate Delaunay Triangulation for ground minima
triangulation = Delaunay_hierarchy(minima_vec_simple.begin(), minima_vec_simple.end());
std::cout << " Number of vertices in Delaunay: " << triangulation.number_of_vertices() << std::endl;
std::cout << " Number of faces in Delaunay: " << triangulation.number_of_faces() << std::endl;
}
int main()
{
// Generate a starting point cloud with random points
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
for(int i=0; i<500; i++)
for(int j=0; j<500; j++)
{
// Generate points which are gridded + a bit of noise in XY, and random Z
pcl::PointXYZ point;
point.x = i + (std::rand()%100)/100.0;
point.y = j + (std::rand()%100)/100.0;
point.z = std::rand();
cloud->points.push_back(point);
}
// Get the ground triangulation
Delaunay_hierarchy triangulation;
delaunayTriangulation(cloud, triangulation);
// Locate the containing face for a bunch of random points
std::cout << "Starting to search for faces..." << std::endl;
auto start_time = std::chrono::high_resolution_clock::now();
for(int i=0; i<3000000; i++)
{
// Random point with X and Y between 0 and 500
CGALPoint test_point((std::rand() % 5000)/10.0, (std::rand() % 5000)/10.0);
Face_handle face = triangulation.locate(test_point);
// here we would do some math using the vertices located above
}
auto stop_time = std::chrono::high_resolution_clock::now();
float duration = std::chrono::duration_cast<std::chrono::microseconds>( stop_time - start_time ).count();
std::cout << "Finished searching for faces - took " << duration/1000000 << std::endl;
}
All the layers of typedefs and templates in CGAL are a bit opaque to me - is there some way I can set these up instead which would allow me to build something like a Triangulation_hierarchy_2 based on a Triangulation_hierarchy_vertex_base_with_info_2? That second type doesn't look like it exists.
Thanks!