diff --git a/.gitignore b/.gitignore index 2a2fe10a..9db8856c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build*/ +*build* *.user assets/demo* .vscode/* diff --git a/.gitmodules b/.gitmodules index 7ef384bc..b0ef7163 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "3rdparty/stb"] path = 3rdparty/stb url = https://github.com/nothings/stb.git +[submodule "3rdparty/happly"] + path = 3rdparty/happly + url = https://github.com/nmwsharp/happly.git diff --git a/3rdparty/happly b/3rdparty/happly new file mode 160000 index 00000000..cfa26115 --- /dev/null +++ b/3rdparty/happly @@ -0,0 +1 @@ +Subproject commit cfa2611550bc7da65855a78af0574b65deb81766 diff --git a/CMakeLists.txt b/CMakeLists.txt index fab35a5b..0599bfd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ OPTION (OpenGR_USE_WEIGHTED_LCP "Use gaussian weights for point samples when com ################################################################################ set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) set(SRC_DIR ${PROJECT_SOURCE_DIR}/src/) +set(THIRDPARTY_DIR ${PROJECT_SOURCE_DIR}/3rdparty) set(ASSETS_DIR ${PROJECT_SOURCE_DIR}/assets) set(SCRIPTS_DIR ${PROJECT_SOURCE_DIR}/scripts) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) @@ -100,7 +101,7 @@ if( NOT EIGEN3_INCLUDE_DIR ) find_package(Eigen3 QUIET) if( (NOT Eigen3_FOUND) OR EIGEN_VERSION_NUMBER VERSION_LESS 3.3 ) - set(EIGEN3_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/3rdparty/Eigen") + set(EIGEN3_INCLUDE_DIR "${THIRDPARTY_DIR}/Eigen") if( NOT EXISTS ${EIGEN3_INCLUDE_DIR}/signature_of_eigen3_matrix_library ) execute_process(COMMAND git submodule update --init -- ${EIGEN3_INCLUDE_DIR} @@ -116,7 +117,7 @@ message(STATUS "Eigen3 root path: ${EIGEN3_INCLUDE_DIR}") ## 2. if any, and version >= 3.3.x, use system version ## 3. otherwise, download (if required) and use submodule if( NOT STB_INCLUDE_DIR ) - set(STB_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/3rdparty/stb") + set(STB_INCLUDE_DIR "${THIRDPARTY_DIR}/stb") if( NOT EXISTS ${STB_INCLUDE_DIR}/stb.h ) execute_process(COMMAND git submodule update --init -- ${STB_INCLUDE_DIR} diff --git a/apps/ExtPointBinding/ext/pointadapter_extlib1.hpp b/apps/ExtPointBinding/ext/pointadapter_extlib1.hpp index 964ee3e1..5ed8269d 100644 --- a/apps/ExtPointBinding/ext/pointadapter_extlib1.hpp +++ b/apps/ExtPointBinding/ext/pointadapter_extlib1.hpp @@ -24,12 +24,12 @@ struct PointAdapter { m_color (Eigen::Map( p.color )) { } - inline const Eigen::Map< const VectorType >& pos() const { return m_pos; } inline const Eigen::Map< const VectorType >& normal() const { return m_normal; } inline const Eigen::Map< const VectorType >& color() const { return m_color; } inline const Eigen::Map< const VectorType >& rgb() const { return m_color; } + inline bool hasColor() const { return (m_color.array() > Scalar(0)).all(); } // invalid colors are encoded with -1 }; } diff --git a/apps/ExtPointBinding/ext/pointadapter_extlib2.hpp b/apps/ExtPointBinding/ext/pointadapter_extlib2.hpp index b5a827af..bce01bc1 100644 --- a/apps/ExtPointBinding/ext/pointadapter_extlib2.hpp +++ b/apps/ExtPointBinding/ext/pointadapter_extlib2.hpp @@ -28,6 +28,8 @@ struct PointAdapter { inline const Eigen::Map< const VectorType >& color() const { return m_color; } inline const Eigen::Map< const VectorType >& rgb() const { return m_color; } + inline bool hasColor() const { return (m_color.array() > Scalar(0)).all(); } // invalid colors are encoded with -1 + }; } diff --git a/apps/io/CMakeLists.txt b/apps/io/CMakeLists.txt index d7a8a327..ec7a36ac 100644 --- a/apps/io/CMakeLists.txt +++ b/apps/io/CMakeLists.txt @@ -7,8 +7,23 @@ set(io_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") file(GLOB_RECURSE io_sources ${io_ROOT}/*.cc) file(GLOB_RECURSE io_headers ${io_ROOT}/*.h ${io_ROOT}/*.hpp) +## Happly: automatic configuration: +## 1. if HAPPLY_INCLUDE_DIR is set, use it directly +## 2. if any, use system version +## 3. otherwise, download (if required) and use submodule +if( NOT HAPPLY_INCLUDE_DIR ) + set(HAPPLY_INCLUDE_DIR "${THIRDPARTY_DIR}/happly") + + if( NOT EXISTS ${HAPPLY_INCLUDE_DIR}/happly.h ) + execute_process(COMMAND git submodule update --init -- ${HAPPLY_INCLUDE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + endif( NOT EXISTS ${HAPPLY_INCLUDE_DIR}/happly.h ) +endif( NOT HAPPLY_INCLUDE_DIR ) +include_directories(${HAPPLY_INCLUDE_DIR}) +message(STATUS "HAPPLY root path: ${HAPPLY_INCLUDE_DIR}") + add_library(opengr_apps_io STATIC ${io_sources} ${io_headers}) -target_include_directories(opengr_apps_io PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) +target_include_directories(opengr_apps_io PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${HAPPLY_INCLUDE_DIR}) find_package(Filesystem QUIET) if(CXX_FILESYSTEM_HAVE_FS) diff --git a/apps/io/gr/io/io.cc b/apps/io/gr/io/io.cc index 5d6250ef..fdafaec7 100644 --- a/apps/io/gr/io/io.cc +++ b/apps/io/gr/io/io.cc @@ -65,9 +65,9 @@ IOManager::formatPolyworksMatrix( /// Limits dependency on stb just to compilation of the library //////////////////////////////////////////////////////////////////////////////// unsigned char* -IOManager::stbi_load_(char const *filename, int *x, int *y, int *comp, int req_comp) +IOManager::stbi_load_(const string& filename, int *x, int *y, int *comp, int req_comp) { - return stbi_load(filename, x, y, comp, req_comp); + return stbi_load(filename.c_str(), x, y, comp, req_comp); } void diff --git a/apps/io/gr/io/io.h b/apps/io/gr/io/io.h index 135594ab..0910749b 100644 --- a/apps/io/gr/io/io.h +++ b/apps/io/gr/io/io.h @@ -17,17 +17,23 @@ struct tripple { - int a; - int b; - int c; - int n1; - int n2; - int n3; - int t1; - int t2; - int t3; - tripple() {} - tripple(int _a, int _b, int _c) : a(_a), b(_b), c(_c) {} + int a {-1}; + int b {-1}; + int c {-1}; + int n1 {-1}; + int n2 {-1}; + int n3 {-1}; + int t1 {-1}; + int t2 {-1}; + int t3 {-1}; + inline tripple() {} + inline tripple(int _a, int _b, int _c) : a(_a), b(_b), c(_c) {} + // defaulted comparison operators are a C++20 extension, so we need to write it explicitely + inline bool operator==(const tripple& o) const { + return a==o.a && b==o.b && c==o.c && + n1==o.n1 && n2==o.n2 && n3==o.n3 && + t1==o.t1 && t2==o.t2 && t3==o.t3; + } }; class IOManager{ @@ -37,9 +43,10 @@ class IOManager{ }; public: - // Obj read/write simple functions. + /// Obj read/write simple functions. + /// \warning For ply files: loads only vertices positions and attributes (faces are ignored) template - bool ReadObject(const char *name, + bool ReadObject(const std::string& name, std::vector > &v, std::vector &tex_coords, std::vector::VectorType> &normals, @@ -51,7 +58,7 @@ class IOManager{ typename NormalRange, typename TrisRange, typename MTLSRange> - bool WriteObject(const char *name, + bool WriteObject(const std::string& name, const PointRange &v, const TextCoordRange &tex_coords, const NormalRange &normals, @@ -64,7 +71,7 @@ class IOManager{ private: template bool - ReadPly(const char *name, + ReadPly(const std::string& name, std::vector > &v, std::vector::VectorType> &normals); @@ -81,12 +88,12 @@ class IOManager{ */ template bool - ReadPtx(const char *name, + ReadPtx(const std::string& name, std::vector > &v); template bool - ReadObj(const char *name, + ReadObj(const std::string& name, std::vector > &v, std::vector &tex_coords, std::vector::VectorType> &normals, @@ -95,7 +102,7 @@ class IOManager{ template bool - WritePly(std::string name, + WritePly(const std::string& name, const PointRange &v, const NormalRange &normals); @@ -105,7 +112,7 @@ class IOManager{ typename TrisRange, typename MTLSRange> bool - WriteObj(std::string name, + WriteObj(const std::string& name, const PointRange &v, const TexCoordRange &tex_coords, const NormalRange &normals, @@ -126,7 +133,7 @@ class IOManager{ /// Limits dependency on stb just to compilation of the library by compiling /// required stbi methods to object files at library compilation. unsigned char* - stbi_load_(char const *filename, int *x, int *y, int *comp, int req_comp); + stbi_load_(const std::string& name, int *x, int *y, int *comp, int req_comp); void stbi_image_free_(void *retval_from_stbi_load); @@ -138,5 +145,4 @@ class IOManager{ }; // class IOMananger #include "io.hpp" -#include "io_ply.h" diff --git a/apps/io/gr/io/io.hpp b/apps/io/gr/io/io.hpp index 85e38747..80fe1d23 100644 --- a/apps/io/gr/io/io.hpp +++ b/apps/io/gr/io/io.hpp @@ -15,6 +15,8 @@ # endif #endif +#include "happly.h" + #define LINE_BUF_SIZE 100 using namespace std; @@ -29,7 +31,7 @@ template bool IOManager::WriteObject( - const char *name, + const string& name, const PointRange &v, const TextCoordRange &tex_coords, const NormalRange &normals, @@ -61,94 +63,69 @@ bool IOManager::WriteObject( template bool IOManager::WritePly( - std::string filename, + const std::string& filename, const PointRange &v, const NormalRange &normals) { - std::ofstream plyFile; - plyFile.open (filename.c_str(), std::ios::out | std::ios::trunc | std::ios::binary); - if (! plyFile.is_open()){ - std::cerr << "Cannot open file to write!" << std::endl; - return false; - } - - bool useNormals = normals.size() == v.size(); - // we check if we have colors by looking if the first rgb vector is void - auto has_color = [](const typename PointRange::value_type& p ) { using Scalar = typename PointRange::value_type::Scalar; - return p.rgb().squaredNorm() > Scalar(0.001); - }; - bool useColors = false; - for(const auto& p : v) - { - if( has_color(p) ) + happly::PLYData plyOut; + + // Compute properties + bool useNormals = normals.size() == v.size(); + // we check if we have colors by looking if the first rgb vector is void + auto has_color = [](const typename PointRange::value_type& p ) { return p.hasColor(); }; + bool useColors = std::find_if(v.begin(), v.end(),has_color) != v.end(); + + // Generate output buffers + // Read all elements in data, correct their depth and print them in the file + std::vectorx,y,z,nx,ny,nz; + std::vectorred, green, blue; + typename NormalRange::const_iterator normal_it = normals.cbegin(); + for(const auto& p : v) { - useColors = true; - break; - } - } - - plyFile.imbue(std::locale::classic()); - - // Write Header - plyFile << "ply" << std::endl; - plyFile << "format binary_little_endian 1.0" << std::endl; - plyFile << "comment File generated by the OpenGR library" << std::endl; - plyFile << "element vertex " << v.size() << std::endl; - plyFile << "property float x" << std::endl; - plyFile << "property float y" << std::endl; - plyFile << "property float z" << std::endl; - - if(useNormals) { - plyFile << "property float nx" << std::endl; - plyFile << "property float ny" << std::endl; - plyFile << "property float nz" << std::endl; - } - - if(useColors) { - plyFile << "property uchar red" << std::endl; - plyFile << "property uchar green" << std::endl; - plyFile << "property uchar blue" << std::endl; - } - - plyFile << "end_header" << std::endl; + x.push_back( Scalar(p.pos()(0)) ); + y.push_back( Scalar(p.pos()(1)) ); + z.push_back( Scalar(p.pos()(2)) ); + + if(useNormals) // size check is done earlier + { + nx.push_back( Scalar((*normal_it)(0)) ); + ny.push_back( Scalar((*normal_it)(1)) ); + nz.push_back( Scalar((*normal_it)(2)) ); + ++normal_it; + } - // Read all elements in data, correct their depth and print them in the file - char tmpChar; - float tmpFloat; - typename NormalRange::const_iterator normal_it = normals.cbegin(); - for(const auto& p : v) - { - tmpFloat = static_cast(p.pos()(0)); - plyFile.write(reinterpret_cast(&tmpFloat),sizeof(float)); - tmpFloat = static_cast(p.pos()(1)); - plyFile.write(reinterpret_cast(&tmpFloat),sizeof(float)); - tmpFloat = static_cast(p.pos()(2)); - plyFile.write(reinterpret_cast(&tmpFloat),sizeof(float)); - - if(useNormals) // size check is done earlier - { - plyFile.write(reinterpret_cast( &(*normal_it)(0) ),sizeof(float)); - plyFile.write(reinterpret_cast( &(*normal_it)(1) ),sizeof(float)); - plyFile.write(reinterpret_cast( &(*normal_it)(2) ),sizeof(float)); - ++normal_it; + if(useColors) + { + red.push_back( static_cast( p.rgb()(0) ) ); + green.push_back( static_cast( p.rgb()(1) ) ); + blue.push_back( static_cast( p.rgb()(2) ) ); + } } - if(useColors) - { - tmpChar = static_cast( p.rgb()(0) ); - plyFile.write(reinterpret_cast(&tmpChar),sizeof(char)); - tmpChar = static_cast( p.rgb()(1) ); - plyFile.write(reinterpret_cast(&tmpChar),sizeof(char)); - tmpChar = static_cast( p.rgb()(2) ); - plyFile.write(reinterpret_cast(&tmpChar),sizeof(char)); + // generate output file structure and set buffers + plyOut.addElement("vertex", v.size()); + auto& el = plyOut.getElement("vertex"); + el.template addProperty("x", x); + el.template addProperty("y", y); + el.template addProperty("z", z); + + if(useNormals) { + el.template addProperty("nx", nx); + el.template addProperty("ny", ny); + el.template addProperty("nz", nz); + } + if(useColors) { + el.template addProperty("red", red); + el.template addProperty("green", green); + el.template addProperty("blue", blue); } - } - plyFile.close(); + plyOut.comments.push_back("Registered with OpenGR (https://github.com/STORM-IRIT/OpenGR/)"); + plyOut.write(filename, happly::DataFormat::Binary); - return true; + return true; } template bool -IOManager::WriteObj(std::string filename, +IOManager::WriteObj(const std::string& filename, const PointRange &v, const TexCoordRange &tex_coords, const NormalRange &normals, @@ -220,7 +197,7 @@ IOManager::WriteObj(std::string filename, //////////////////////////////////////////////////////////////////////////////// template bool -IOManager::ReadObject(const char *name, +IOManager::ReadObject(const std::string& name, vector > &v, vector &tex_coords, vector::VectorType> &normals, @@ -244,7 +221,7 @@ IOManager::ReadObject(const char *name, } template -bool IOManager::ReadPtx(const char *filename, vector > &vertex) +bool IOManager::ReadPtx(const std::string& filename, vector > &vertex) { fstream f(filename, ios::in); if (!f || f.fail()) { @@ -303,7 +280,7 @@ bool IOManager::ReadPtx(const char *filename, vector > &vertex) template bool -IOManager::ReadObj(const char *filename, +IOManager::ReadObj(const std::string& filename, vector > &v, vector &tex_coords, vector::VectorType> &normals, @@ -444,3 +421,68 @@ IOManager::ReadObj(const char *filename, if (v.size() == 0) return false; return true; } + + +template +bool +IOManager::ReadPly(const std::string& filename, + vector > &v, + vector::VectorType> &normals){ + + + // Construct a data object by reading from file + happly::PLYData plyIn(filename); + if( plyIn.hasElement("vertex") ) + { + auto& el = plyIn.getElement("vertex"); + std::vector names = el.getPropertyNames(); + + std::vector x, y, z, nx, ny, nz; + std::vector red, green, blue; + + if( el.hasProperty("x") && el.hasProperty("y") && el.hasProperty("z") ) + { + x = el.getProperty("x"); + y = el.getProperty("y"); + z = el.getProperty("z"); + v.resize( x.size() ); + } + + bool hasNormals = false; + if( el.hasProperty("nx") && el.hasProperty("ny") && el.hasProperty("nz") ) + { + nx = el.getProperty("nx"); + ny = el.getProperty("ny"); + nz = el.getProperty("nz"); + normals.resize( nx.size() ); + hasNormals = true; + } + + bool hasColor = false; + if( el.hasProperty("red") && el.hasProperty("green") && el.hasProperty("blue") ) + { + red = el.getProperty("red"); + green = el.getProperty("green"); + blue = el.getProperty("blue"); + hasColor = true; + } + + const unsigned int size = v.size(); + using VectorType = typename Point3D::VectorType; + for( unsigned int i = 0; i < size; ++i){ + auto& vv = v[i]; + vv.pos() = {x[i], y[i], z[i]}; + if( hasNormals ){ + vv.set_normal( {nx[i], ny[i], nz[i]} ); + normals[i] = vv.normal(); + } + if( hasColor ){ + vv.set_rgb( {Scalar(red[i]), Scalar(green[i]), Scalar(blue[i])} ); + } + } + } + else + return false; + + return true; +} \ No newline at end of file diff --git a/apps/io/gr/io/io_ply.h b/apps/io/gr/io/io_ply.h deleted file mode 100644 index d99c40ec..00000000 --- a/apps/io/gr/io/io_ply.h +++ /dev/null @@ -1,399 +0,0 @@ -#pragma once - -#include - -typedef enum { - BINARY_BIG_ENDIAN_1, - BINARY_LITTLE_ENDIAN_1, - ASCII_1 -} PLYFormat; - -static const unsigned int MAX_COMMENT_SIZE = 256; - - -inline unsigned int -readHeader ( const char *filename, - unsigned int & numOfVertices, - unsigned int & numOfFaces, - PLYFormat & format, - unsigned int & numOfVertexProperties, - bool & haveColor ) -{ - using namespace std; - - // use binary mode to preserve end line symbols on linux and windows - ifstream in (filename, std::ios_base::in | std::ios_base::binary); - if (!in){ - cerr << "(PLY) error opening file" << endl; - return 0; - } - - numOfVertexProperties = 0; - numOfVertices = 0; - numOfFaces = 0; - haveColor = false; - - string current, currentelement; - in >> current; - if (current != "ply"){ - cerr << "(PLY) not a PLY file" << endl; - return 0; - } - in >> current; - int lid = 0; - while (current != "end_header") { - if (current == "format") { - in >> current; - if (current == "binary_big_endian") { - in >> current; - if (current == "1.0") - format = BINARY_BIG_ENDIAN_1; - else{ - cerr << "(PLY) error parsing header - bad binary big endian version" << endl; - return 0; - } - } else if (current == "binary_little_endian") { - in >> current; - if (current == "1.0") - format = BINARY_LITTLE_ENDIAN_1; - else{ - cerr << "(PLY) error parsing header - bad binary little endian version" << endl; - return 0; - } - } else if (current == "ascii") { - in >> current; - if (current == "1.0") - format = ASCII_1; - else{ - cerr << "(PLY) error parsing header - bad ascii version" << endl; - return 0; - } - } else { - cerr << "(PLY) error parsing header (format)" << endl; - return 0; - } - } else if (current == "element") { - in >> current; - if (current == "vertex"){ - currentelement = current; - in >> numOfVertices; - } - else if (current == "face"){ - currentelement = current; - in >> numOfFaces; - } - else{ - cerr << "(PLY) ignoring unknown element " << current << endl; - currentelement = ""; - } - } else if (currentelement != "" && current == "property") { - in >> current; - if (current == "float" || current == "double") { - numOfVertexProperties++; - in >> current; - } - else if (current == "uchar") { // color - numOfVertexProperties++; - haveColor = true; - in >> current; - } - else if (current == "list") { - in >> current; - in >> current; - in >> current; - } else { - cerr << "(PLY) error parsing header (property)" << endl; - return 0; - } - } else if ( (current == "comment") || (current.find("obj_info") != std::string::npos) ) { - char comment[MAX_COMMENT_SIZE]; - in.getline (comment, MAX_COMMENT_SIZE); - } else { - ; - //cerr << "(PLY) ignoring line: " << current << endl; - //return 0; - } - in >> current; - lid++; - } - - unsigned int headerSize = in.tellg (); - in.close (); - return headerSize+1; -} - - -template -void -bigLittleEndianSwap (T * v, unsigned int numOfElements) -{ - char * tmp = (char*)v; - for (unsigned int j = 0; j < numOfElements; j++){ - unsigned int offset = 4*j; - char c = tmp[offset]; - tmp[offset] = tmp[offset+3]; - tmp[offset+3] = c; - c = tmp[offset+1]; - tmp[offset+1] = tmp[offset+2]; - tmp[offset+2] = c; - } -} - -inline bool -almostsafefread ( void * ptr, size_t size, size_t count, FILE * stream ){ - size_t result = fread (ptr,size,count,stream); - return (result == count); -} - -template -inline bool -almostsafefscanf ( FILE * stream, const char * format, Args&&... args ){ - int count = fscanf (stream,format, std::forward(args)...); - return (count == targetCount); -} - -template -bool -readBinary1Body (const std::string & filename, - unsigned int headerSize, - unsigned int numOfVertices, - unsigned int numOfFaces, - unsigned int numOfVertexProperties, - bool haveColor, - bool bigEndian, - std::vector >& vertex, - std::vector::VectorType>& normal, - std::vector& face ) -{ - using namespace std; - using namespace gr; - - FILE * in = fopen (filename.c_str (), "r"); - if (!in){ - cerr << "(PLY) error opening file" << endl; - return false; - } - - char c; - for (unsigned int i = 0; i < headerSize; i++) { - almostsafefread (&c, 1, 1, in); - } - - // ***************** - // Reading geometry. - // ***************** - typename Point3D::VectorType n; - typename Point3D::VectorType rgb; - float * v = new float[numOfVertexProperties]; - unsigned char rgb_buff [4]; - - for (unsigned int i = 0; i < numOfVertices && !feof (in); i++) { - if (numOfVertexProperties==10){ - almostsafefread (v, 4, 6, in); - almostsafefread (rgb_buff, sizeof(unsigned char), 4, in); - }else if (numOfVertexProperties==9){ - almostsafefread (v, 4, 6, in); - almostsafefread (rgb_buff, sizeof(unsigned char), 3, in); - }else if (numOfVertexProperties==6 && haveColor){ - almostsafefread (v, 4, 3, in); - almostsafefread (rgb_buff, sizeof(unsigned char), 3, in); - }else if (numOfVertexProperties==7 ){ - almostsafefread (v, 4, 3, in); - almostsafefread (rgb_buff, sizeof(unsigned char), 4, in); - } - else - almostsafefread (v, 4, numOfVertexProperties, in); - if (bigEndian == true) - bigLittleEndianSwap (v, numOfVertexProperties); - vertex.emplace_back( v[0],v[1],v[2] ); - - if (numOfVertexProperties == 6){ - if (haveColor){ - rgb << rgb_buff[0], rgb_buff[1], rgb_buff[2]; - vertex.back().set_rgb(rgb); - }else{ - n << v[3], v[4], v[5]; - normal.push_back (n); - vertex.back().set_normal(n); - } - }else if (numOfVertexProperties == 7){ - rgb << rgb_buff[0], rgb_buff[1], rgb_buff[2]; - vertex.back().set_rgb(rgb); - }else if (numOfVertexProperties == 9 || numOfVertexProperties == 10){ - n << v[3], v[4], v[5]; - rgb << rgb_buff[0], rgb_buff[1], rgb_buff[2]; - normal.push_back (n); - vertex.back().set_normal(n); - vertex.back().set_rgb(rgb); - } - } - delete [] v; - - if (numOfFaces != 0){ - if (feof (in)){ - cerr << "(PLY) incomplete file" << endl; - return false; - } - - // ***************** - // Reading topology. - // ***************** - for (unsigned int i = 0; i < numOfFaces && !feof (in); i++) { - unsigned int f[4]; - char polygonSize; - almostsafefread (&polygonSize, 1, 1, in); - almostsafefread (f, 4, 3, in); - if (bigEndian == true) - bigLittleEndianSwap (f, 3); - face.emplace_back(f[0],f[1],f[2]); - } - } - - return true; -} - -template -bool -readASCII1Body (const std::string & filename, - unsigned int headerSize, - unsigned int numOfVertices, - unsigned int numOfFaces, - unsigned int numOfVertexProperties, - bool haveColor, - std::vector >& vertex, - std::vector::VectorType>& normal, - std::vector& face ) -{ - using namespace std; - using namespace gr; - - FILE * in = fopen (filename.c_str (), "r"); - if (!in){ - cerr << "(PLY) error opening file" << endl; - return false; - } - - char c; - for (unsigned int i = 0; i < headerSize; i++) { - almostsafefread (&c, 1, 1, in); - } - - // ***************** - // Reading geometry. - // ***************** - typename Point3D::VectorType n; - typename Point3D::VectorType rgb; - unsigned int rgb_buff [4]; - for (unsigned int i = 0; i < numOfVertices && !feof (in); i++) { - std::vector v(numOfVertexProperties); - - if (numOfVertexProperties==10){ - for (unsigned int j = 0; j < 6; j++) - almostsafefscanf<1> (in, "%f", &v[j]); - for (unsigned int j = 0; j < 4; j++) - almostsafefscanf<1> (in, "%i", &rgb_buff[j]); - } - else if (numOfVertexProperties==9){ - for (unsigned int j = 0; j < 6; j++) - almostsafefscanf<1> (in, "%f", &v[j]); - for (unsigned int j = 0; j < 3; j++) - almostsafefscanf<1> (in, "%i", &rgb_buff[j]); - }else if (numOfVertexProperties==6 && haveColor){ - for (unsigned int j = 0; j < 3; j++) - almostsafefscanf<1> (in, "%f", &v[j]); - for (unsigned int j = 0; j < 3; j++) - almostsafefscanf<1> (in, "%i", &rgb_buff[j]); - }else if (numOfVertexProperties==7){ - for (unsigned int j = 0; j < 3; j++) - almostsafefscanf<1> (in, "%f", &v[j]); - for (unsigned int j = 0; j < 4; j++) - almostsafefscanf<1> (in, "%i", &rgb_buff[j]); - } - else - for (unsigned int j = 0; j < numOfVertexProperties; j++) - almostsafefscanf<1> (in, "%f", &v[j]); - - vertex.emplace_back( v[0],v[1],v[2] ); - - if (numOfVertexProperties == 6){ - if (haveColor){ - rgb << rgb_buff[0], rgb_buff[1], rgb_buff[2]; - vertex.back().set_rgb(rgb); - - }else{ - n << v[3], v[4], v[5]; - normal.push_back (n); - vertex.back().set_normal(n); - } - }else if (numOfVertexProperties == 7){ - rgb << rgb_buff[0], rgb_buff[1], rgb_buff[2]; - vertex.back().set_rgb(rgb); - }else if (numOfVertexProperties == 9 || numOfVertexProperties == 10){ - n << v[3], v[4], v[5]; - rgb << rgb_buff[0], rgb_buff[1], rgb_buff[2]; - normal.push_back (n); - vertex.back().set_normal(n); - vertex.back().set_rgb(rgb); - } - } - - if (numOfFaces != 0){ - if (feof (in)){ - cerr << "(PLY) incomplete file" << endl; - return false; - } - - // ***************** - // Reading topology. - // ***************** - for (unsigned int i = 0; i < numOfFaces && !feof (in); i++) { - int f[3]; - int polygonSize; - almostsafefscanf<4> (in, "%d %d %d %d", &polygonSize, &f[0], &f[1], &f[2]); - face.emplace_back(f[0],f[1],f[2]); - } - } - - return true; -} - -template -bool -IOManager::ReadPly(const char *filename, - vector > &v, - vector::VectorType> &normals){ - - vector face; - unsigned int numOfVertexProperties, numOfVertices, numOfFaces; - PLYFormat format; - bool haveColor; - unsigned int headerSize = readHeader (filename, - numOfVertices, - numOfFaces, - format, - numOfVertexProperties, - haveColor); - if (haveColor) - cout << "haveColor" << endl; - if (headerSize != 0){ - if (format == BINARY_BIG_ENDIAN_1) - return readBinary1Body (filename, headerSize, numOfVertices, - numOfFaces, - numOfVertexProperties, haveColor, true, v, normals, face ); - else if (format == BINARY_LITTLE_ENDIAN_1) - return readBinary1Body (filename, headerSize, numOfVertices, - numOfFaces, - numOfVertexProperties, haveColor, false, v, normals, face ); - else if (format == ASCII_1) - return readASCII1Body ( filename, headerSize, numOfVertices, - numOfFaces, - numOfVertexProperties, haveColor, v, normals, face ); - else{ - cerr << "(PLY) no support for this PLY format" << endl; - return false; - } - } - - return false; -} - diff --git a/assets/hippo1.ply b/assets/hippo1.ply new file mode 100644 index 00000000..81d0bf51 Binary files /dev/null and b/assets/hippo1.ply differ diff --git a/assets/hippo2.ply b/assets/hippo2.ply new file mode 100644 index 00000000..985939cb Binary files /dev/null and b/assets/hippo2.ply differ diff --git a/scripts/run-example.sh b/scripts/run-example.sh index 4594ed0b..392b8aea 100755 --- a/scripts/run-example.sh +++ b/scripts/run-example.sh @@ -66,11 +66,13 @@ SPACER="------------------------------" echo ${SPACER} echo "Running Super4PCS" time -p ../bin/Super4PCS -i ../assets/hippo1.obj ../assets/hippo2.obj -o 0.7 -d 0.01 -t 1000 -n 200 -r super4pcs_fast.obj -m mat_super4pcs_fast.txt +time -p ../bin/Super4PCS -i ../assets/hippo1.ply ../assets/hippo2.ply -o 0.7 -d 0.01 -t 1000 -n 200 -r super4pcs_fast.ply -m mat_super4pcs_fast.txt echo ${SPACER} echo "Running 4PCS" time -p ../bin/Super4PCS -i ../assets/hippo1.obj ../assets/hippo2.obj -o 0.7 -d 0.01 -t 1000 -n 200 -r 4pcs_fast.obj -m mat_4pcs_fast.txt -x +time -p ../bin/Super4PCS -i ../assets/hippo1.ply ../assets/hippo2.ply -o 0.7 -d 0.01 -t 1000 -n 200 -r 4pcs_fast.ply -m mat_4pcs_fast.txt -x if [ -f ../bin/OpenGR-PCLWrapper ]; then diff --git a/src/gr/utils/shared.h b/src/gr/utils/shared.h index 7a5a1098..48d903ec 100644 --- a/src/gr/utils/shared.h +++ b/src/gr/utils/shared.h @@ -74,7 +74,10 @@ struct PointConcept { inline PointConcept(const ExternalPoint&) { } /*! \brief Read access to the position property */ - inline const VectorType& pos() const { } + inline const VectorType& pos() const { } + + /*! \brief Indicates if the instance contains a valid color */ + inline bool hasColor() const { } }; #endif @@ -118,7 +121,7 @@ class Point3D inline void normalize() { normal_.normalize(); } - inline bool hasColor() const { return rgb_.squaredNorm() > Scalar(0.001); } + inline bool hasColor() const { return (rgb_.array() > Scalar(0)).all(); } // invalid colors are encoded with -1 Scalar& x() { return pos_.coeffRef(0); } Scalar& y() { return pos_.coeffRef(1); } @@ -128,7 +131,12 @@ class Point3D Scalar y() const { return pos_.coeff(1); } Scalar z() const { return pos_.coeff(2); } - + bool operator==(const Point3D& other) const + { + return pos_.isApprox( other.pos_ ) && + normal_.isApprox( other.normal_ ) && + rgb_.isApprox( other.rgb_ ); + } private: /// Normal. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f0cd36d1..25bf5cee 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -157,4 +157,21 @@ if (CXX_FILESYSTEM_HAVE_FS) if(OpenGR_USE_CHEALPIX) target_link_libraries(matching3pcs ${Chealpix_LIBS} ) endif(OpenGR_USE_CHEALPIX) + + + ############################################# + ## io + if (OpenGR_COMPILE_APPS) + set(io_SRCS io.cc ) + add_executable(test_io ${io_SRCS} ${testing_SRCS}) + add_test(NAME test_io + #CONFIGURATIONS Release + COMMAND test_io) + set_tests_properties ( test_io PROPERTIES TIMEOUT 600) # in seconds + target_link_libraries(test_io gr::io ${OpenGRTestsDeps} std::filesystem) + target_compile_definitions(test_io PUBLIC -DCXX_FILESYSTEM_HAVE_FS -DCXX_FILESYSTEM_IS_EXPERIMENTAL=$ -DCXX_FILESYSTEM_NAMESPACE=${CXX_FILESYSTEM_NAMESPACE}) + add_dependencies(test_io dataset-standford-bunny) + add_dependencies(buildtests test_io) + endif (OpenGR_COMPILE_APPS) endif (CXX_FILESYSTEM_HAVE_FS) + diff --git a/tests/io.cc b/tests/io.cc new file mode 100644 index 00000000..cc3c34b9 --- /dev/null +++ b/tests/io.cc @@ -0,0 +1,116 @@ +// Copyright 2022 Nicolas Mellado +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// -------------------------------------------------------------------------- // +// +// Authors: Nicolas Mellado +// +// This test runs io routines and check data integrity +// This test is part of the implementation of the library OpenGR. + +#include "gr/io/io.h" + +#include +#include +#include +#include + +#ifndef CXX_FILESYSTEM_HAVE_FS +# error std::filesystem is required to compile this file +#endif +#if CXX_FILESYSTEM_IS_EXPERIMENTAL +# include +#else +# include +#endif + +#include "testing.h" + + +using namespace std; +using namespace gr; + +using Scalar = float; + +/*! + Read a configuration file from Standford 3D shape repository and + output a set of filename and eigen transformations + */ +inline string +extractFilesAndTrFromStandfordConfFile(){ + const string confFilePath = "./datasets/bunny/data/bun.conf"; // the first 3d model of this file will be loaded. + VERIFY (CXX_FILESYSTEM_NAMESPACE::exists(confFilePath) && CXX_FILESYSTEM_NAMESPACE::is_regular_file(confFilePath)); + + // extract the working directory for the configuration path + const string workingDir = CXX_FILESYSTEM_NAMESPACE::path(confFilePath).parent_path().string(); + VERIFY (CXX_FILESYSTEM_NAMESPACE::exists(workingDir)); + + // read the configuration file and call the matching process + std::string line; + std::ifstream confFile; + confFile.open(confFilePath); + VERIFY (confFile.is_open()); + + vector tokens; + do { + getline(confFile, line); + istringstream iss(line); + tokens = {istream_iterator{iss}, istream_iterator{}}; + } while (tokens[0].compare("bmesh") != 0); + + // here we know that the tokens are: + // [0]: keyword, must be bmesh + // [1]: 3D object filename + // [2-4]: target translation with previous object + // [5-8]: target quaternion with previous object + VERIFY (tokens.size() == 9); + string res = CXX_FILESYSTEM_NAMESPACE::path(confFilePath).parent_path().string()+string("/")+tokens[1]; + VERIFY(CXX_FILESYSTEM_NAMESPACE::exists(res) && CXX_FILESYSTEM_NAMESPACE::is_regular_file(res)); + + confFile.close(); + + return res; +} + + +int main(int argc, const char **argv) { + using std::string; + + if(!Testing::init_testing(1, argv)) return EXIT_FAILURE; + + string filename = extractFilesAndTrFromStandfordConfFile(); + string tempfilename = "temp.ply"; + + cout << "Loading " << filename << endl; + + std::vector > v, v2; + std::vector tex_coords, tex_coords2; + std::vector::VectorType> normals, normals2; + std::vector tris, tris2; + std::vector mtls, mtls2; + + IOManager manager; + manager.ReadObject(filename, v, tex_coords, normals, tris, mtls); + manager.WriteObject(tempfilename, v, tex_coords, normals, tris, mtls); + manager.ReadObject(tempfilename, v2, tex_coords2, normals2, tris2, mtls2); + + // check if we get the same file after saving and loading again + VERIFY(std::equal(v.cbegin(), v.cend(), v2.cbegin())); + VERIFY(std::equal(tex_coords.cbegin(), tex_coords.cend(), tex_coords2.cbegin())); + VERIFY(std::equal(normals.cbegin(), normals.cend(), normals2.cbegin())); + VERIFY(std::equal(tris.cbegin(), tris.cend(), tris2.cbegin())); + VERIFY(std::equal(mtls.cbegin(), mtls.cend(), mtls2.cbegin())); + + return EXIT_SUCCESS; +}