diff --git a/.gitmodules b/.gitmodules index 6e700fa..6d1cff1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,9 @@ [submodule "external/ponca"] path = external/ponca url = https://github.com/poncateam/ponca.git +[submodule "external/argparse"] + path = external/argparse + url = https://github.com/p-ranav/argparse.git +[submodule "external/stb"] + path = external/stb + url = https://github.com/nothings/stb.git diff --git a/CMakeLists.txt b/CMakeLists.txt index d3ca3c3..2ab381e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,13 @@ set( NANOGUI_BUILD_PYTHON CACHE BOOL OFF) message("\n\n == CMAKE recursively building NanoGUI\n") add_subdirectory("external/nanogui") +# Add argparse +set(ARGPARSE_INSTALL OFF) +set(ARGPARSE_BUILD_TESTS OFF) +set(ARGPARSE_BUILD_SAMPLES OFF) +message("\n\n == CMAKE recursively building argparse\n") +add_subdirectory("external/argparse") + # Find OpenMP find_package(OpenMP) set(OpenMP_link_libraries ) @@ -57,6 +64,8 @@ add_executable( poncaplot src/myview.cpp src/application.h src/application.cpp + src/cli.h + src/cli.cpp src/poncaTypes.h src/drawingPass.h src/drawingPasses/distanceField.h @@ -68,6 +77,8 @@ add_executable( poncaplot target_include_directories(poncaplot PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/external/nanogui/include" "${CMAKE_CURRENT_SOURCE_DIR}/external/ponca/" + "${CMAKE_CURRENT_SOURCE_DIR}/external/argparse/include" + "${CMAKE_CURRENT_SOURCE_DIR}/external/stb/" "${CMAKE_CURRENT_SOURCE_DIR}/src/") # Link settings diff --git a/external/argparse b/external/argparse new file mode 160000 index 0000000..af442b4 --- /dev/null +++ b/external/argparse @@ -0,0 +1 @@ +Subproject commit af442b4da0cd7a07b56fa709bd16571889dc7fda diff --git a/external/stb b/external/stb new file mode 160000 index 0000000..f4a71b1 --- /dev/null +++ b/external/stb @@ -0,0 +1 @@ +Subproject commit f4a71b13373436a2866c5d68f8f80ac6f0bc1ffe diff --git a/src/application.cpp b/src/application.cpp index febf97c..61cf7d9 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -27,19 +27,17 @@ using namespace nanogui; const int tex_width = 500; const int tex_height = 500; -PoncaPlotApplication::PoncaPlotApplication() : -Screen(Vector2i(1200, 1024), "PoncaPlot"), m_dataMgr(new DataManager()){ +PoncaPlotApplication::PoncaPlotApplication(DataManager* mgr) : +Screen(Vector2i(1200, 1024), "PoncaPlot"), m_dataMgr(mgr){ m_dataMgr->setKdTreePostUpdateFunction([this]() { this->renderPasses(); }); - passDFWithKdTree = new DistanceFieldWithKdTree(); - passPlaneFit = new PlaneFitField(); - passSphereFit = new SphereFitField(); - passOrientedSphereFit = new OrientedSphereFitField(); - passUnorientedSphereFit = new UnorientedSphereFitField(); + // force creation of all supported DrawingPasses + for(int i=0;i!=m_dataMgr->nbSupportedDrawingPasses;++i) + m_dataMgr->getDrawingPass(i); m_passes[0] = new FillPass( {1,1,1,1}); - m_passes[1] = passOrientedSphereFit; + m_passes[1] = m_dataMgr->getDrawingPass("Oriented Sphere"); m_passes[2] = new ColorMap({1,1,1,1}); m_passes[3] = new DisplayPoint({0,0,0,1}); @@ -98,22 +96,16 @@ Screen(Vector2i(1200, 1024), "PoncaPlot"), m_dataMgr(new DataManager()){ new nanogui::Label(window, "Select Fit Type", "sans-bold"); - auto combo =new nanogui::ComboBox(window, - { "Distance Field", - "Plane", - "Sphere", - "Oriented Sphere", - "Unoriented Sphere"}); + + std::vector names; + names.resize(m_dataMgr->nbSupportedDrawingPasses); + for (const auto& p : m_dataMgr->supportedDrawingPasses) + names[p.second] = p.first; + + auto combo =new nanogui::ComboBox(window, names); combo->set_selected_index(3); 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!"); - } + m_passes[1] = m_dataMgr->getDrawingPass(id); buildPassInterface(id); renderPasses(); }); @@ -141,6 +133,11 @@ Screen(Vector2i(1200, 1024), "PoncaPlot"), m_dataMgr(new DataManager()){ new nanogui::Label(distanceFieldWidget, "no parameter available"); } + passPlaneFit = dynamic_cast(m_dataMgr->getDrawingPass("Plane")); + passSphereFit = dynamic_cast(m_dataMgr->getDrawingPass("Sphere")); + passOrientedSphereFit = dynamic_cast(m_dataMgr->getDrawingPass("Oriented Sphere")); + passUnorientedSphereFit = dynamic_cast(m_dataMgr->getDrawingPass("Unoriented Sphere")); + { genericFitWidget = new nanogui::Widget(window); genericFitWidget->set_layout(new GroupLayout()); diff --git a/src/application.h b/src/application.h index c95c87c..70dca45 100644 --- a/src/application.h +++ b/src/application.h @@ -20,7 +20,7 @@ namespace nanogui{ class PoncaPlotApplication : public nanogui::Screen { public: - PoncaPlotApplication(); + PoncaPlotApplication(DataManager* mgr); bool keyboard_event(int key, int scancode, int action, int modifiers) override; @@ -46,9 +46,7 @@ class PoncaPlotApplication : public nanogui::Screen { *genericFitWidget, //< parameters applicable to all fitting techniques *planeFitWidget, *sphereFitWidget, *orientedSphereFitWidget, *unorientedSphereFitWidget, *pass3Widget,*pass4Widget; - DistanceFieldWithKdTree *passDFWithKdTree; - PlaneFitField *passPlaneFit; - SphereFitField *passSphereFit; - OrientedSphereFitField *passOrientedSphereFit; - UnorientedSphereFitField *passUnorientedSphereFit; + + + BaseFitField* passPlaneFit, *passSphereFit, *passOrientedSphereFit, *passUnorientedSphereFit; }; diff --git a/src/cli.cpp b/src/cli.cpp new file mode 100644 index 0000000..fa1012f --- /dev/null +++ b/src/cli.cpp @@ -0,0 +1,161 @@ +#include "cli.h" + +#include "dataManager.h" +#include "drawingPass.h" +//#include "drawingPasses/distanceField.h" + +#include "argparse/argparse.hpp" + + +#define STB_IMAGE_STATIC +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_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 "stb_image.h" +#include "stb_image_write.h" +#include // floor + +PoncaPlotCLI::PoncaPlotCLI(DataManager* mgr) : m_dataMgr(mgr){ + +} + +bool +PoncaPlotCLI::run(int argc, char **argv) { + + struct { + std::string inputPath {}; + struct { + std::string name {"Oriented Sphere"}; + float scale {40}; + } fitting; + struct { + std::string path {}; + size_t width {500}; + size_t height {500}; + } output; + } params; + + std::vector names; + names.resize(m_dataMgr->nbSupportedDrawingPasses); + for (const auto& p : m_dataMgr->supportedDrawingPasses) + names[p.second] = p.first; + std::string namesStr; + for (const auto& n : names) + namesStr.append("\"" + n + "\" "); + + + + argparse::ArgumentParser program("poncaplot-cli"); + program.add_argument("-i", "--input") + .required() + .help("input file (.pts or .txt)"); + + // output controls + { + program.add_argument("-o", "--output") + .help("output file (image)"); + program.add_argument("-W", "--width") + .help("output image width (in pixels)") + .scan<'i', size_t>() + .default_value(params.output.width); + program.add_argument("-H", "--height") + .help("output image height (in pixels)") + .scan<'i', size_t>() + .default_value(params.output.height); + } + + // fitting controls + { + auto ft = program.add_argument("-f", "--fitType") + .default_value(params.fitting.name) + .help("fit type: [" + namesStr + "]"); + for (const auto& type : m_dataMgr->supportedDrawingPasses) + ft.add_choice(type.first); + + program.add_argument("-s") + .help("scale size (in pixels)") + .scan<'g', float>() + .default_value(params.fitting.scale); + } + + // return value of the method: do we skip the GUI ? + bool skipGUI = true; + + bool loaded = false; + try { + program.parse_args(argc, argv); + params.inputPath = program.get("-i"); + if (! params.inputPath.empty()) + { + loaded = m_dataMgr->loadPointCloud(params.inputPath);; + + // load fit properties + if (program.is_used("-f")) params.fitting.name = program.get("-f"); + if (program.is_used("-s")) params.fitting.scale = program.get("-s"); + + // load output properties + auto output = program.present("-o"); + if( output ) { + params.output.path = output.value(); + if (program.is_used("-W")) params.output.width = program.get("-W"); + if (program.is_used("-H")) params.output.height = program.get("-H"); + } else + skipGUI = false; // no output is set: display GUI with parameters set + } + } + catch (const std::exception& err) { + std::cout << err.what() << std::endl; + std::cout << program; + skipGUI = false; + } + + // configure and do rendering + if (loaded && skipGUI){ + auto texture = new float [params.output.width * params.output.height * 4]; + + // configure fitting + std::cout << "Configure fitting" << std::endl; + auto pass = m_dataMgr->getDrawingPass(params.fitting.name); + + if( auto fit = dynamic_cast(pass) ) { + fit->m_scale = params.fitting.scale; + } + + // configure renderer + std::cout << "Configure renderer" << std::endl; + std::array renderPasses{ + new FillPass( {1,1,1,1}) + , pass + , new ColorMap({1,1,1,1}) +// , new DisplayPoint({0,0,0,1}) + }; + + // render + std::cout << "Render" << std::endl; + const auto& points = m_dataMgr->getKdTree(); + for (auto* p : renderPasses) { + p->render(points, texture, int(params.output.width), int(params.output.height)); + } + + std::cout << "Save image" << std::endl; + { + auto buffer = new char [params.output.width * params.output.height * 4]; + std::transform(texture, texture + params.output.width * params.output.height * 4, + buffer, + [](float in) -> char{ + return char(std::floor(in*255.f)); + }); + stbi_write_png(params.output.path.c_str(), params.output.width, params.output.height, + 4, buffer, params.output.width * 4); + stbi_image_free(buffer); + } + + delete[] texture; + } + + return skipGUI; +} diff --git a/src/cli.h b/src/cli.h new file mode 100644 index 0000000..96c9520 --- /dev/null +++ b/src/cli.h @@ -0,0 +1,15 @@ +#pragma once + +// forward declarations +class DataManager; + +class PoncaPlotCLI { + +public: + PoncaPlotCLI(DataManager* mgr); + bool run(int argc, char**argv); + +private: + float* m_texture {nullptr}; + DataManager *m_dataMgr{nullptr}; +}; diff --git a/src/dataManager.cpp b/src/dataManager.cpp index ecec428..8b86917 100644 --- a/src/dataManager.cpp +++ b/src/dataManager.cpp @@ -3,6 +3,15 @@ #include #include +DataManager::DataManager() { + m_drawingPasses.fill(nullptr); +} + +DataManager::~DataManager() { + for (auto* p : m_drawingPasses) + delete p; +} + bool DataManager::savePointCloud(const std::string& path) const{ if( path.empty() ) return false; @@ -106,3 +115,36 @@ DataManager::fitPointCloudToRange(const std::pair& rangesEnd, const } } +DrawingPass* +DataManager::getDrawingPass(const std::string& name){ + return getDrawingPass(supportedDrawingPasses.at(name)); +} + +DrawingPass* +DataManager::getDrawingPass(size_t index){ + if (index >= nbSupportedDrawingPasses) return nullptr; + + DrawingPass** p = &(m_drawingPasses[index]); + if((*p) == nullptr) { + switch (index) { + case 0: //Distance Field + *p = new DistanceFieldWithKdTree(); + break; + case 1: // Plane + *p = new PlaneFitField(); + break; + case 2: // Sphere + *p = new SphereFitField(); + break; + case 3: // Oriented Sphere + *p = new OrientedSphereFitField(); + break; + case 4: // Unoriented Sphere + *p = new UnorientedSphereFitField(); + break; + default: + break; + } + } + return *p; +} diff --git a/src/dataManager.h b/src/dataManager.h index 7a8e80a..be74f5a 100644 --- a/src/dataManager.h +++ b/src/dataManager.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include //pair #include @@ -11,6 +12,8 @@ #include #include "poncaTypes.h" +#include "drawingPasses/distanceField.h" +#include "drawingPasses/poncaFitField.h" #ifndef M_PI @@ -19,39 +22,8 @@ #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; - } - } - [[nodiscard]] inline std::optional getAabb() const { - if (! Base::is_leaf()) - return Base::getAsInner().m_aabb; - else - return std::optional(); - } -}; + +struct DrawingPass; /// Structure holding shared data struct DataManager { @@ -61,6 +33,9 @@ struct DataManager { using PointContainer = std::vector; // stores x,y,normal angle in radians using VectorType = typename KdTree::VectorType; + DataManager(); + ~DataManager(); + /// Read access to point collection inline const KdTree& getKdTree() const { return m_tree; } @@ -95,9 +70,52 @@ struct DataManager { /// \param Number of neighbors (3 means current point and 2 closest points: left and right) void computeNormals(int k = 3); + /// Names of the supported drawing passes + static constexpr size_t nbSupportedDrawingPasses = 5; + const std::map supportedDrawingPasses { + {"Distance Field", 0}, + {"Plane", 1}, + {"Sphere", 2}, + {"Oriented Sphere", 3}, + {"Unoriented Sphere", 4} + }; + + DrawingPass* getDrawingPass(const std::string& name); + + /// Build a a drawing pass + /// \param index of the pass name in supportedDrawingPasses + DrawingPass* getDrawingPass(size_t index); + + template + bool processPass(int index, Functor f) { + switch (index) { + case 0: //Distance Field + f(dynamic_cast(getDrawingPass(0))); + break; + case 1: // Plane + f(dynamic_cast(getDrawingPass(1))); + break; + case 2: // Sphere + f(dynamic_cast(getDrawingPass(2))); + break; + case 3: // Oriented Sphere + f(dynamic_cast(getDrawingPass(3))); + break; + case 4: // Unoriented Sphere + f(dynamic_cast(getDrawingPass(4))); + break; + default: + return false; + } + return true; + } + + private: PointContainer m_points; KdTree m_tree; std::function m_updateFunction {[](){}}; + + std::array m_drawingPasses; }; diff --git a/src/drawingPass.h b/src/drawingPass.h index 2366043..0e4f66b 100644 --- a/src/drawingPass.h +++ b/src/drawingPass.h @@ -3,19 +3,19 @@ #include #include -#include "dataManager.h" +#include "poncaTypes.h" /// Base class to rendering processes struct DrawingPass { - virtual void render(const DataManager::KdTree& points, float*buffer, int w, int h) = 0; + virtual void render(const KdTree& points, float*buffer, int w, int h) = 0; virtual ~DrawingPass() = default; }; struct FillPass : public DrawingPass { inline explicit FillPass(const nanogui::Vector4f &fillColor = {1,1,1,1}) : m_fillColor(fillColor) {} - void render(const DataManager::KdTree& /*points*/, float*buffer, int w, int h) override{ + void render(const KdTree& /*points*/, float*buffer, int w, int h) override{ #pragma omp parallel for default(none) shared(buffer, w, h) for(auto j = 0; j +#include "dataManager.h" #include "application.h" +#include "cli.h" -int main(int /* argc */, char ** /* argv */) { - try { - nanogui::init(); +DataManager *mgr {nullptr}; - /* scoped variables */ { - nanogui::ref app = new PoncaPlotApplication(); - app->dec_ref(); - app->draw_all(); - app->set_visible(true); - nanogui::mainloop(1 / 10.f * 1000); - } +void clean(){ + if (mgr){ + delete mgr; + mgr = nullptr; + } +} - nanogui::shutdown(); - } catch (const std::exception &e) { - std::string error_msg = std::string("Caught a fatal error: ") + std::string(e.what()); - std::cerr << error_msg << std::endl; - return -1; - } catch (...) { - std::cerr << "Caught an unknown error!" << std::endl; +int main(int argc , char ** argv) { + mgr = new DataManager(); + + PoncaPlotCLI cli(mgr); + + if (! cli.run(argc, argv)) { + std::cout << "CLI does not want to run: launching graphic app" << std::endl; + try { + nanogui::init(); + + /* scoped variables */ { + nanogui::ref app = new PoncaPlotApplication(mgr); + app->dec_ref(); + app->draw_all(); + app->set_visible(true); + nanogui::mainloop(1 / 10.f * 1000); + } + + nanogui::shutdown(); + } catch (const std::exception &e) { + std::string error_msg = std::string("Caught a fatal error: ") + std::string(e.what()); + std::cerr << error_msg << std::endl; + clean(); + return -1; + } catch (...) { + std::cerr << "Caught an unknown error!" << std::endl; + } } + clean(); return 0; } diff --git a/src/poncaTypes.h b/src/poncaTypes.h index b9b0873..11b90fe 100644 --- a/src/poncaTypes.h +++ b/src/poncaTypes.h @@ -2,6 +2,7 @@ #include +#include class DataPoint { @@ -24,4 +25,41 @@ using WeightFunc = Ponca::DistWeightFunc; using SphereFit = Ponca::Basket; using OrientedSphereFit= Ponca::Basket; -using UnorientedSphereFit = Ponca::Basket; \ No newline at end of file +using UnorientedSphereFit = Ponca::Basket; + + + + +#include + +template > +struct MyKdTreeInnerNode : public Ponca::KdTreeDefaultInnerNode { + using AabbType = _AabbType; + AabbType m_aabb{}; +}; + +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; + } + } + [[nodiscard]] inline std::optional getAabb() const { + if (! Base::is_leaf()) + return Base::getAsInner().m_aabb; + else + return std::optional(); + } +}; + +using KdTree = Ponca::KdTreeBase>;