Skip to content

Cookbook

Point cloud editor

src/example/ext_pointcloud_editor.cpp

ext_pointcloud_editor.cpp
#include <glk/io/ply_io.hpp>
#include <glk/pointcloud_buffer.hpp>
#include <glk/indexed_pointcloud_buffer.hpp>
#include <glk/primitives/primitives.hpp>
#include <guik/model_control.hpp>
#include <guik/viewer/light_viewer.hpp>
#include <portable-file-dialogs.h>

class PointCloudEditor {
public:
  PointCloudEditor() {
    auto viewer = guik::LightViewer::instance();

    cube_matrix.reset(new guik::ModelControl("cube_matrix"));

    viewer->register_ui_callback("ui", [this] { ui_callback(); });
    viewer->spin();
  }

private:
  void ui_callback() {
    auto viewer = guik::LightViewer::instance();

    ImGui::Begin("control", nullptr, ImGuiWindowFlags_AlwaysAutoResize);

    // Load points    
    if (ImGui::Button("Load point cloud")) {
      for (const auto& filename : pfd::open_file("Select a PLY file").result()) {
        // Load points from PLY and add them to the point list
        auto ply = glk::load_ply(filename);
        if (ply) {
          points.insert(points.end(), ply->vertices.begin(), ply->vertices.end());
        }
      }

      // Show points on the viewer
      if (!points.empty()) {
        cloud_buffer = std::make_shared<glk::PointCloudBuffer>(points);
        viewer->update_drawable("points", cloud_buffer, guik::Rainbow());
      }
    }

    // Save points
    if (ImGui::Button("Save point cloud")) {
      auto path = pfd::save_file("Select a destination path to save PLY").result();
      if (!path.empty() && !points.empty()) {
        glk::save_ply_binary(path, points.data(), points.size());
      }
    }

    // Show the cube representing the filtering area
    ImGui::Separator();
    cube_matrix->draw_gizmo_ui();
    cube_matrix->draw_gizmo();
    viewer->update_drawable("cube", glk::Primitives::cube(), guik::FlatColor({1.0f, 0.5f, 0.0f, 0.5f}, cube_matrix->model_matrix()).make_transparent());

    // Find points in the filtering area
    if (ImGui::Button("Select points")) {
      // The inverse of the cube model matrix transforms world points in the cube coordinate system
      Eigen::Matrix4f inv_cube_matrix = cube_matrix->model_matrix().inverse();

      selected_points.clear();
      neg_selected_points.clear();
      for (int i = 0; i < points.size(); i++) {
        Eigen::Vector3f pt = (Eigen::Affine3f(inv_cube_matrix) * points[i]);
        // Points in [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] are inside of the cube
        if ((pt.array() > Eigen::Array3f::Constant(-0.5f)).all() && (pt.array() < Eigen::Array3f::Constant(0.5f)).all()) {
          selected_points.emplace_back(i);
        } else {
          neg_selected_points.emplace_back(i);
        }
      }

      // Show the selected points with large orange points
      viewer->update_drawable("selected", std::make_shared<glk::IndexedPointCloudBuffer>(cloud_buffer, selected_points), guik::FlatOrange().set_point_scale(2.0f));
    }

    // Remove the selected points from the point list
    if (ImGui::Button("Remove points")) {
      // Leave only "un"selected points
      std::vector<Eigen::Vector3f> filtered;
      std::transform(neg_selected_points.begin(), neg_selected_points.end(), std::back_inserter(filtered), [&](const auto i) { return points[i]; });
      points = std::move(filtered);

      selected_points.clear();
      neg_selected_points.clear();

      cloud_buffer = std::make_shared<glk::PointCloudBuffer>(points);
      viewer->update_drawable("points", cloud_buffer, guik::Rainbow());
      viewer->remove_drawable("selected");
    }

    ImGui::End();
  }

private:
  std::unique_ptr<guik::ModelControl> cube_matrix;      // Model matrix of the cube representing the filtering area

  std::vector<Eigen::Vector3f> points;                  // Point cloud
  std::shared_ptr<glk::PointCloudBuffer> cloud_buffer;  // CloudBuffer of points
  std::vector<unsigned int> selected_points;            // Selected points
  std::vector<unsigned int> neg_selected_points;        // Negative of selected points
};

int main(int argc, char** argv) {
  PointCloudEditor editor;
  return 0;
}
  • PLY file IO with portable-file-dialogs
  • Gizmo-based model matrix control
  • Point cloud rendering using PointCloudBuffer and IndexedPointCloudBuffer