From c25638f27b2056216c80b280f2583e42525bf80d Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Tue, 12 Dec 2023 17:12:07 +0100 Subject: [PATCH 01/13] Move Application to dedicated source file --- CMakeLists.txt | 4 +- src/application.cpp | 249 ++++++++++++++++++++++++++++++++++++++ src/application.h | 50 ++++++++ src/main.cpp | 283 +------------------------------------------- 4 files changed, 304 insertions(+), 282 deletions(-) create mode 100644 src/application.cpp create mode 100644 src/application.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cb3c57d..8063745 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,11 +53,13 @@ endif() add_executable( poncaplot src/myview.h src/myview.cpp + src/application.h + src/application.cpp src/drawingPass.h src/drawingPasses/distanceField.h src/drawingPasses/poncaFitField.h src/main.cpp - ) +) # Include settings target_include_directories(poncaplot PUBLIC diff --git a/src/application.cpp b/src/application.cpp new file mode 100644 index 0000000..8666bde --- /dev/null +++ b/src/application.cpp @@ -0,0 +1,249 @@ +#include "application.h" + +#include "myview.h" +#include "drawingPass.h" +#include "drawingPasses/distanceField.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include // GLFW_KEY_ESCAPE and others + +using namespace nanogui; + +#define CONFIG_PONCA_FIT_INTERFACE(FitWidget,fit,name) \ + { \ + FitWidget = new nanogui::Widget(window); \ + FitWidget->set_layout(new GroupLayout()); \ + } + +const int tex_width = 500; +const int tex_height = 500; + +PoncaPlotApplication::PoncaPlotApplication() : Screen(Vector2i(1024, 768), "PoncaPlot"){ + + passDFWithKdTree = new DistanceFieldWithKdTree(); + passPlaneFit = new PlaneFitField(); + passSphereFit = new SphereFitField(); + passOrientedSphereFit = new OrientedSphereFitField(); + passUnorientedSphereFit = new UnorientedSphereFitField(); + + m_passes[0] = new FillPass( {255,255,255,255}); + m_passes[1] = passDFWithKdTree; + m_passes[2] = new DisplayPoint({255,0,0,255}); + + inc_ref(); + auto *window = new Window(this, "Controls"); + window->set_position(Vector2i(0, 0)); + window->set_layout(new GroupLayout()); + + + new nanogui::Label(window, "Select Fit Type", "sans-bold"); + auto combo =new nanogui::ComboBox(window, + { "Distance Field", + "Plane", + "Sphere", + "Oriented Sphere", + "Unoriented Sphere"}); + combo->set_callback([this](int id){ + switch (id) { + case 0: m_passes[1] = passDFWithKdTree; break; + case 1: m_passes[1] = passPlaneFit; break; + case 2: m_passes[1] = passSphereFit; break; + case 3: m_passes[1] = passOrientedSphereFit; break; + case 4: m_passes[1] = passUnorientedSphereFit; break; + default: throw std::runtime_error("Unknown Field type!"); + } + buildPassInterface(id); + renderPasses(); + }); + + + // create pass 1 interface + { + pass1Widget = new nanogui::Widget(window); + pass1Widget->set_layout(new GroupLayout()); + new nanogui::Label(pass1Widget, "Background filling", "sans-bold"); + new nanogui::Label(pass1Widget, "Color"); + // dunno why, but sets colorpicker in range [0-255], but reads in [0-1] + auto cp = new ColorPicker(pass1Widget, (dynamic_cast(m_passes[0]))->m_fillColor); + cp->set_final_callback([this](const Color &c) { + dynamic_cast(m_passes[0])->m_fillColor = 255.f * c; + renderPasses(); + }); + } + + // create configurable passes interface + { + distanceFieldWidget = new nanogui::Widget(window); + distanceFieldWidget->set_layout(new GroupLayout()); + new nanogui::Label(distanceFieldWidget, "Distance Field", "sans-bold"); + new nanogui::Label(distanceFieldWidget, "no parameter available"); + } + + { + genericFitWidget = new nanogui::Widget(window); + genericFitWidget->set_layout(new GroupLayout()); + new nanogui::Label(genericFitWidget, "Local Fitting", "sans-bold"); + new nanogui::Label(genericFitWidget, "Scale"); + auto slider = new Slider(genericFitWidget); + slider->set_value(passPlaneFit->m_scale); // init with plane, but sync with current. + slider->set_range({5,200}); + slider->set_callback([&](float value) { + passPlaneFit->m_scale = value; + passSphereFit->m_scale = value; + passOrientedSphereFit->m_scale = value; + passUnorientedSphereFit->m_scale = value; + renderPasses(); + }); + + new Label(genericFitWidget, "MLS Iterations :", "sans-bold"); + auto int_box = new IntBox(genericFitWidget, passPlaneFit->m_iter); + int_box->set_editable(true); + int_box->set_spinnable(true); + int_box->set_min_value(1); + int_box->set_max_value(10); + int_box->set_value_increment(1); + int_box->set_callback([&](int value) { + passPlaneFit->m_iter = value; + passSphereFit->m_iter = value; + passOrientedSphereFit->m_iter = value; + passUnorientedSphereFit->m_iter = value; + renderPasses(); + }); + } + + CONFIG_PONCA_FIT_INTERFACE(planeFitWidget,passPlaneFit,combo->items()[1]) + CONFIG_PONCA_FIT_INTERFACE(sphereFitWidget,passSphereFit,combo->items()[2]) + CONFIG_PONCA_FIT_INTERFACE(orientedSphereFitWidget,passOrientedSphereFit,combo->items()[3]) + CONFIG_PONCA_FIT_INTERFACE(unorientedSphereFitWidget,passUnorientedSphereFit,combo->items()[4]) + + // create pass 3 interface + { + pass3Widget = new nanogui::Widget(window); + pass3Widget->set_layout(new GroupLayout()); + new nanogui::Label(pass3Widget, "Points Display", "sans-bold"); + new nanogui::Label(pass3Widget, "Color"); + // dunno why, but sets colorpicker in range [0-255], but reads in [0-1] + auto cp = new ColorPicker(pass3Widget, (dynamic_cast(m_passes[2]))->m_pointColor); + cp->set_final_callback([this](const Color &c) { + dynamic_cast(m_passes[2])->m_pointColor = 255.f * c; + renderPasses(); + }); + auto slider = new Slider(pass3Widget); + slider->set_value(dynamic_cast(m_passes[2])->m_halfSize); + slider->set_range({1,20}); + slider->set_callback([&](float value) { + dynamic_cast(m_passes[2])->m_halfSize = int(value); + m_image_view->setSelectionThreshold(value); + renderPasses(); + }); + } + + window = new Window(this, "Image"); + window->set_position(Vector2i(200, 0)); + window->set_size(Vector2i(768,768)); + window->set_layout(new GroupLayout(3)); + + m_textureBufferPing = new uint8_t [tex_width * tex_height * 4]; // use UInt8 RGBA textures + m_textureBufferPong = new uint8_t [tex_width * tex_height * 4]; // use UInt8 RGBA textures + m_texture = new Texture( + Texture::PixelFormat::RGBA, + Texture::ComponentFormat::UInt8, + {tex_width,tex_height}, + Texture::InterpolationMode::Trilinear, + Texture::InterpolationMode::Nearest, + Texture::WrapMode::ClampToEdge); + + m_image_view = new MyView(window); + m_image_view->set_size(Vector2i(768, 768)); + m_image_view->set_image(m_texture ); + m_image_view->fitImage(); + m_image_view->center(); + m_image_view->setUpdateFunction([this](){ this->renderPasses();}); + + renderPasses(); // render twice to fill m_textureBufferPing and m_textureBufferPong + renderPasses(); + + // call perform_layout + buildPassInterface(0); +} + + +bool +PoncaPlotApplication::keyboard_event(int key, int scancode, int action, int modifiers) { + if (Screen::keyboard_event(key, scancode, action, modifiers)) + return true; + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { + set_visible(false); + return true; + } + return false; +} + +void +PoncaPlotApplication::draw(NVGcontext *ctx) { +// if (m_needUpdate) + Screen::draw(ctx); +} + +void +PoncaPlotApplication::draw_contents() { + if (m_needUpdate){ + m_texture->upload(m_computeInPing ? m_textureBufferPong : m_textureBufferPing); + m_needUpdate = false; + } + Screen::draw_contents(); +} + +void +PoncaPlotApplication::buildPassInterface(int id){ + distanceFieldWidget->set_visible(false); + genericFitWidget->set_visible(false); + planeFitWidget->set_visible(false); + sphereFitWidget->set_visible(false); + orientedSphereFitWidget->set_visible(false); + unorientedSphereFitWidget->set_visible(false); + switch (id) { + case 0: + distanceFieldWidget->set_visible(true); + break; + case 1: + genericFitWidget->set_visible(true); + planeFitWidget->set_visible(true); + break; + case 2: + genericFitWidget->set_visible(true); + sphereFitWidget->set_visible(true); + break; + case 3: + genericFitWidget->set_visible(true); + orientedSphereFitWidget->set_visible(true); + break; + case 4: + genericFitWidget->set_visible(true); + unorientedSphereFitWidget->set_visible(true); + break; + default: throw std::runtime_error("Unknown Field type!"); + } + perform_layout(); +} + +void +PoncaPlotApplication::renderPasses() { + std::cout << "[Main] Update texture" << std::endl; + const auto& points = m_image_view->getPointCollection(); + for (auto* p : m_passes) { + p->render(points, m_computeInPing ? m_textureBufferPing : m_textureBufferPong, tex_width, tex_height); + } + + m_computeInPing = ! m_computeInPing; + m_needUpdate = true; +} + diff --git a/src/application.h b/src/application.h new file mode 100644 index 0000000..fb81d43 --- /dev/null +++ b/src/application.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +// cannot forward declare aliases +#include "drawingPasses/poncaFitField.h" + +// forward declarations +class DrawingPass; +class DistanceFieldWithKdTree; +class MyView; + +namespace nanogui{ + class Texture; +} + + + +class PoncaPlotApplication : public nanogui::Screen { + +public: + PoncaPlotApplication(); + + bool keyboard_event(int key, int scancode, int action, int modifiers) override; + + void draw(NVGcontext *ctx) override; + + void draw_contents() override; + +private: + void buildPassInterface(int id); + + void renderPasses(); +private: + uint8_t* m_textureBufferPing {nullptr}, * m_textureBufferPong {nullptr}; + bool m_computeInPing{true}; + nanogui::Texture* m_texture {nullptr}; + std::array m_passes{nullptr, nullptr, nullptr}; + bool m_needUpdate{false}; + MyView *m_image_view {nullptr}; + Widget* pass1Widget, *distanceFieldWidget, + *genericFitWidget, //< parameters applicable to all fitting techniques + *planeFitWidget, *sphereFitWidget, *orientedSphereFitWidget, *unorientedSphereFitWidget, + *pass3Widget; + DistanceFieldWithKdTree *passDFWithKdTree; + PlaneFitField *passPlaneFit; + SphereFitField *passSphereFit; + OrientedSphereFitField *passOrientedSphereFit; + UnorientedSphereFitField *passUnorientedSphereFit; +}; diff --git a/src/main.cpp b/src/main.cpp index 41345d4..57fedd9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,295 +11,16 @@ BSD-style license that can be found in the LICENSE.txt file. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include -#include -#include - -#include "myview.h" -#include "drawingPass.h" -#include "drawingPasses/distanceField.h" -#include "drawingPasses/poncaFitField.h" - - -#define STB_IMAGE_STATIC -#define STB_IMAGE_IMPLEMENTATION -#if defined(_MSC_VER) -# pragma warning (disable: 4505) // don't warn about dead code in stb_image.h -#elif defined(__GNUC__) -# pragma GCC diagnostic ignored "-Wunused-function" -#endif -#include - -const int tex_width = 500; -const int tex_height = 500; - -using namespace nanogui; - -MyView *image_view {nullptr}; - - - -class ExampleApplication : public Screen { -private: - // We need to use a macro, to avoid the lambda to be defined in a local context -#define CONFIG_PONCA_FIT_INTERFACE(FitWidget,fit,name) \ - { \ - FitWidget = new nanogui::Widget(window); \ - FitWidget->set_layout(new GroupLayout()); \ - } - - - -public: - ExampleApplication() - : Screen(Vector2i(1024, 768), "PoncaPlot"){ - - passDFWithKdTree = new DistanceFieldWithKdTree(); - passPlaneFit = new PlaneFitField(); - passSphereFit = new SphereFitField(); - passOrientedSphereFit = new OrientedSphereFitField(); - passUnorientedSphereFit = new UnorientedSphereFitField(); - - m_passes[0] = new FillPass( {255,255,255,255}); - m_passes[1] = passDFWithKdTree; - m_passes[2] = new DisplayPoint({255,0,0,255}); - - inc_ref(); - auto *window = new Window(this, "Controls"); - window->set_position(Vector2i(0, 0)); - window->set_layout(new GroupLayout()); - - - new nanogui::Label(window, "Select Fit Type", "sans-bold"); - auto combo =new nanogui::ComboBox(window, - { "Distance Field", - "Plane", - "Sphere", - "Oriented Sphere", - "Unoriented Sphere"}); - combo->set_callback([this](int id){ - switch (id) { - case 0: m_passes[1] = passDFWithKdTree; break; - case 1: m_passes[1] = passPlaneFit; break; - case 2: m_passes[1] = passSphereFit; break; - case 3: m_passes[1] = passOrientedSphereFit; break; - case 4: m_passes[1] = passUnorientedSphereFit; break; - default: throw std::runtime_error("Unknown Field type!"); - } - buildPassInterface(id); - renderPasses(); - }); - - - // create pass 1 interface - { - pass1Widget = new nanogui::Widget(window); - pass1Widget->set_layout(new GroupLayout()); - new nanogui::Label(pass1Widget, "Background filling", "sans-bold"); - new nanogui::Label(pass1Widget, "Color"); - // dunno why, but sets colorpicker in range [0-255], but reads in [0-1] - auto cp = new ColorPicker(pass1Widget, (dynamic_cast(m_passes[0]))->m_fillColor); - cp->set_final_callback([this](const Color &c) { - dynamic_cast(m_passes[0])->m_fillColor = 255.f * c; - renderPasses(); - }); - } - - // create configurable passes interface - { - distanceFieldWidget = new nanogui::Widget(window); - distanceFieldWidget->set_layout(new GroupLayout()); - new nanogui::Label(distanceFieldWidget, "Distance Field", "sans-bold"); - new nanogui::Label(distanceFieldWidget, "no parameter available"); - } - - { - genericFitWidget = new nanogui::Widget(window); - genericFitWidget->set_layout(new GroupLayout()); - new nanogui::Label(genericFitWidget, "Local Fitting", "sans-bold"); - new nanogui::Label(genericFitWidget, "Scale"); - auto slider = new Slider(genericFitWidget); - slider->set_value(passPlaneFit->m_scale); // init with plane, but sync with current. - slider->set_range({5,200}); - slider->set_callback([&](float value) { - passPlaneFit->m_scale = value; - passSphereFit->m_scale = value; - passOrientedSphereFit->m_scale = value; - passUnorientedSphereFit->m_scale = value; - renderPasses(); - }); - new Label(genericFitWidget, "MLS Iterations :", "sans-bold"); - auto int_box = new IntBox(genericFitWidget, passPlaneFit->m_iter); - int_box->set_editable(true); - int_box->set_spinnable(true); - int_box->set_min_value(1); - int_box->set_max_value(10); - int_box->set_value_increment(1); - int_box->set_callback([&](int value) { - passPlaneFit->m_iter = value; - passSphereFit->m_iter = value; - passOrientedSphereFit->m_iter = value; - passUnorientedSphereFit->m_iter = value; - renderPasses(); - }); - } - - CONFIG_PONCA_FIT_INTERFACE(planeFitWidget,passPlaneFit,combo->items()[1]) - CONFIG_PONCA_FIT_INTERFACE(sphereFitWidget,passSphereFit,combo->items()[2]) - CONFIG_PONCA_FIT_INTERFACE(orientedSphereFitWidget,passOrientedSphereFit,combo->items()[3]) - CONFIG_PONCA_FIT_INTERFACE(unorientedSphereFitWidget,passUnorientedSphereFit,combo->items()[4]) - - // create pass 3 interface - { - pass3Widget = new nanogui::Widget(window); - pass3Widget->set_layout(new GroupLayout()); - new nanogui::Label(pass3Widget, "Points Display", "sans-bold"); - new nanogui::Label(pass3Widget, "Color"); - // dunno why, but sets colorpicker in range [0-255], but reads in [0-1] - auto cp = new ColorPicker(pass3Widget, (dynamic_cast(m_passes[2]))->m_pointColor); - cp->set_final_callback([this](const Color &c) { - dynamic_cast(m_passes[2])->m_pointColor = 255.f * c; - renderPasses(); - }); - auto slider = new Slider(pass3Widget); - slider->set_value(dynamic_cast(m_passes[2])->m_halfSize); - slider->set_range({1,20}); - slider->set_callback([&](float value) { - dynamic_cast(m_passes[2])->m_halfSize = int(value); - image_view->setSelectionThreshold(value); - renderPasses(); - }); - } - - window = new Window(this, "Image"); - window->set_position(Vector2i(200, 0)); - window->set_size(Vector2i(768,768)); - window->set_layout(new GroupLayout(3)); - - m_textureBufferPing = new uint8_t [tex_width * tex_height * 4]; // use UInt8 RGBA textures - m_textureBufferPong = new uint8_t [tex_width * tex_height * 4]; // use UInt8 RGBA textures - m_texture = new Texture( - Texture::PixelFormat::RGBA, - Texture::ComponentFormat::UInt8, - {tex_width,tex_height}, - Texture::InterpolationMode::Trilinear, - Texture::InterpolationMode::Nearest, - Texture::WrapMode::ClampToEdge); - - image_view = new MyView(window); - image_view->set_size(Vector2i(768,768)); - image_view->set_image( m_texture ); - image_view->fitImage(); - image_view->center(); - image_view->setUpdateFunction([this](){ this->renderPasses();}); - - renderPasses(); // render twice to fill m_textureBufferPing and m_textureBufferPong - renderPasses(); - - // call perform_layout - buildPassInterface(0); - } - - bool keyboard_event(int key, int scancode, int action, int modifiers) override { - if (Screen::keyboard_event(key, scancode, action, modifiers)) - return true; - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { - set_visible(false); - return true; - } - return false; - } - - void draw(NVGcontext *ctx) override { -// if (m_needUpdate) - Screen::draw(ctx); - } - - void draw_contents() override { - if (m_needUpdate){ - m_texture->upload(m_computeInPing ? m_textureBufferPong : m_textureBufferPing); - m_needUpdate = false; - } - Screen::draw_contents(); - } - - void buildPassInterface(int id){ - distanceFieldWidget->set_visible(false); - genericFitWidget->set_visible(false); - planeFitWidget->set_visible(false); - sphereFitWidget->set_visible(false); - orientedSphereFitWidget->set_visible(false); - unorientedSphereFitWidget->set_visible(false); - switch (id) { - case 0: - distanceFieldWidget->set_visible(true); - break; - case 1: - genericFitWidget->set_visible(true); - planeFitWidget->set_visible(true); - break; - case 2: - genericFitWidget->set_visible(true); - sphereFitWidget->set_visible(true); - break; - case 3: - genericFitWidget->set_visible(true); - orientedSphereFitWidget->set_visible(true); - break; - case 4: - genericFitWidget->set_visible(true); - unorientedSphereFitWidget->set_visible(true); - break; - default: throw std::runtime_error("Unknown Field type!"); - } - perform_layout(); - } - - void renderPasses() { - std::cout << "[Main] Update texture" << std::endl; - const auto& points = image_view->getPointCollection(); - for (auto* p : m_passes) { - p->render(points, m_computeInPing ? m_textureBufferPing : m_textureBufferPong, tex_width, tex_height); - } - - m_computeInPing = ! m_computeInPing; - m_needUpdate = true; - } -private: - uint8_t* m_textureBufferPing {nullptr}, * m_textureBufferPong {nullptr}; - bool m_computeInPing{true}; - Texture* m_texture {nullptr}; - std::array m_passes{nullptr, nullptr, nullptr}; - bool m_needUpdate{false}; - Widget* pass1Widget, *distanceFieldWidget, - *genericFitWidget, //< parameters applicable to all fitting techniques - *planeFitWidget, *sphereFitWidget, *orientedSphereFitWidget, *unorientedSphereFitWidget, - *pass3Widget; - DistanceFieldWithKdTree *passDFWithKdTree; - PlaneFitField *passPlaneFit; - SphereFitField *passSphereFit; - OrientedSphereFitField *passOrientedSphereFit; - UnorientedSphereFitField *passUnorientedSphereFit; -}; +#include "application.h" int main(int /* argc */, char ** /* argv */) { try { nanogui::init(); /* scoped variables */ { - ref app = new ExampleApplication(); + nanogui::ref app = new PoncaPlotApplication(); app->dec_ref(); app->draw_all(); app->set_visible(true); From cb0db4923cf516848d5613620c36c903f8e795a5 Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Wed, 13 Dec 2023 09:47:32 +0100 Subject: [PATCH 02/13] Fix compilation on windows --- src/main.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 57fedd9..547ce4c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,11 +30,7 @@ int main(int /* argc */, char ** /* argv */) { nanogui::shutdown(); } catch (const std::exception &e) { std::string error_msg = std::string("Caught a fatal error: ") + std::string(e.what()); - #if defined(_WIN32) - MessageBoxA(nullptr, error_msg.c_str(), NULL, MB_ICONERROR | MB_OK); - #else - std::cerr << error_msg << std::endl; - #endif + std::cerr << error_msg << std::endl; return -1; } catch (...) { std::cerr << "Caught an unknown error!" << std::endl; From 0db23fec3d5fa00a0f9f596c382d79fac236dd0b Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Wed, 13 Dec 2023 13:47:12 +0100 Subject: [PATCH 03/13] Move data storage to dedicated class DataMgr --- CMakeLists.txt | 1 + src/application.cpp | 11 +++--- src/application.h | 4 +++ src/dataManager.h | 56 +++++++++++++++++++++++++++++++ src/drawingPass.h | 12 +++---- src/drawingPasses/distanceField.h | 4 +-- src/drawingPasses/poncaFitField.h | 4 +-- src/myview.cpp | 38 +++++++++------------ src/myview.h | 36 ++------------------ 9 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 src/dataManager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8063745..09569d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ endif() # Create an executable add_executable( poncaplot + src/dataManager.h src/myview.h src/myview.cpp src/application.h diff --git a/src/application.cpp b/src/application.cpp index 8666bde..cde92e6 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,6 +1,7 @@ #include "application.h" #include "myview.h" +#include "dataManager.h" #include "drawingPass.h" #include "drawingPasses/distanceField.h" @@ -26,7 +27,10 @@ using namespace nanogui; const int tex_width = 500; const int tex_height = 500; -PoncaPlotApplication::PoncaPlotApplication() : Screen(Vector2i(1024, 768), "PoncaPlot"){ +PoncaPlotApplication::PoncaPlotApplication() : +Screen(Vector2i(1024, 768), "PoncaPlot"), m_dataMgr(new DataManager()){ + + m_dataMgr->setUpdateFunction([this](){ this->renderPasses();}); passDFWithKdTree = new DistanceFieldWithKdTree(); passPlaneFit = new PlaneFitField(); @@ -161,12 +165,11 @@ PoncaPlotApplication::PoncaPlotApplication() : Screen(Vector2i(1024, 768), "Ponc Texture::InterpolationMode::Nearest, Texture::WrapMode::ClampToEdge); - m_image_view = new MyView(window); + m_image_view = new MyView(window, m_dataMgr); m_image_view->set_size(Vector2i(768, 768)); m_image_view->set_image(m_texture ); m_image_view->fitImage(); m_image_view->center(); - m_image_view->setUpdateFunction([this](){ this->renderPasses();}); renderPasses(); // render twice to fill m_textureBufferPing and m_textureBufferPong renderPasses(); @@ -238,7 +241,7 @@ PoncaPlotApplication::buildPassInterface(int id){ void PoncaPlotApplication::renderPasses() { std::cout << "[Main] Update texture" << std::endl; - const auto& points = m_image_view->getPointCollection(); + const auto& points = m_dataMgr->getPointCollection(); for (auto* p : m_passes) { p->render(points, m_computeInPing ? m_textureBufferPing : m_textureBufferPong, tex_width, tex_height); } diff --git a/src/application.h b/src/application.h index fb81d43..7c4b0d5 100644 --- a/src/application.h +++ b/src/application.h @@ -9,6 +9,7 @@ class DrawingPass; class DistanceFieldWithKdTree; class MyView; +class DataManager; namespace nanogui{ class Texture; @@ -37,6 +38,9 @@ class PoncaPlotApplication : public nanogui::Screen { nanogui::Texture* m_texture {nullptr}; std::array m_passes{nullptr, nullptr, nullptr}; bool m_needUpdate{false}; + + DataManager *m_dataMgr{nullptr}; + MyView *m_image_view {nullptr}; Widget* pass1Widget, *distanceFieldWidget, *genericFitWidget, //< parameters applicable to all fitting techniques diff --git a/src/dataManager.h b/src/dataManager.h new file mode 100644 index 0000000..f108b13 --- /dev/null +++ b/src/dataManager.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include + +#include + +class DataPoint +{ +public: + enum {Dim = 2}; + using Scalar = float; + using VectorType = Eigen::Vector; + using MatrixType = Eigen::Matrix; + [[nodiscard]] inline const auto& pos() const {return m_pos;} + [[nodiscard]] inline const auto& normal() const {return m_normal;} + /// \fixme Use maps to avoid duplication + explicit inline DataPoint(const nanogui::Vector3f &pn) + : m_pos({pn.x(), pn.y()}), m_normal({std::cos(pn.z()), std::sin(pn.z())}) {} +private: + VectorType m_pos, m_normal; +}; + +/// Structure holding shared data +struct DataManager { +public: + using PointCollection = Ponca::KdTree; + using PointContainer = std::vector; // stores x,y,normal angle in radians + + /// Read access to point collection + inline const PointCollection& getPointCollection() const { return m_tree; } + + /// Update point collection from point container + inline void updatePointCollection() { + if(m_points.empty()) m_tree.clear(); + else m_tree.build(m_points ); + m_updateFunction(); + } + + /// Read access to point container + inline const PointContainer& getPointContainer() const { return m_points; } + + /// Read access to point container + /// \warning + inline PointContainer& getPointContainer() { return m_points; } + + /// Set Update function, called after each point update + inline void setUpdateFunction(std::function &&f) { m_updateFunction = f; } + +private: + PointContainer m_points; + PointCollection m_tree; + std::function m_updateFunction {[](){}}; +}; + diff --git a/src/drawingPass.h b/src/drawingPass.h index 984795a..a7e6ab4 100644 --- a/src/drawingPass.h +++ b/src/drawingPass.h @@ -3,19 +3,19 @@ #include #include -#include "myview.h" +#include "dataManager.h" /// Base class to rendering processes struct DrawingPass { - virtual void render(const MyView::PointCollection& points, uint8_t*buffer, int w, int h) = 0; + virtual void render(const DataManager::PointCollection& points, uint8_t*buffer, int w, int h) = 0; virtual ~DrawingPass() = default; }; struct FillPass : public DrawingPass { inline explicit FillPass(const nanogui::Vector4i &fillColor = {255,255,255,255}) : m_fillColor(fillColor) {} - void render(const MyView::PointCollection& /*points*/, uint8_t*buffer, int w, int h) override{ + void render(const DataManager::PointCollection& /*points*/, uint8_t*buffer, int w, int h) override{ #pragma omp parallel for default(none) shared(buffer, w, h) for(auto j = 0; j >; diff --git a/src/myview.cpp b/src/myview.cpp index 0bf3928..dddda61 100644 --- a/src/myview.cpp +++ b/src/myview.cpp @@ -9,7 +9,7 @@ using namespace nanogui; -MyView::MyView(nanogui::Widget *parent) : ImageView(parent) { +MyView::MyView(nanogui::Widget *parent, DataManager* mgr) : ImageView(parent), m_dataMgr(mgr) { std::cout<< "Controls:\n" << " ctrl+mouse move: move view\n" << " scroll: zoom in/out\n" @@ -43,10 +43,11 @@ MyView::fitImage() { //#define USE_KDTREE int MyView::findPointId(const Vector2f &lp) const{ - if(! m_points.empty()) { + const auto& points = m_dataMgr->getPointContainer(); + if(! points.empty()) { int i = 0; #ifndef USE_KDTREE - for(const auto&p : m_points) { + for(const auto&p : points) { Vector2f query(p.x(), p.y()); if (norm((lp - query)) <= m_selectionThreshold) return i; @@ -54,9 +55,10 @@ MyView::findPointId(const Vector2f &lp) const{ } #else DataPoint::VectorType query(lp.x(), lp.y()); - auto res = m_tree.nearest_neighbor(query); + const auto& tree = m_dataMgr->getPointCollection(); + auto res = tree.nearest_neighbor(query); if (res.begin() != res.end() && - (query-m_tree.point_data()[res.get()].pos()).norm()getPointContainer().emplace_back(lp.x(), lp.y(), M_PI / 2.); + m_dataMgr->updatePointCollection(); } } else { m_movedPoint = pointId; @@ -97,13 +99,14 @@ MyView::mouse_drag_event(const nanogui::Vector2i &p, const nanogui::Vector2i &re if (isInsideImage(lp)) { if (m_movedPoint>=0) { + auto& points = m_dataMgr->getPointContainer(); switch (button) { case 1: //left click // if is on a point // std::cout << "Move point by [" << rel << "]" << std::endl; - m_points[m_movedPoint].x() = lp.x(); - m_points[m_movedPoint].y() = lp.y(); - updateCollection(); + points[m_movedPoint].x() = lp.x(); + points[m_movedPoint].y() = lp.y(); + m_dataMgr->updatePointCollection(); break; case 2: //right click { @@ -111,9 +114,9 @@ MyView::mouse_drag_event(const nanogui::Vector2i &p, const nanogui::Vector2i &re if (rel.x() < 0) dist *=-1; // if is on a point auto relAngle = std::asin(float(dist) / 50.1f); // move by 40px to get 90 degree angle - m_points[m_movedPoint].z() += relAngle; + points[m_movedPoint].z() += relAngle; // std::cout << "Change normal by " << rel << ". Gives angle " << m_points[m_movedPoint].z() << std::endl; - updateCollection(); + m_dataMgr->updatePointCollection(); } break; default: @@ -123,14 +126,3 @@ MyView::mouse_drag_event(const nanogui::Vector2i &p, const nanogui::Vector2i &re } return true; } - -void -MyView::updateCollection() { - // recompute KdTree - if(m_points.empty()) - m_tree.clear(); - else - m_tree.build(m_points ); - - m_updateFunction(); -} diff --git a/src/myview.h b/src/myview.h index 9472547..e089c75 100644 --- a/src/myview.h +++ b/src/myview.h @@ -3,35 +3,16 @@ #include #include -#include +#include "dataManager.h" #include #include #include -class DataPoint -{ -public: - enum {Dim = 2}; - using Scalar = float; - using VectorType = Eigen::Vector; - using MatrixType = Eigen::Matrix; - [[nodiscard]] inline const auto& pos() const {return m_pos;} - [[nodiscard]] inline const auto& normal() const {return m_normal;} - /// \fixme Use maps to avoid duplication - explicit inline DataPoint(const nanogui::Vector3f &pn) - : m_pos({pn.x(), pn.y()}), m_normal({std::cos(pn.z()), std::sin(pn.z())}) {} -private: - VectorType m_pos, m_normal; -}; - - class MyView : public nanogui::ImageView { public: - using PointCollection = Ponca::KdTree; - /// Initialize the widget - explicit MyView(Widget *parent); + explicit MyView(Widget *parent, DataManager* mgr); // Check if a point is inside the image bool isInsideImage(const nanogui::Vector2f &lp) const; @@ -53,22 +34,11 @@ class MyView : public nanogui::ImageView { /// Disable scrolling inline bool scroll_event(const nanogui::Vector2i &p, const nanogui::Vector2f &rel) override {return true;} - /// Read access to point collection - inline const PointCollection& getPointCollection() const { return m_tree; } - - /// Set Update function, called after each point update - inline void setUpdateFunction(std::function &&f) { m_updateFunction = f; } - /// Set selection threshold inline void setSelectionThreshold(float dist) { m_selectionThreshold = dist; } private: - inline void updateCollection(); - - using PointContainer = std::vector; // stores x,y,normal angle in radians - PointContainer m_points; - PointCollection m_tree; - std::function m_updateFunction {[](){}}; int m_movedPoint{-1}; float m_selectionThreshold{2}; // distance in pixel used to select points + DataManager* m_dataMgr{nullptr}; }; From af56eb70f517172152e92a7528c7646c488f5f7b Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Wed, 13 Dec 2023 13:52:01 +0100 Subject: [PATCH 04/13] Rename DataManager methods for clarity --- src/application.cpp | 4 ++-- src/dataManager.h | 10 +++++----- src/drawingPass.h | 10 +++++----- src/drawingPasses/distanceField.h | 4 ++-- src/drawingPasses/poncaFitField.h | 4 ++-- src/myview.cpp | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/application.cpp b/src/application.cpp index cde92e6..69b6480 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -30,7 +30,7 @@ const int tex_height = 500; PoncaPlotApplication::PoncaPlotApplication() : Screen(Vector2i(1024, 768), "PoncaPlot"), m_dataMgr(new DataManager()){ - m_dataMgr->setUpdateFunction([this](){ this->renderPasses();}); + m_dataMgr->setKdTreePostUpdateFunction([this]() { this->renderPasses(); }); passDFWithKdTree = new DistanceFieldWithKdTree(); passPlaneFit = new PlaneFitField(); @@ -241,7 +241,7 @@ PoncaPlotApplication::buildPassInterface(int id){ void PoncaPlotApplication::renderPasses() { std::cout << "[Main] Update texture" << std::endl; - const auto& points = m_dataMgr->getPointCollection(); + const auto& points = m_dataMgr->getKdTree(); for (auto* p : m_passes) { p->render(points, m_computeInPing ? m_textureBufferPing : m_textureBufferPong, tex_width, tex_height); } diff --git a/src/dataManager.h b/src/dataManager.h index f108b13..dd0ccdf 100644 --- a/src/dataManager.h +++ b/src/dataManager.h @@ -25,14 +25,14 @@ class DataPoint /// Structure holding shared data struct DataManager { public: - using PointCollection = Ponca::KdTree; + using KdTree = Ponca::KdTree; using PointContainer = std::vector; // stores x,y,normal angle in radians /// Read access to point collection - inline const PointCollection& getPointCollection() const { return m_tree; } + inline const KdTree& getKdTree() const { return m_tree; } /// Update point collection from point container - inline void updatePointCollection() { + inline void updateKdTree() { if(m_points.empty()) m_tree.clear(); else m_tree.build(m_points ); m_updateFunction(); @@ -46,11 +46,11 @@ struct DataManager { inline PointContainer& getPointContainer() { return m_points; } /// Set Update function, called after each point update - inline void setUpdateFunction(std::function &&f) { m_updateFunction = f; } + inline void setKdTreePostUpdateFunction(std::function &&f) { m_updateFunction = f; } private: PointContainer m_points; - PointCollection m_tree; + KdTree m_tree; std::function m_updateFunction {[](){}}; }; diff --git a/src/drawingPass.h b/src/drawingPass.h index a7e6ab4..b8e9d49 100644 --- a/src/drawingPass.h +++ b/src/drawingPass.h @@ -8,14 +8,14 @@ /// Base class to rendering processes struct DrawingPass { - virtual void render(const DataManager::PointCollection& points, uint8_t*buffer, int w, int h) = 0; + virtual void render(const DataManager::KdTree& points, uint8_t*buffer, int w, int h) = 0; virtual ~DrawingPass() = default; }; struct FillPass : public DrawingPass { inline explicit FillPass(const nanogui::Vector4i &fillColor = {255,255,255,255}) : m_fillColor(fillColor) {} - void render(const DataManager::PointCollection& /*points*/, uint8_t*buffer, int w, int h) override{ + void render(const DataManager::KdTree& /*points*/, uint8_t*buffer, int w, int h) override{ #pragma omp parallel for default(none) shared(buffer, w, h) for(auto j = 0; j >; diff --git a/src/myview.cpp b/src/myview.cpp index dddda61..0e9b6ce 100644 --- a/src/myview.cpp +++ b/src/myview.cpp @@ -55,7 +55,7 @@ MyView::findPointId(const Vector2f &lp) const{ } #else DataPoint::VectorType query(lp.x(), lp.y()); - const auto& tree = m_dataMgr->getPointCollection(); + const auto& tree = m_dataMgr->getKdTree(); auto res = tree.nearest_neighbor(query); if (res.begin() != res.end() && (query-tree.point_data()[res.get()].pos()).norm()getPointContainer().emplace_back(lp.x(), lp.y(), M_PI / 2.); - m_dataMgr->updatePointCollection(); + m_dataMgr->updateKdTree(); } } else { m_movedPoint = pointId; @@ -106,7 +106,7 @@ MyView::mouse_drag_event(const nanogui::Vector2i &p, const nanogui::Vector2i &re // std::cout << "Move point by [" << rel << "]" << std::endl; points[m_movedPoint].x() = lp.x(); points[m_movedPoint].y() = lp.y(); - m_dataMgr->updatePointCollection(); + m_dataMgr->updateKdTree(); break; case 2: //right click { @@ -116,7 +116,7 @@ MyView::mouse_drag_event(const nanogui::Vector2i &p, const nanogui::Vector2i &re auto relAngle = std::asin(float(dist) / 50.1f); // move by 40px to get 90 degree angle points[m_movedPoint].z() += relAngle; // std::cout << "Change normal by " << rel << ". Gives angle " << m_points[m_movedPoint].z() << std::endl; - m_dataMgr->updatePointCollection(); + m_dataMgr->updateKdTree(); } break; default: From 62da17b451328a19cbfeba0d3cd9d1d209c5f245 Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Thu, 14 Dec 2023 16:31:10 +0100 Subject: [PATCH 05/13] Add file loading/saving --- CMakeLists.txt | 1 + src/application.cpp | 25 +++++++++++++++++ src/dataManager.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++++ src/dataManager.h | 18 +++++++++++++ src/myview.cpp | 6 +---- 5 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 src/dataManager.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 09569d3..c942a20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ endif() # Create an executable add_executable( poncaplot src/dataManager.h + src/dataManager.cpp src/myview.h src/myview.cpp src/application.h diff --git a/src/application.cpp b/src/application.cpp index 69b6480..61d45a2 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -47,6 +47,31 @@ Screen(Vector2i(1024, 768), "PoncaPlot"), m_dataMgr(new DataManager()){ window->set_position(Vector2i(0, 0)); window->set_layout(new GroupLayout()); + // IO + { + new Label(window, "File dialog", "sans-bold"); + auto *tools = new Widget(window); + tools->set_layout(new BoxLayout(Orientation::Vertical, + Alignment::Middle, 0, 6)); + auto *b = new Button(tools, "Open point cloud"); + b->set_callback([&] { + auto path = file_dialog( + {{"dat", "Text file x y nx y"}, + {"txt", "Text file x y nx y"}}, false); + std::cout << "Load file from: " << path << std::endl; + m_dataMgr->loadPointCloud(path); + }); + b = new Button(tools, "Save point cloud"); + b->set_callback([&] { + auto path = file_dialog( + {{"dat", "Text file x y nx y"}, + {"txt", "Text file x y nx y"}}, true); + std::cout << "Save file to: " << path << std::endl; + m_dataMgr->savePointCloud(path); + }); + } + + new nanogui::Label(window, "Select Fit Type", "sans-bold"); auto combo =new nanogui::ComboBox(window, diff --git a/src/dataManager.cpp b/src/dataManager.cpp new file mode 100644 index 0000000..3e0de1c --- /dev/null +++ b/src/dataManager.cpp @@ -0,0 +1,66 @@ +#include "dataManager.h" + +#include +#include + +bool +DataManager::savePointCloud(const std::string& path) const{ + if( path.empty() ) return false; + + std::ofstream file; + file.open (path); + + if( ! file.is_open() ) return false; + + file << "# x y nx ny " << "\n"; + for( const auto & p : m_tree.point_data() ){ + file << p.pos().transpose() << " " << p.normal().transpose() << "\n"; + } + file.close(); + return true; +} +bool +DataManager::loadPointCloud(const std::string& path){ + if( path.empty() ) return false; + + std::ifstream file; + file.open (path); + + if( ! file.is_open() ) return false; + + m_points.clear(); + + std::string line; + std::vector numbers; + while ( getline (file,line) ) + { + // trim comments + std::size_t found = line.find('#'); + if (found!=std::string::npos) + line = line.substr(0,found); + + if ( ! line.empty() ) { + numbers.clear(); + std::istringstream is(line); + numbers.assign(std::istream_iterator(is), std::istream_iterator()); + + if (numbers.size() == 2){ // loaded x-y only, set normal to default value + m_points.emplace_back(numbers[0], numbers[1], DEFAULT_POINT_ANGLE); + } else if (numbers.size() == 4){ // loaded x-y only, set normal to default value + m_points.emplace_back(numbers[0], numbers[1], std::acos(numbers[2])); + } else { // malformed line + std::cerr << "Skipping malformed line: [" << line << "]" << std::endl; + } + } + } + + file.close(); + updateKdTree(); + return true; +} + +void +DataManager::fitPointCloudToRange(const std::pair& rangesEnd, const std::pair& rangesStart){ + +} + diff --git a/src/dataManager.h b/src/dataManager.h index dd0ccdf..dab000d 100644 --- a/src/dataManager.h +++ b/src/dataManager.h @@ -1,6 +1,8 @@ #pragma once +#include //pair #include +#include #include @@ -22,6 +24,12 @@ class DataPoint VectorType m_pos, m_normal; }; +#ifndef M_PI +// Source: http://www.geom.uiuc.edu/~huberty/math5337/groupe/digits.html +#define M_PI 3.141592653589793238462643383279502884197169399375105820974944592307816406 +#endif +#define DEFAULT_POINT_ANGLE M_PI / 2. + /// Structure holding shared data struct DataManager { public: @@ -48,6 +56,16 @@ struct DataManager { /// Set Update function, called after each point update inline void setKdTreePostUpdateFunction(std::function &&f) { m_updateFunction = f; } + /// IO: save current point cloud to file + bool savePointCloud(const std::string& path) const; + + /// IO: load point cloud from file + bool loadPointCloud(const std::string& path); + + /// Utils: fit point cloud to coordinates ranges + void fitPointCloudToRange(const std::pair& rangesEnd, + const std::pair& rangesStart = {0,0}); + private: PointContainer m_points; KdTree m_tree; diff --git a/src/myview.cpp b/src/myview.cpp index 0e9b6ce..c240db1 100644 --- a/src/myview.cpp +++ b/src/myview.cpp @@ -2,10 +2,6 @@ #include -#ifndef M_PI -// Source: http://www.geom.uiuc.edu/~huberty/math5337/groupe/digits.html -#define M_PI 3.141592653589793238462643383279502884197169399375105820974944592307816406 -#endif using namespace nanogui; @@ -77,7 +73,7 @@ MyView::mouse_button_event(const Vector2i &p, int button, bool down, int modifie if (pointId < 0) { if(button == 0) { // create new point iif left click (button id seems to be different wrt drag event std::cout << "MyView::add new point" << std::endl; - m_dataMgr->getPointContainer().emplace_back(lp.x(), lp.y(), M_PI / 2.); + m_dataMgr->getPointContainer().emplace_back(lp.x(), lp.y(), DEFAULT_POINT_ANGLE); m_dataMgr->updateKdTree(); } } else { From 2b0e802ace20df5ef4db59499ea1ec83605ddb7b Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Thu, 14 Dec 2023 16:31:36 +0100 Subject: [PATCH 06/13] Attempt to use custom node type with Ponca Kdtree --- src/dataManager.h | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/dataManager.h b/src/dataManager.h index dab000d..40c74d0 100644 --- a/src/dataManager.h +++ b/src/dataManager.h @@ -3,6 +3,7 @@ #include //pair #include #include +#include #include @@ -30,10 +31,39 @@ class DataPoint #endif #define DEFAULT_POINT_ANGLE M_PI / 2. +template > +struct MyKdTreeInnerNode : public Ponca::KdTreeDefaultInnerNode { + using AabbType = _AabbType; + AabbType m_aabb{}; +}; +// +//template +//using MyKdTreeNode = Ponca::KdTreeCustomizableNode >; + +template +struct MyKdTreeNode : Ponca::KdTreeCustomizableNode> { + + using Base = Ponca::KdTreeCustomizableNode>; + using AabbType = typename Base::AabbType; + + void configure_range(Index start, Index size, const AabbType &aabb) + { + Base::configure_range(start, size, aabb); + if (! Base::is_leaf() ) + { + Base::getAsInner().m_aabb = aabb; + } + } +}; + /// Structure holding shared data struct DataManager { public: - using KdTree = Ponca::KdTree; +// using KdTree = Ponca::KdTree; + using KdTree = Ponca::KdTreeBase>; using PointContainer = std::vector; // stores x,y,normal angle in radians /// Read access to point collection From b74fed592f684f3cd59ff7961bf66ab56cf5f938 Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Thu, 14 Dec 2023 21:44:17 +0100 Subject: [PATCH 07/13] Implement DataManager::fitPointCloudToRange --- src/dataManager.cpp | 15 ++++++++++++++- src/dataManager.h | 8 ++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/dataManager.cpp b/src/dataManager.cpp index 3e0de1c..9bfcaff 100644 --- a/src/dataManager.cpp +++ b/src/dataManager.cpp @@ -61,6 +61,19 @@ DataManager::loadPointCloud(const std::string& path){ void DataManager::fitPointCloudToRange(const std::pair& rangesEnd, const std::pair& rangesStart){ - + if (m_points.empty()) return; + if (m_tree.node_count() == 0) updateKdTree(); + auto aabb = m_tree.node_data()[0].getAabb(); + if (aabb){ + VectorType requestedSize {rangesEnd.first - rangesStart.first, rangesEnd.second - rangesStart.second}; + VectorType scaleFactors = requestedSize.array() / aabb->diagonal().array(); + float scale = scaleFactors.minCoeff(); + for (auto& p : m_points) + { + p.x() *= scale; + p.y() *= scale; + } + updateKdTree(); + } } diff --git a/src/dataManager.h b/src/dataManager.h index 40c74d0..7d1c366 100644 --- a/src/dataManager.h +++ b/src/dataManager.h @@ -1,5 +1,6 @@ #pragma once +#include #include //pair #include #include @@ -57,6 +58,12 @@ struct MyKdTreeNode : Ponca::KdTreeCustomizableNode getAabb() const { + if (! Base::is_leaf()) + return Base::getAsInner().m_aabb; + else + return std::optional(); + } }; /// Structure holding shared data @@ -65,6 +72,7 @@ struct DataManager { // using KdTree = Ponca::KdTree; using KdTree = Ponca::KdTreeBase>; using PointContainer = std::vector; // stores x,y,normal angle in radians + using VectorType = typename KdTree::VectorType; /// Read access to point collection inline const KdTree& getKdTree() const { return m_tree; } From 20391d01a08cf2849922d3496c67bc83223113a4 Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Thu, 14 Dec 2023 21:44:27 +0100 Subject: [PATCH 08/13] Update GUI to call fitPointCloudToRange --- src/application.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/application.cpp b/src/application.cpp index 61d45a2..b9a4c7a 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -43,13 +43,12 @@ Screen(Vector2i(1024, 768), "PoncaPlot"), m_dataMgr(new DataManager()){ m_passes[2] = new DisplayPoint({255,0,0,255}); inc_ref(); - auto *window = new Window(this, "Controls"); + auto *window = new Window(this, "Utils"); window->set_position(Vector2i(0, 0)); window->set_layout(new GroupLayout()); // IO { - new Label(window, "File dialog", "sans-bold"); auto *tools = new Widget(window); tools->set_layout(new BoxLayout(Orientation::Vertical, Alignment::Middle, 0, 6)); @@ -69,8 +68,16 @@ Screen(Vector2i(1024, 768), "PoncaPlot"), m_dataMgr(new DataManager()){ std::cout << "Save file to: " << path << std::endl; m_dataMgr->savePointCloud(path); }); + b = new Button(tools, "Fit point cloud view"); + b->set_callback([&] { + const int bordersize = 20; + m_dataMgr->fitPointCloudToRange({tex_width-bordersize, tex_height-bordersize},{bordersize,bordersize}); + }); } + window = new Window(this, "Fitting Controls"); + window->set_position(Vector2i(0, 200)); + window->set_layout(new GroupLayout()); new nanogui::Label(window, "Select Fit Type", "sans-bold"); From 25b06bd60bfcad06d05456d8b4e05a91ce81625a Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Thu, 14 Dec 2023 22:39:06 +0100 Subject: [PATCH 09/13] Move ponca types to dedicated header file --- CMakeLists.txt | 1 + src/dataManager.h | 17 ++--------------- src/drawingPasses/poncaFitField.h | 16 ++++++---------- src/poncaTypes.h | 27 +++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 25 deletions(-) create mode 100644 src/poncaTypes.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c942a20..19f7b04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ add_executable( poncaplot src/myview.cpp src/application.h src/application.cpp + src/poncaTypes.h src/drawingPass.h src/drawingPasses/distanceField.h src/drawingPasses/poncaFitField.h diff --git a/src/dataManager.h b/src/dataManager.h index 7d1c366..78bc424 100644 --- a/src/dataManager.h +++ b/src/dataManager.h @@ -10,21 +10,8 @@ #include -class DataPoint -{ -public: - enum {Dim = 2}; - using Scalar = float; - using VectorType = Eigen::Vector; - using MatrixType = Eigen::Matrix; - [[nodiscard]] inline const auto& pos() const {return m_pos;} - [[nodiscard]] inline const auto& normal() const {return m_normal;} - /// \fixme Use maps to avoid duplication - explicit inline DataPoint(const nanogui::Vector3f &pn) - : m_pos({pn.x(), pn.y()}), m_normal({std::cos(pn.z()), std::sin(pn.z())}) {} -private: - VectorType m_pos, m_normal; -}; +#include "poncaTypes.h" + #ifndef M_PI // Source: http://www.geom.uiuc.edu/~huberty/math5337/groupe/digits.html diff --git a/src/drawingPasses/poncaFitField.h b/src/drawingPasses/poncaFitField.h index 0e130d1..2839c41 100644 --- a/src/drawingPasses/poncaFitField.h +++ b/src/drawingPasses/poncaFitField.h @@ -1,7 +1,7 @@ #pragma once -#include "../drawingPass.h" -#include +#include "../drawingPass.h" +#include "../poncaTypes.h" struct BaseFitField : public DrawingPass{ inline explicit BaseFitField() : DrawingPass() {} @@ -61,11 +61,7 @@ struct FitField : public BaseFitField { } }; -using PointType = typename DataManager::KdTree::DataPoint; -using Scalar = typename PointType::Scalar; -using WeightFunc = Ponca::DistWeightFunc >; - -using PlaneFitField = FitField>; -using SphereFitField = FitField>; -using OrientedSphereFitField = FitField>; -using UnorientedSphereFitField = FitField>; +using PlaneFitField = FitField; +using SphereFitField = FitField; +using OrientedSphereFitField = FitField; +using UnorientedSphereFitField = FitField; diff --git a/src/poncaTypes.h b/src/poncaTypes.h new file mode 100644 index 0000000..b9b0873 --- /dev/null +++ b/src/poncaTypes.h @@ -0,0 +1,27 @@ +#pragma once + +#include + + +class DataPoint +{ +public: + enum {Dim = 2}; + using Scalar = float; + using VectorType = Eigen::Vector; + using MatrixType = Eigen::Matrix; + [[nodiscard]] inline const auto& pos() const {return m_pos;} + [[nodiscard]] inline const auto& normal() const {return m_normal;} + /// \fixme Use maps to avoid duplication + explicit inline DataPoint(const nanogui::Vector3f &pn) + : m_pos({pn.x(), pn.y()}), m_normal({std::cos(pn.z()), std::sin(pn.z())}) {} +private: + VectorType m_pos, m_normal; +}; + +using WeightFunc = Ponca::DistWeightFunc >; + +using PlaneFit = Ponca::Basket; +using SphereFit = Ponca::Basket; +using OrientedSphereFit= Ponca::Basket; +using UnorientedSphereFit = Ponca::Basket; \ No newline at end of file From 2b6b23247856b7b52858f635b222e0c7831c419c Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Thu, 14 Dec 2023 22:41:31 +0100 Subject: [PATCH 10/13] Compute normals for point clouds without normals --- src/dataManager.cpp | 31 ++++++++++++++++++++++++++++++- src/dataManager.h | 4 ++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/dataManager.cpp b/src/dataManager.cpp index 9bfcaff..5f24ea9 100644 --- a/src/dataManager.cpp +++ b/src/dataManager.cpp @@ -32,6 +32,9 @@ DataManager::loadPointCloud(const std::string& path){ std::string line; std::vector numbers; + + bool needToComputeNormals = false; + while ( getline (file,line) ) { // trim comments @@ -46,6 +49,7 @@ DataManager::loadPointCloud(const std::string& path){ if (numbers.size() == 2){ // loaded x-y only, set normal to default value m_points.emplace_back(numbers[0], numbers[1], DEFAULT_POINT_ANGLE); + needToComputeNormals = true; } else if (numbers.size() == 4){ // loaded x-y only, set normal to default value m_points.emplace_back(numbers[0], numbers[1], std::acos(numbers[2])); } else { // malformed line @@ -53,12 +57,37 @@ DataManager::loadPointCloud(const std::string& path){ } } } - file.close(); updateKdTree(); + + // Use plane fit to compute unoriented normals. Use knn and constant weights + if(needToComputeNormals) computeNormals(); + return true; } +void +DataManager::computeNormals(int k){ + using WeightFunc = Ponca::DistWeightFunc >; + using PlaneFit = Ponca::Basket; + + std::cout << "Recompute normals" << std::endl; + + for (auto& pp : m_points){ + VectorType p {pp.x(),pp.y()}; + PlaneFit fit; + fit.setWeightFunc(WeightFunc()); + // Set the evaluation position + fit.init(p); + // Fit plane (method compute handles multipass fitting + if (fit.computeWithIds(m_tree.k_nearest_neighbors(p, k), m_tree.point_data()) == Ponca::STABLE) { + pp.z() = std::acos(fit.primitiveGradient().normalized().x()); + } else + std::cerr << "Something weird happened here..." << std::endl; + } + updateKdTree(); +} + void DataManager::fitPointCloudToRange(const std::pair& rangesEnd, const std::pair& rangesStart){ if (m_points.empty()) return; diff --git a/src/dataManager.h b/src/dataManager.h index 78bc424..78221b9 100644 --- a/src/dataManager.h +++ b/src/dataManager.h @@ -91,6 +91,10 @@ struct DataManager { void fitPointCloudToRange(const std::pair& rangesEnd, const std::pair& rangesStart = {0,0}); + /// Utils: compute normals using covariance plane fit + /// \param Number of neighbors (3 means current point and 2 closest points: left and right) + void computeNormals(int k = 3); + private: PointContainer m_points; KdTree m_tree; From 63e6e167a6eb4b0f2751d7959ee7b30ebf32d657 Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Thu, 14 Dec 2023 22:41:59 +0100 Subject: [PATCH 11/13] Add Ctrl-click to invert normal of a point --- src/myview.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/myview.cpp b/src/myview.cpp index c240db1..13e325c 100644 --- a/src/myview.cpp +++ b/src/myview.cpp @@ -7,10 +7,10 @@ using namespace nanogui; MyView::MyView(nanogui::Widget *parent, DataManager* mgr) : ImageView(parent), m_dataMgr(mgr) { std::cout<< "Controls:\n" - << " ctrl+mouse move: move view\n" << " scroll: zoom in/out\n" << " left click: move point\n" - << " right click: change point normal" + << " right click + move: rotate point normal" + << " ctrl+click: invert normal\n" << std::endl; } @@ -65,19 +65,26 @@ bool MyView::mouse_button_event(const Vector2i &p, int button, bool down, int modifiers) { auto lp = pos_to_pixel(p - m_pos); - // left click, no modifier, on press - if (modifiers==0 && down && isInsideImage(lp)) { -// std::cout << "mouse_button_event: p=[" << pos_to_pixel(p - m_pos) << "], button=[" << button -// << "], isdown: " << down << std::endl; + // left click, on press + if (down && isInsideImage(lp)) { auto pointId = findPointId(lp); - if (pointId < 0) { - if(button == 0) { // create new point iif left click (button id seems to be different wrt drag event - std::cout << "MyView::add new point" << std::endl; - m_dataMgr->getPointContainer().emplace_back(lp.x(), lp.y(), DEFAULT_POINT_ANGLE); + if (modifiers == 0) { // no modified + if (pointId < 0) { + if (button == 0) { // create new point iif left click (button id seems to be different wrt drag event + std::cout << "MyView::add new point" << std::endl; + m_dataMgr->getPointContainer().emplace_back(lp.x(), lp.y(), DEFAULT_POINT_ANGLE); + m_dataMgr->updateKdTree(); + } + } else { + m_movedPoint = pointId; + } + } else if (modifiers == 2) { // Ctrl + if (pointId >= 0) { + std::cout << "Flip normal of point " << pointId << std::endl; + auto& angle = m_dataMgr->getPointContainer()[pointId].z(); + angle = float(std::fmod(angle + M_PI, 2.*M_PI)); m_dataMgr->updateKdTree(); } - } else { - m_movedPoint = pointId; } return true; } From 5924f6042575aa450177289b2a028ac47c7834bd Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Fri, 15 Dec 2023 19:21:34 +0100 Subject: [PATCH 12/13] Fix file loading --- src/dataManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dataManager.cpp b/src/dataManager.cpp index 5f24ea9..ad2a9f0 100644 --- a/src/dataManager.cpp +++ b/src/dataManager.cpp @@ -51,7 +51,7 @@ DataManager::loadPointCloud(const std::string& path){ m_points.emplace_back(numbers[0], numbers[1], DEFAULT_POINT_ANGLE); needToComputeNormals = true; } else if (numbers.size() == 4){ // loaded x-y only, set normal to default value - m_points.emplace_back(numbers[0], numbers[1], std::acos(numbers[2])); + m_points.emplace_back(numbers[0], numbers[1], std::atan2(numbers[3], numbers[2])); } else { // malformed line std::cerr << "Skipping malformed line: [" << line << "]" << std::endl; } From f128299566bb5bb51c7da36108208af712cfa95c Mon Sep 17 00:00:00 2001 From: Nicolas Mellado Date: Fri, 15 Dec 2023 19:29:34 +0100 Subject: [PATCH 13/13] Add point cloud files (4 points and bunny) --- dataset/4points.dat | 5 ++ dataset/bunny.dat | 129 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 dataset/4points.dat create mode 100644 dataset/bunny.dat diff --git a/dataset/4points.dat b/dataset/4points.dat new file mode 100644 index 0000000..e36ec29 --- /dev/null +++ b/dataset/4points.dat @@ -0,0 +1,5 @@ +# x y nx ny +215.87 324.15 -4.37114e-08 1 +95.1759 262.079 -0.996128 -0.0879132 +212.422 150.35 0.0445487 -0.999007 +337.943 244.147 0.999632 -0.0271429 diff --git a/dataset/bunny.dat b/dataset/bunny.dat new file mode 100644 index 0000000..2608478 --- /dev/null +++ b/dataset/bunny.dat @@ -0,0 +1,129 @@ +# x y nx ny +228.969 18.6691 -0.142503 -0.989794 +214.614 20.8804 -0.271324 -0.962488 +201.227 26.4899 -0.270028 -0.962853 +187.027 28.6103 -0.024168 -0.999708 +172.552 27.1909 0.119336 -0.992854 +158.155 25.1401 0.0932225 -0.995645 +143.624 24.4823 -0.0251171 -0.999685 +129.198 25.8689 -0.285292 -0.958441 +116.491 32.5679 -0.917162 -0.398515 +114.078 46.0523 -0.986312 -0.164892 +113.047 58.3086 -0.933771 -0.357872 +107.169 70.0382 -0.998194 0.0600762 +114.214 82.7168 -0.779438 0.626479 +124.976 92.1766 -0.533188 0.845997 +138.334 97.9326 -0.602708 0.797962 +146.754 108.722 -0.99998 0.00636636 +136.929 115.636 -0.410366 -0.911921 +122.925 119.605 -0.320878 -0.947121 +109.408 124.96 -0.482556 -0.875865 +97.7304 133.49 -0.619289 -0.785163 +86.6565 142.904 -0.69817 -0.715932 +76.9588 153.746 -0.856326 -0.516435 +67.5209 164.833 -0.824662 -0.565626 + 60.66 177.532 -0.827018 -0.562176 + 54.496 190.655 -0.962354 -0.271799 +49.2984 204.147 -0.982173 0.187981 +49.1295 218.672 -0.99896 0.0456026 +50.6214 233.132 -0.97224 0.233986 +55.1744 246.216 -0.976651 -0.214833 +44.1788 252.743 -0.525772 -0.850626 +32.4983 261.072 -0.75969 -0.650286 +24.9319 273.362 -0.952743 0.303779 + 23.986 287.768 -0.995882 0.0906624 +27.5213 301.856 -0.968207 0.25015 +31.2583 315.916 -0.977588 0.210529 + 33.64 330.268 -0.986761 0.162181 +35.9799 344.644 -0.999184 0.0403894 +34.8005 359.07 -0.993444 0.114323 +39.2236 372.802 -0.83245 0.5541 +50.1519 382.122 -0.505101 0.86306 +63.7849 387.189 -0.294318 0.955707 +77.9122 390.671 -0.251752 0.967792 +91.3056 394.351 -0.780164 0.625575 +92.7628 408.839 -0.994975 0.100121 +94.2209 423.323 -0.986009 0.166691 +97.5992 437.442 -0.935884 0.352308 +104.321 450.132 -0.704323 0.709879 +117.155 456.866 -0.210798 0.97753 +131.551 455.997 0.355984 0.934492 +144.143 448.292 0.844111 0.536168 +146.473 433.199 0.996914 -0.0785065 + 144.98 418.774 0.90178 0.432195 +139.837 405.185 0.516433 0.856328 +146.181 405.872 -0.234265 0.972173 +157.091 415.51 -0.29162 0.956534 +167.883 425.279 -0.652211 0.758038 +179.147 434.487 -0.608992 0.793177 +190.953 442.992 -0.560781 0.827964 +203.241 450.806 -0.535292 0.844667 +215.504 458.55 -0.548279 0.836296 +227.544 466.739 -0.545749 0.837949 +239.897 474.438 -0.41457 0.910017 +253.418 478.504 0.196699 0.980464 + 264.74 469.527 0.764407 0.644734 +271.739 456.915 0.822954 0.568108 +274.838 442.769 0.995683 -0.0928154 +274.414 428.243 0.969379 -0.245568 +267.912 415.501 0.783857 -0.620942 +256.868 406.07 0.559931 -0.828539 +243.988 399.333 0.437505 -0.899216 +230.716 393.346 0.40087 -0.916135 +217.332 387.669 0.431133 -0.902288 +204.507 380.823 0.469868 -0.882737 +191.649 373.998 0.491561 -0.870843 +179.188 366.531 0.530932 -0.847414 +167.035 358.577 0.824017 -0.566565 + 164.58 345.056 0.995925 -0.0901891 +169.426 331.373 0.998315 0.0580234 +178.374 319.982 0.574449 0.81854 +192.019 315.535 0.288952 0.957344 +206.017 311.637 0.273907 0.961756 +219.951 307.58 0.09506 0.995472 + 234.37 308.931 -0.169144 0.985591 +248.477 312.474 -0.24806 0.968745 +262.565 316.151 -0.243243 0.969965 +276.718 319.556 -0.204287 0.978911 +291.036 322.092 -0.121988 0.992532 +305.553 323.101 -0.0368414 0.999321 +320.104 323.164 0.0386137 0.999254 +334.569 321.979 0.214754 0.976668 +348.227 316.981 0.393408 0.919364 +361.282 310.548 0.47724 0.878773 +373.783 303.103 0.567348 0.823478 +385.183 294.081 0.659128 0.75203 +395.877 284.838 0.725596 0.688121 +404.893 272.733 0.742014 0.670385 + 413.06 260.701 0.832799 0.553575 +420.705 248.285 0.958039 0.286638 +425.819 234.605 0.852343 0.522983 + 431.05 220.008 0.978726 0.205173 +432.721 206.391 0.988524 -0.151062 +434.553 191.957 0.9943 -0.106623 +435.824 177.454 0.998241 -0.0592836 +436.265 163.099 0.68304 0.730381 +450.814 163.261 0.170737 0.985317 +464.132 158.249 0.605563 0.795797 +473.121 146.902 0.939887 0.341486 +474.213 132.334 0.999997 -0.0025838 +472.654 117.864 0.979425 -0.201807 +468.376 104.012 0.895933 -0.44419 +459.942 92.2238 0.748117 -0.663567 +449.223 82.4162 0.619113 -0.785302 +437.176 74.275 0.516586 -0.856235 +424.354 67.4126 0.427279 -0.90412 +410.904 61.8593 0.346664 -0.937989 +397.089 57.3358 0.299092 -0.954224 +383.158 53.1626 0.505331 -0.862926 +372.752 43.0794 0.65725 -0.753672 +361.382 34.173 0.458552 -0.888668 +347.475 30.0311 0.221991 -0.975049 +333.107 27.7349 0.133278 -0.991079 + 318.63 26.1521 0.101192 -0.994867 +304.152 24.7898 0.096887 -0.995295 +289.656 23.3317 0.103085 -0.994673 +275.184 21.7876 0.107156 -0.994242 +260.722 20.2132 0.105711 -0.994397 +246.242 18.7108 0.0588211 -0.998269 +231.711 18.5042 0.00632855 -0.99998