diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a1345915..920e4c312 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,12 +19,12 @@ SET(OpenMVS_BUILD_TOOLS ON CACHE BOOL "Build example applications") SET(OpenMVS_USE_OPENMP ON CACHE BOOL "Enable OpenMP library") SET(OpenMVS_USE_OPENGL ON CACHE BOOL "Enable OpenGL library") SET(OpenMVS_USE_BREAKPAD ON CACHE BOOL "Enable BreakPad library") -SET(OpenMVS_USE_PYTHON OFF CACHE BOOL "Enable Python library bindings") +SET(OpenMVS_USE_PYTHON ON CACHE BOOL "Enable Python library bindings") SET(OpenMVS_USE_CERES OFF CACHE BOOL "Enable CERES optimization library") SET(OpenMVS_USE_CUDA ON CACHE BOOL "Enable CUDA library") -SET(OpenMVS_USE_FAST_FLOAT2INT ON CACHE BOOL "Use an optimized code to convert real numbers to int") +SET(OpenMVS_USE_FAST_FLOAT2INT OFF CACHE BOOL "Use an optimized code to convert real numbers to int") SET(OpenMVS_USE_FAST_INVSQRT OFF CACHE BOOL "Use an optimized code to compute the inverse square root (slower in fact on modern compilers)") -SET(OpenMVS_USE_FAST_CBRT ON CACHE BOOL "Use an optimized code to compute the cubic root") +SET(OpenMVS_USE_FAST_CBRT OFF CACHE BOOL "Use an optimized code to compute the cubic root") SET(OpenMVS_USE_SSE ON CACHE BOOL "Enable SSE optimizations") SET(OpenMVS_MAX_CUDA_COMPATIBILITY OFF CACHE BOOL "Build for maximum CUDA device compatibility") SET(OpenMVS_ENABLE_TESTS ON CACHE BOOL "Enable test code") @@ -52,10 +52,15 @@ ENDIF() PROJECT(OpenMVS) SET(OpenMVS_MAJOR_VERSION 2) -SET(OpenMVS_MINOR_VERSION 2) +SET(OpenMVS_MINOR_VERSION 3) SET(OpenMVS_PATCH_VERSION 0) SET(OpenMVS_VERSION ${OpenMVS_MAJOR_VERSION}.${OpenMVS_MINOR_VERSION}.${OpenMVS_PATCH_VERSION}) +# Disable SSE on unsuported platforms +IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm|ARM|aarch64|AARCH64)") + SET(OpenMVS_USE_SSE OFF) +ENDIF() + # Define helper functions and macros. INCLUDE(build/Utils.cmake) @@ -109,16 +114,15 @@ if(OpenMVS_USE_CUDA) ENABLE_LANGUAGE(CUDA) # CUDA-11.x can not be compiled using C++14 standard on Windows string(REGEX MATCH "^[0-9]+" CUDA_MAJOR ${CMAKE_CUDA_COMPILER_VERSION}) - if(${CUDA_MAJOR} GREATER 10 AND CMAKE_CXX_COMPILER MATCHES "MSVC" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "19.29") - message("Working around windows build failure with visual studio. Visual studio 16.10 introduced a compiler bug with compilng CUDA code with C++14. Set the cuda standard to 17 as a workaround.") - set(CMAKE_CUDA_STANDARD 17) + if(${CUDA_MAJOR} GREATER 10) + SET(CMAKE_CUDA_STANDARD 17) endif() EXECUTE_PROCESS(COMMAND "${CMAKE_CUDA_COMPILER}" --list-gpu-arch OUTPUT_VARIABLE LIST_GPU_ARCH ERROR_QUIET) if(NOT LIST_GPU_ARCH AND OpenMVS_MAX_CUDA_COMPATIBILITY) message(WARNING "Cannot compile for max CUDA compatibility, nvcc does not support --list-gpu-arch") - set(OpenMVS_MAX_CUDA_COMPATIBILITY NO) + SET(OpenMVS_MAX_CUDA_COMPATIBILITY OFF) endif() if(NOT OpenMVS_MAX_CUDA_COMPATIBILITY) if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) @@ -172,19 +176,21 @@ if(OpenMVS_USE_BREAKPAD) endif() endif() +SET(Boost_EXTRA_COMPONENTS "") if(OpenMVS_USE_PYTHON) - FIND_PACKAGE(PythonLibs 3.0 REQUIRED) - if(PythonLibs_FOUND) - INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIRS}) - LIST(APPEND OpenMVS_EXTRA_INCLUDES ${PYTHON_INCLUDE_DIRS}) - LIST(APPEND OpenMVS_EXTRA_LIBS ${PYTHON_LIBRARIES}) - MESSAGE(STATUS "Python ${PYTHON_VERSION} found (include: ${PYTHON_INCLUDE_DIRS})") + FIND_PACKAGE(Python3 COMPONENTS Interpreter Development REQUIRED) + if(Python3_FOUND) + INCLUDE_DIRECTORIES(${Python3_INCLUDE_DIRS}) + LIST(APPEND OpenMVS_EXTRA_INCLUDES ${Python3_INCLUDE_DIRS}) + LIST(APPEND OpenMVS_EXTRA_LIBS ${Python3_LIBRARIES}) + LIST(APPEND Boost_EXTRA_COMPONENTS python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}) + MESSAGE(STATUS "Python ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR} found (include: ${Python3_INCLUDE_DIRS})") else() MESSAGE("-- Can't find Python. Continuing without it.") endif() endif() -FIND_PACKAGE(Boost REQUIRED COMPONENTS iostreams program_options system serialization OPTIONAL_COMPONENTS python3) +FIND_PACKAGE(Boost REQUIRED COMPONENTS iostreams program_options system serialization OPTIONAL_COMPONENTS ${Boost_EXTRA_COMPONENTS}) if(Boost_FOUND) LIST(APPEND OpenMVS_EXTRA_INCLUDES ${Boost_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) @@ -195,12 +201,12 @@ if(Boost_FOUND) LIST(APPEND Boost_LIBRARIES zstd) endif() SET(_USE_BOOST TRUE) - if(Boost_python3_FOUND) + if(OpenMVS_USE_PYTHON AND Boost_${Boost_EXTRA_COMPONENTS}_FOUND) SET(_USE_BOOST_PYTHON TRUE) endif() endif() -FIND_PACKAGE(Eigen3 REQUIRED) +FIND_PACKAGE(Eigen3 3.4 REQUIRED) if(EIGEN3_FOUND) LIST(APPEND OpenMVS_EXTRA_INCLUDES ${EIGEN3_INCLUDE_DIR}) INCLUDE_DIRECTORIES(${EIGEN3_INCLUDE_DIR}) diff --git a/apps/DensifyPointCloud/DensifyPointCloud.cpp b/apps/DensifyPointCloud/DensifyPointCloud.cpp index 9a50c58ee..f314c7736 100644 --- a/apps/DensifyPointCloud/DensifyPointCloud.cpp +++ b/apps/DensifyPointCloud/DensifyPointCloud.cpp @@ -47,6 +47,7 @@ namespace { namespace OPT { String strInputFileName; +String strPointCloudFileName; String strOutputFileName; String strViewNeighborsFileName; String strOutputViewNeighborsFileName; @@ -73,8 +74,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -86,7 +96,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "produce this help message") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -127,6 +137,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description config("Densify options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("pointcloud-file,p", boost::program_options::value(&OPT::strPointCloudFileName), "sparse point-cloud with views file name to densify (overwrite existing point-cloud)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the dense point-cloud (optional)") ("view-neighbors-file", boost::program_options::value(&OPT::strViewNeighborsFileName), "input filename containing the list of views and their neighbors (optional)") ("output-view-neighbors-file", boost::program_options::value(&OPT::strOutputViewNeighborsFileName), "output filename containing the generated list of views and their neighbors") @@ -153,7 +164,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("estimate-roi", boost::program_options::value(&OPT::nEstimateROI)->default_value(2), "estimate and set region-of-interest (0 - disabled, 1 - enabled, 2 - adaptive)") ("crop-to-roi", boost::program_options::value(&OPT::bCrop2ROI)->default_value(true), "crop scene using the region-of-interest") ("remove-dmaps", boost::program_options::value(&bRemoveDmaps)->default_value(false), "remove depth-maps after fusion") - ("tower-mode", boost::program_options::value(&OPT::nTowerMode)->default_value(3), "add a cylinder of points in the center of ROI; scene assume to be Z-up oriented (0 - disabled, 1 - replace, 2 - append, 3 - select neighbors, <0 - force tower mode)") + ("tower-mode", boost::program_options::value(&OPT::nTowerMode)->default_value(4), "add a cylinder of points in the center of ROI; scene assume to be Z-up oriented (0 - disabled, 1 - replace, 2 - append, 3 - select neighbors, 4 - select neighbors & append, <0 - force tower mode)") ; // hidden options, allowed both on command line and @@ -211,6 +222,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) return false; // initialize optional options + Util::ensureValidPath(OPT::strPointCloudFileName); Util::ensureValidPath(OPT::strOutputFileName); Util::ensureValidPath(OPT::strViewNeighborsFileName); Util::ensureValidPath(OPT::strOutputViewNeighborsFileName); @@ -242,29 +254,14 @@ bool Initialize(size_t argc, LPCTSTR* argv) if (!bValidConfig && !OPT::strDenseConfigFileName.empty()) OPTDENSE::oConfig.Save(OPT::strDenseConfigFileName); - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -280,7 +277,8 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; Scene scene(OPT::nMaxThreads); @@ -296,12 +294,16 @@ int main(int argc, LPCTSTR* argv) scene.mesh.SamplePoints(ROUND2INT(-OPT::fSampleMesh), pointcloud); VERBOSE("Sample mesh completed: %u points (%s)", pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str()); pointcloud.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T(".ply")); - Finalize(); return EXIT_SUCCESS; } // load and estimate a dense point-cloud - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))); + if (sceneType == Scene::SCENE_NA) + return EXIT_FAILURE; + if (!OPT::strPointCloudFileName.empty() && !scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointCloudFileName))) { + VERBOSE("error: cannot load point-cloud file"); return EXIT_FAILURE; + } if (!OPT::strMaskPath.empty()) { Util::ensureValidFolderPath(OPT::strMaskPath); for (Image& image : scene.images) { @@ -322,7 +324,6 @@ int main(int argc, LPCTSTR* argv) return EXIT_FAILURE; fs >> scene.obb; scene.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); - Finalize(); return EXIT_SUCCESS; } if (!scene.IsBounded()) @@ -332,7 +333,6 @@ int main(int argc, LPCTSTR* argv) if (!fs) return EXIT_FAILURE; fs << scene.obb; - Finalize(); return EXIT_SUCCESS; } if (OPT::nTowerMode!=0) @@ -355,7 +355,6 @@ int main(int argc, LPCTSTR* argv) if (!scene.ExportMeshToDepthMaps(MAKE_PATH_SAFE(OPT::strExportDepthMapsName))) return EXIT_FAILURE; VERBOSE("Mesh projection completed: %u depth-maps (%s)", scene.images.size(), TD_TIMER_GET_FMT().c_str()); - Finalize(); return EXIT_SUCCESS; } if (OPT::fMaxSubsceneArea > 0) { @@ -363,7 +362,6 @@ int main(int argc, LPCTSTR* argv) Scene::ImagesChunkArr chunks; scene.Split(chunks, OPT::fMaxSubsceneArea); scene.ExportChunks(chunks, GET_PATH_FULL(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); - Finalize(); return EXIT_SUCCESS; } if (OPT::thFilterPointCloud < 0) { @@ -372,7 +370,6 @@ int main(int argc, LPCTSTR* argv) const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T("_filtered")); scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); scene.pointcloud.Save(baseFileName+_T(".ply")); - Finalize(); return EXIT_SUCCESS; } if (OPT::nExportNumViews && scene.pointcloud.IsValid()) { @@ -388,7 +385,6 @@ int main(int argc, LPCTSTR* argv) scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); scene.pointcloud.Save(baseFileName+_T(".ply")); } - Finalize(); return EXIT_SUCCESS; } if (OPT::fEstimateScale > 0 && !scene.pointcloud.IsEmpty() && !scene.images.empty()) { @@ -402,20 +398,21 @@ int main(int argc, LPCTSTR* argv) } const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); scene.pointcloud.SaveWithScale(baseFileName+_T("_scale.ply"), scene.images, OPT::fEstimateScale); - Finalize(); return EXIT_SUCCESS; } - if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS) { + PointCloud sparsePointCloud; + if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS || sceneType == Scene::SCENE_INTERFACE) { #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 1 && !scene.pointcloud.IsEmpty()) scene.pointcloud.PrintStatistics(scene.images.data(), &scene.obb); #endif + if ((ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + sparsePointCloud = scene.pointcloud; TD_TIMER_START(); if (!scene.DenseReconstruction(OPT::nFusionMode, OPT::bCrop2ROI, OPT::fBorderROI)) { if (ABS(OPT::nFusionMode) != 1) return EXIT_FAILURE; VERBOSE("Depth-maps estimated (%s)", TD_TIMER_GET_FMT().c_str()); - Finalize(); return EXIT_SUCCESS; } VERBOSE("Densifying point-cloud completed: %u points (%s)", scene.pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str()); @@ -423,14 +420,14 @@ int main(int argc, LPCTSTR* argv) // save the final point-cloud const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); - scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); - scene.pointcloud.Save(baseFileName+_T(".ply")); + scene.pointcloud.Save(baseFileName+_T(".ply"), (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+_T(".ply")); #endif - - Finalize(); + if ((ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + scene.pointcloud.Swap(sparsePointCloud); + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp index 03d16b2be..571c5d1e7 100644 --- a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp +++ b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp @@ -31,8 +31,6 @@ #include "../../libs/MVS/Common.h" #include "../../libs/MVS/Scene.h" -#define _USE_OPENCV -#include "../../libs/MVS/Interface.h" #include #include "endian.h" @@ -70,9 +68,9 @@ bool bFromOpenMVS; // conversion direction bool bNormalizeIntrinsics; bool bForceSparsePointCloud; String strInputFileName; +String strPointCloudFileName; String strOutputFileName; String strImageFolder; -unsigned nMaxResolution; unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -80,8 +78,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -93,7 +100,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "imports SfM or MVS scene stored in COLMAP undistoreted format OR exports MVS scene to COLMAP format") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -111,11 +118,11 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description config("Main options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input COLMAP folder containing cameras, images and points files OR input MVS project file") + ("pointcloud-file,p", boost::program_options::value(&OPT::strPointCloudFileName), "point-cloud with views file name (overwrite existing point-cloud)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the MVS project") ("image-folder", boost::program_options::value(&OPT::strImageFolder)->default_value(COLMAP_IMAGES_FOLDER), "folder to the undistorted images") - ("max-resolution", boost::program_options::value(&OPT::nMaxResolution)->default_value(0), "make sure image resolution are not not larger than this (0 - disabled)") ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(false), "normalize intrinsics while exporting to MVS format") - ("force-points,p", boost::program_options::value(&OPT::bForceSparsePointCloud)->default_value(false), "force exporting point-cloud as sparse points also even if dense point-cloud detected") + ("force-points,e", boost::program_options::value(&OPT::bForceSparsePointCloud)->default_value(false), "force exporting point-cloud as sparse points also even if dense point-cloud detected") ; boost::program_options::options_description cmdline_options; @@ -131,7 +138,6 @@ bool Initialize(size_t argc, LPCTSTR* argv) // parse command line options boost::program_options::store(boost::program_options::command_line_parser((int)argc, argv).options(cmdline_options).positional(p).run(), OPT::vm); boost::program_options::notify(OPT::vm); - Util::ensureValidPath(OPT::strInputFileName); INIT_WORKING_FOLDER; // parse configuration file std::ifstream ifs(MAKE_PATH_SAFE(OPT::strConfigFileName)); @@ -153,6 +159,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); // validate input + Util::ensureValidPath(OPT::strInputFileName); + Util::ensureValidPath(OPT::strPointCloudFileName); const bool bInvalidCommand(OPT::strInputFileName.empty()); if (OPT::vm.count("help") || bInvalidCommand) { boost::program_options::options_description visible("Available options"); @@ -182,29 +190,14 @@ bool Initialize(size_t argc, LPCTSTR* argv) OPT::strOutputFileName = OPT::strInputFileName + _T("scene") MVS_EXT; } - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -710,7 +703,7 @@ bool DetermineInputSource(const String& filenameTXT, const String& filenameBIN, } -bool ImportScene(const String& strFolder, const String& strOutFolder, Interface& scene) +bool ImportScene(const String& strFolder, const String& strOutFolder, Interface& scene, PointCloud& pointcloud) { // read camera list typedef std::unordered_map CamerasMap; @@ -742,10 +735,11 @@ bool ImportScene(const String& strFolder, const String& strOutFolder, Interface& Interface::Platform::Camera camera; camera.name = colmapCamera.model; camera.K = Interface::Mat33d::eye(); + // account for different pixel center conventions as COLMAP uses pixel center at (0.5,0.5) camera.K(0,0) = colmapCamera.params[0]; camera.K(1,1) = colmapCamera.params[1]; - camera.K(0,2) = colmapCamera.params[2]; - camera.K(1,2) = colmapCamera.params[3]; + camera.K(0,2) = colmapCamera.params[2]-REAL(0.5); + camera.K(1,2) = colmapCamera.params[3]-REAL(0.5); camera.R = Interface::Mat33d::eye(); camera.C = Interface::Pos3d(0,0,0); if (OPT::bNormalizeIntrinsics) { @@ -800,7 +794,7 @@ bool ImportScene(const String& strFolder, const String& strOutFolder, Interface& // read points list const String filenameDensePoints(strFolder+COLMAP_DENSE_POINTS); const String filenameDenseVisPoints(strFolder+COLMAP_DENSE_POINTS_VISIBILITY); - if (!File::access(filenameDensePoints) || !File::access(filenameDenseVisPoints)) { + { // parse sparse point-cloud const String filenamePointsTXT(strFolder+COLMAP_POINTS_TXT); const String filenamePointsBIN(strFolder+COLMAP_POINTS_BIN); @@ -811,7 +805,6 @@ bool ImportScene(const String& strFolder, const String& strOutFolder, Interface& return false; } LOG_OUT() << "Reading points: " << filenamePoints << std::endl; - COLMAP::Point point; while (file.good() && point.Read(file, binary)) { Interface::Vertex vertex; @@ -827,10 +820,11 @@ bool ImportScene(const String& strFolder, const String& strOutFolder, Interface& scene.vertices.emplace_back(std::move(vertex)); scene.verticesColor.emplace_back(Interface::Color{point.c}); } - } else { + } + pointcloud.Release(); + if (File::access(filenameDensePoints) && File::access(filenameDenseVisPoints)) { // parse dense point-cloud LOG_OUT() << "Reading points: " << filenameDensePoints << " and " << filenameDenseVisPoints << std::endl; - PointCloud pointcloud; if (!pointcloud.Load(filenameDensePoints)) { VERBOSE("error: unable to open file '%s'", filenameDensePoints.c_str()); return false; @@ -846,22 +840,17 @@ bool ImportScene(const String& strFolder, const String& strOutFolder, Interface& VERBOSE("error: point-cloud and visibility have different size"); return false; } + pointcloud.pointViews.resize(numPoints); for (size_t i=0; iGetWidth(); cam.height = ptrImage->GetHeight(); // unnormalize camera intrinsics - const Interface::Mat33d K(platform.GetFullK(0, cam.width, cam.height)); - cam.params[0] = K(0,0); - cam.params[1] = K(1,1); - cam.params[2] = K(0,2); - cam.params[3] = K(1,2); + K = platform.GetFullK(0, cam.width, cam.height); } else { cam.width = camera.width; cam.height = camera.height; - cam.params[0] = camera.K(0,0); - cam.params[1] = camera.K(1,1); - cam.params[2] = camera.K(0,2); - cam.params[3] = camera.K(1,2); + K = camera.K; } + // account for different pixel center conventions as COLMAP uses pixel center at (0.5,0.5) + cam.params[0] = K(0,0); + cam.params[1] = K(1,1); + cam.params[2] = K(0,2)+REAL(0.5); + cam.params[3] = K(1,2)+REAL(0.5); if (!cam.Write(file, binary)) return false; - KMatrix& K = Ks.emplace_back(KMatrix::IDENTITY); - K(0,0) = cam.params[0]; - K(1,1) = cam.params[1]; - K(0,2) = cam.params[2]; - K(1,2) = cam.params[3]; + Ks.emplace_back(K); cams.emplace_back(cam); } } @@ -1045,7 +1069,7 @@ bool ExportScene(const String& strFolder, const Interface& scene, bool bForceSpa cameras.resize((unsigned)scene.images.size()); for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { const Interface::Image& image = scene.images[ID]; - if (image.poseID == MVS::NO_ID) + if (image.poseID == NO_ID) continue; const Interface::Platform& platform = scene.platforms[image.platformID]; const Interface::Platform::Pose& pose = platform.poses[image.poseID]; @@ -1099,6 +1123,9 @@ bool ExportScene(const String& strFolder, const Interface& scene, bool bForceSpa proj.idPoint = ID; const Point3 X(vertex.X); ProjectVertex_3x4_3_2(cameras[view.imageID].P.val, X.ptr(), proj.p.data()); + // account for different pixel center conventions as COLMAP uses pixel center at (0.5,0.5) + proj.p[0] += REAL(0.5); + proj.p[1] += REAL(0.5); img.projs.emplace_back(proj); } point.c = scene.verticesColor.empty() ? Interface::Col3(255,255,255) : scene.verticesColor[ID].c; @@ -1184,7 +1211,7 @@ bool ExportScene(const String& strFolder, const Interface& scene, bool bForceSpa file.write(&view.imageID, sizeof(uint32_t)); } } - if (!pointcloud.Save(filenameDensePoints, true)) { + if (!pointcloud.Save(filenameDensePoints, false, true)) { VERBOSE("error: unable to write file '%s'", filenameDensePoints.c_str()); return false; } @@ -1230,12 +1257,12 @@ bool ExportScene(const String& strFolder, const Interface& scene, bool bForceSpa bool ExportIntrinsicsTxt(const String& fileName, const Interface& scene) { LOG_OUT() << "Writing intrinsics: " << fileName << std::endl; - uint32_t idxValidK(MVS::NO_ID); + uint32_t idxValidK(NO_ID); for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { const Interface::Image& image = scene.images[ID]; if (!image.IsValid()) continue; - if (idxValidK == MVS::NO_ID) { + if (idxValidK == NO_ID) { idxValidK = ID; continue; } @@ -1244,7 +1271,7 @@ bool ExportIntrinsicsTxt(const String& fileName, const Interface& scene) return false; } } - if (idxValidK == MVS::NO_ID) + if (idxValidK == NO_ID) return false; const Interface::Image& image = scene.images[idxValidK]; String imagefileName(image.name); @@ -1295,7 +1322,7 @@ bool ExportImagesLog(const String& fileName, const Interface& scene) const Interface::Image& image = scene.images[ID]; Eigen::Matrix3d R(Eigen::Matrix3d::Identity()); Eigen::Vector3d t(Eigen::Vector3d::Zero()); - if (image.poseID != MVS::NO_ID) { + if (image.poseID != NO_ID) { const Interface::Platform& platform = scene.platforms[image.platformID]; const Interface::Platform::Pose& pose = platform.poses[image.poseID]; R = Eigen::Map(pose.R.val).transpose(); @@ -1336,7 +1363,7 @@ bool ExportImagesCamera(const String& pathName, const Interface& scene) RMatrix R(RMatrix::IDENTITY); CMatrix t(CMatrix::ZERO); unsigned width(0), height(0); - if (image.platformID != MVS::NO_ID && image.cameraID != MVS::NO_ID) { + if (image.platformID != NO_ID && image.cameraID != NO_ID) { const Interface::Platform& platform = scene.platforms[image.platformID]; const Interface::Platform::Camera& camera = platform.cameras[image.cameraID]; if (camera.HasResolution()) { @@ -1349,7 +1376,7 @@ bool ExportImagesCamera(const String& pathName, const Interface& scene) height = pImage->GetHeight(); K = platform.GetFullK(image.cameraID, width, height); } - if (image.poseID != MVS::NO_ID) { + if (image.poseID != NO_ID) { const Interface::Platform::Pose& pose = platform.poses[image.poseID]; R = pose.R.t(); t = pose.C; @@ -1381,29 +1408,13 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; TD_TIMER_START(); if (OPT::bFromOpenMVS) { - if (OPT::nMaxResolution > 0) { - // scale and save scene images - MVS::Scene scene(OPT::nMaxThreads); - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) - return EXIT_FAILURE; - const String folderName(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)) + String::FormatString("images%u" PATH_SEPARATOR_STR, OPT::nMaxResolution)); - if (!scene.ScaleImages(OPT::nMaxResolution, 0, folderName)) { - DEBUG("error: can not scale scene images to '%s'", folderName.c_str()); - return EXIT_FAILURE; - } - const String fileName(Util::insertBeforeFileExt(MAKE_PATH_SAFE(OPT::strInputFileName), _T("_new"))); - if (!scene.Save(fileName, scene.mesh.IsEmpty() ? ARCHIVE_MVS : ARCHIVE_DEFAULT)) { - DEBUG("error: can not save scene to '%s'", fileName.c_str()); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; - } // read MVS input data Interface scene; if (!ARCHIVE::SerializeLoad(scene, MAKE_PATH_SAFE(OPT::strInputFileName))) @@ -1418,6 +1429,8 @@ int main(int argc, LPCTSTR* argv) ExportImagesCamera((OPT::strOutputFileName=Util::getFileFullName(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName)))+PATH_SEPARATOR, scene); } else { // write COLMAP input data + if (!OPT::strPointCloudFileName.empty() && !ImportPointCloud(MAKE_PATH_SAFE(OPT::strPointCloudFileName), scene)) + return EXIT_FAILURE; Util::ensureFolderSlash(OPT::strOutputFileName); ExportScene(MAKE_PATH_SAFE(OPT::strOutputFileName), scene, OPT::bForceSparsePointCloud); } @@ -1426,16 +1439,20 @@ int main(int argc, LPCTSTR* argv) // read COLMAP input data Interface scene; const String strOutFolder(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName))); - if (!ImportScene(MAKE_PATH_SAFE(OPT::strInputFileName), strOutFolder, scene)) + PointCloud pointcloud; + if (!ImportScene(MAKE_PATH_SAFE(OPT::strInputFileName), strOutFolder, scene, pointcloud)) return EXIT_FAILURE; // write MVS input data Util::ensureFolder(strOutFolder); if (!ARCHIVE::SerializeSave(scene, MAKE_PATH_SAFE(OPT::strOutputFileName))) return EXIT_FAILURE; - VERBOSE("Exported data: %u images & %u vertices (%s)", scene.images.size(), scene.vertices.size(), TD_TIMER_GET_FMT().c_str()); + if (!pointcloud.IsEmpty() && !pointcloud.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName)) + _T(".ply"), true)) + return EXIT_FAILURE; + VERBOSE("Exported data: %u images, %u points%s (%s)", + scene.images.size(), scene.vertices.size(), pointcloud.IsEmpty()?"":String::FormatString(", %d dense points", pointcloud.GetSize()).c_str(), + TD_TIMER_GET_FMT().c_str()); } - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/InterfaceMVSNet/InterfaceMVSNet.cpp b/apps/InterfaceMVSNet/InterfaceMVSNet.cpp index 31da44f71..5db3f8528 100644 --- a/apps/InterfaceMVSNet/InterfaceMVSNet.cpp +++ b/apps/InterfaceMVSNet/InterfaceMVSNet.cpp @@ -31,6 +31,8 @@ #include "../../libs/MVS/Common.h" #include "../../libs/MVS/Scene.h" +#define JSON_NOEXCEPTION +#include "../../libs/IO/json.hpp" #include using namespace MVS; @@ -38,12 +40,19 @@ using namespace MVS; // D E F I N E S /////////////////////////////////////////////////// +// uncomment to enable multi-threading based on OpenMP +#ifdef _USE_OPENMP +#define MVSNET_USE_OPENMP +#endif + #define APPNAME _T("InterfaceMVSNet") #define MVS_EXT _T(".mvs") #define MVSNET_IMAGES_FOLDER _T("images") #define MVSNET_CAMERAS_FOLDER _T("cams") #define MVSNET_IMAGES_EXT _T(".jpg") #define MVSNET_CAMERAS_NAME _T("_cam.txt") +#define RTMV_CAMERAS_EXT _T(".json") +#define NERFSTUDIO_TRANSFORMS _T("transforms.json") // S T R U C T S /////////////////////////////////////////////////// @@ -60,8 +69,17 @@ namespace OPT { boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -73,7 +91,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "produce this help message") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -141,30 +159,17 @@ bool Initialize(size_t argc, LPCTSTR* argv) // initialize optional options Util::ensureValidPath(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) + if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFileName(OPT::strInputFileName) + "scene" MVS_EXT; - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -210,6 +215,20 @@ void ImageListParse(const LPSTR* argv, Matrix3x4& P) P(2, 3) = String::FromString(argv[11]); } +// convert a range-map to depth-map +void RangeToDepthMap(const Image32F& rangeMap, const Camera& camera, DepthMap& depthMap) +{ + depthMap.create(rangeMap.size()); + for (int y = 0; y < depthMap.rows; ++y) { + const float* const rangeRow = rangeMap.ptr(y); + float* const depthRow = depthMap.ptr(y); + for (int x = 0; x < depthMap.cols; ++x) { + const float range = rangeRow[x]; + depthRow[x] = (Depth)(range <= 0 ? 0 : normalized(camera.TransformPointI2C(Point2(x,y))).z*range); + } + } +} + // parse scene stored in MVSNet format composed of undistorted images and camera poses // for example see GigaVision benchmark (http://gigamvs.net): // |--sceneX @@ -217,17 +236,14 @@ void ImageListParse(const LPSTR* argv, Matrix3x4& P) // |--xxx.jpg // |--xxx.jpg // .... -// |--xxx.jpg // |--cams // |--xxx_cam.txt // |--xxx_cam.txt // .... -// |--xxx_cam.txt // |--render_cams // |--xxx_cam.txt // |--xxx_cam.txt // .... -// |--xxx_cam.txt // // where the camera parameter of one image stored in a cam.txt file contains the camera // extrinsic E = [R|t], intrinsic K and the depth range: @@ -243,18 +259,15 @@ void ImageListParse(const LPSTR* argv, Matrix3x4& P) // K20 K21 K22 // // DEPTH_MIN DEPTH_INTERVAL (DEPTH_NUM DEPTH_MAX) -bool ParseSceneMVSNet(Scene& scene) +bool ParseSceneMVSNet(Scene& scene, const String& strPath) { #if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) - String strPath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)); - Util::ensureValidFolderPath(strPath); - const std::filesystem::path path(static_cast(strPath)); IIndex prevPlatformID = NO_ID; - for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(path / MVSNET_IMAGES_FOLDER)) { + for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator((strPath + MVSNET_IMAGES_FOLDER).c_str())) { if (entry.path().extension() != MVSNET_IMAGES_EXT) continue; // parse camera - const std::string strCamFileName((path / MVSNET_CAMERAS_FOLDER / entry.path().stem()).string() + MVSNET_CAMERAS_NAME); + const std::string strCamFileName(strPath + MVSNET_CAMERAS_FOLDER PATH_SEPARATOR_STR + entry.path().stem().string().c_str() + MVSNET_CAMERAS_NAME); std::ifstream fcam(strCamFileName); if (!fcam) continue; @@ -335,6 +348,332 @@ bool ParseSceneMVSNet(Scene& scene) #endif // _SUPPORT_CPP17 } +// RTMV scene format: http://www.cs.umd.edu/~mmeshry/projects/rtmv +// |--sceneX +// |--images +// |--xxx.jpg +// |--xxx.jpg +// .... +// |--outputs (optional) +// |--depthxxxx.exr +// |--normalxxxx.exr +// .... +// |--transforms.json +bool ParseSceneNerfstudio(Scene& scene, const String& strPath) +{ + const nlohmann::json data = nlohmann::json::parse(std::ifstream(strPath + NERFSTUDIO_TRANSFORMS)); + if (data.empty()) + return false; + // parse camera + const cv::Size resolution(data["w"].get(), data["h"].get()); + const IIndex platformID = scene.platforms.size(); + Platform& platform = scene.platforms.emplace_back(); + Platform::Camera& camera = platform.cameras.emplace_back(); + camera.K = KMatrix::IDENTITY; + camera.R = RMatrix::IDENTITY; + camera.C = CMatrix::ZERO; + camera.K(0,0) = data["fl_x"].get(); + camera.K(1,1) = data["fl_y"].get(); + camera.K(0,2) = data["cx"].get(); + camera.K(1,2) = data["cy"].get(); + const String cameraModel = data["camera_model"].get(); + if (cameraModel == "SIMPLE_PINHOLE") { + } else + // check ZERO radial distortion for all "PERSPECTIVE" type cameras + if (cameraModel == "PINHOLE" || cameraModel == "SIMPLE_RADIAL" || cameraModel == "RADIAL" || cameraModel == "OPENCV") { + const REAL k1 = data["k1"].get(); + const REAL k2 = data["k2"].get(); + const REAL p1 = data["p1"].get(); + const REAL p2 = data["p2"].get(); + if (k1 != 0 || k2 != 0 || p1 != 0 || p2 != 0) { + VERBOSE("error: radial distortion not supported"); + return false; + } + } else { + VERBOSE("error: camera model not supported"); + return false; + } + // parse images + const nlohmann::json& frames = data["frames"]; + for (const nlohmann::json& frame: frames) { + // set image + // frames expected to be ordered in JSON + const IIndex imageID = scene.images.size(); + const String strFileName(strPath + frame["file_path"].get().c_str()); + Image& imageData = scene.images.emplace_back(); + imageData.platformID = platformID; + imageData.cameraID = 0; // only one camera per platform supported by this format + imageData.poseID = NO_ID; + imageData.ID = imageID; + imageData.name = strFileName; + ASSERT(Util::isFullPath(imageData.name)); + // set image resolution + imageData.width = resolution.width; + imageData.height = resolution.height; + imageData.scale = 1; + // load camera pose + imageData.poseID = platform.poses.size(); + Platform::Pose& pose = platform.poses.emplace_back(); + const auto Ps = frame["transform_matrix"].get>>(); + Eigen::Matrix4d P{ + {Ps[0][0], Ps[0][1], Ps[0][2], Ps[0][3]}, + {Ps[1][0], Ps[1][1], Ps[1][2], Ps[1][3]}, + {Ps[2][0], Ps[2][1], Ps[2][2], Ps[2][3]}, + {Ps[3][0], Ps[3][1], Ps[3][2], Ps[3][3]} + }; + // revert nerfstudio conversion: + // convert from COLMAP's camera coordinate system (OpenCV) to ours (OpenGL) + // c2w[0:3, 1:3] *= -1 + // c2w = c2w[np.array([1, 0, 2, 3]), :] + // c2w[2, :] *= -1 + P.row(2) *= -1; + P.row(0).swap(P.row(1)); + P.col(2) *= -1; + P.col(1) *= -1; + // set camera pose + pose.R = P.topLeftCorner<3, 3>().transpose().eval(); + pose.R.EnforceOrthogonality(); + pose.C = P.topRightCorner<3, 1>().eval(); + imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); + // try reading depth-map and normal-map + DepthMap depthMap; { + const String depthPath(strPath + String::FormatString("outputs/depth%04u.exr", imageID)); + const Image32F rangeMap = cv::imread(depthPath, cv::IMREAD_UNCHANGED); + if (rangeMap.empty()) { + VERBOSE("Unable to load depthmap %s.", depthPath.c_str()); + continue; + } + RangeToDepthMap(rangeMap, imageData.camera, depthMap); + } + NormalMap normalMap; { + const String normalPath(strPath + String::FormatString("outputs/normal%04u.exr", imageID)); + normalMap = cv::imread(normalPath, cv::IMREAD_UNCHANGED); + if (normalMap.empty()) { + VERBOSE("Unable to load normalMap %s.", normalPath.c_str()); + continue; + } + } + const ConfidenceMap confMap; + const ViewsMap viewsMap; + const IIndexArr IDs = {imageID}; + double dMin, dMax; + cv::minMaxIdx(depthMap, &dMin, &dMax, NULL, NULL, depthMap > 0); + const String dmapPath(strPath + String::FormatString("depth%04u.dmap", imageID)); + if (!ExportDepthDataRaw(dmapPath, + imageData.name, IDs, resolution, + camera.K, pose.R, pose.C, + (float)dMin, (float)dMax, + depthMap, normalMap, confMap, viewsMap)) + { + VERBOSE("Unable to save dmap: %s", dmapPath.c_str()); + continue; + } + } + if (scene.images.size() < 2) + return false; + scene.nCalibratedImages = (unsigned)scene.images.size(); + return true; +} + +// RTMV scene format: http://www.cs.umd.edu/~mmeshry/projects/rtmv +// |--sceneX +// |--xxx.exr +// |--xxx.seg.exr +// |--xxx.depth.exr +// |--xxx.json +// .... +bool ParseSceneRTMV(Scene& scene, const String& strPath) +{ + const String strImagePath(strPath + "images/"); + Util::ensureFolder(strImagePath); + std::vector strImageNames; + #if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) + for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(strPath.c_str())) { + if (entry.path().extension() != RTMV_CAMERAS_EXT) + continue; + strImageNames.emplace_back(entry.path().stem().string()); + } + #else + VERBOSE("error: C++17 is required to parse RTMV format"); + return false; + #endif // _SUPPORT_CPP17 + IIndex prevPlatformID = NO_ID; + scene.images.resize((IIndex)strImageNames.size()); + scene.platforms.reserve((IIndex)strImageNames.size()); + #ifdef MVSNET_USE_OPENMP + #pragma omp parallel for schedule(dynamic) + for (int_t i=0; i<(int_t)strImageNames.size(); ++i) { + #else + FOREACH(i, strImageNames) { + #endif + const IIndex imageID((IIndex)i); + const String& strImageName(strImageNames[imageID]); + // parse camera + const String strFileName(strPath + strImageName); + const nlohmann::json dataCamera = nlohmann::json::parse(std::ifstream(strFileName+RTMV_CAMERAS_EXT)); + if (dataCamera.empty()) + continue; + const nlohmann::json& data = dataCamera["camera_data"]; + const cv::Size resolution(data["width"].get(), data["height"].get()); + // set platform + Matrix3x3 K = Matrix3x3::IDENTITY; + K(0,0) = data["intrinsics"]["fx"].get(); + K(1,1) = data["intrinsics"]["fy"].get(); + K(0,2) = data["intrinsics"]["cx"].get(); + K(1,2) = data["intrinsics"]["cy"].get(); + IIndex platformID; + if (prevPlatformID == NO_ID || !K.IsEqual(scene.platforms[prevPlatformID].cameras[0].K, 1e-3)) { + #ifdef MVSNET_USE_OPENMP + #pragma omp critical + #endif + { + prevPlatformID = platformID = scene.platforms.size(); + Platform& platform = scene.platforms.emplace_back(); + Platform::Camera& camera = platform.cameras.emplace_back(); + platform.poses.reserve((IIndex)strImageNames.size()); + camera.K = K; + camera.R = RMatrix::IDENTITY; + camera.C = CMatrix::ZERO; + } + } else { + platformID = prevPlatformID; + } + Platform& platform = scene.platforms[platformID]; + // set image + Image& imageData = scene.images[imageID]; + imageData.platformID = platformID; + imageData.cameraID = 0; // only one camera per platform supported by this format + imageData.poseID = NO_ID; + imageData.ID = imageID; + imageData.name = strImagePath+strImageName+".jpg"; + ASSERT(Util::isFullPath(imageData.name)); + { + cv::Mat image = cv::imread(strFileName+".exr", cv::IMREAD_UNCHANGED); + ASSERT(image.type() == CV_32FC4); + std::vector channels; + cv::split(image, channels); + cv::merge(std::vector{channels[0], channels[1], channels[2]}, image); + image.convertTo(imageData.image, CV_8UC3, 255); + } + ASSERT(resolution == imageData.image.size()); + if (imageData.image.empty()) { + VERBOSE("Unable to load image %s.", (strFileName+".exr").c_str()); + continue; + } + cv::imwrite(imageData.name, imageData.image); + imageData.ReleaseImage(); + // set image resolution + imageData.width = resolution.width; + imageData.height = resolution.height; + imageData.scale = 1; + // load camera pose + #ifdef MVSNET_USE_OPENMP + #pragma omp critical + #endif + { + imageData.poseID = platform.poses.size(); + platform.poses.emplace_back(); + } + Platform::Pose& pose = platform.poses[imageData.poseID]; + const auto Ps = data["cam2world"].get>>(); + Eigen::Matrix4d P{ + {Ps[0][0], Ps[1][0], Ps[2][0], Ps[3][0]}, + {Ps[0][1], Ps[1][1], Ps[2][1], Ps[3][1]}, + {Ps[0][2], Ps[1][2], Ps[2][2], Ps[3][2]}, + {Ps[0][3], Ps[1][3], Ps[2][3], Ps[3][3]} + }; + // apply the same transforms as nerfstudio converter + P.row(2) *= -1; + P.row(0).swap(P.row(1)); + P.col(2) *= -1; + P.col(1) *= -1; + // set camera pose + pose.R = P.topLeftCorner<3, 3>().transpose().eval(); + pose.R.EnforceOrthogonality(); + pose.C = P.topRightCorner<3, 1>().eval(); + imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); + // try reading the segmentation mask + { + cv::Mat imgMask = cv::imread(strFileName+".seg.exr", cv::IMREAD_UNCHANGED); + if (imgMask.empty()) { + VERBOSE("Unable to load segmentation mask %s.", (strFileName+".seg.exr").c_str()); + continue; + } + ASSERT(imgMask.type() == CV_32FC4); + ASSERT(resolution == imgMask.size()); + std::vector channels; + cv::split(imgMask, channels); + channels[0].convertTo(imgMask, CV_16U); + imageData.maskName = strImagePath+strImageName+".mask.png"; + cv::imwrite(imageData.maskName, imgMask); + } + // try reading the depth-map + DepthMap depthMap; { + const cv::Mat imgDepthMap = cv::imread(strFileName+".depth.exr", cv::IMREAD_UNCHANGED); + if (imgDepthMap.empty()) { + VERBOSE("Unable to load depthmap %s.", (strFileName+".depth.exr").c_str()); + continue; + } + ASSERT(imgDepthMap.type() == CV_32FC4); + ASSERT(resolution == imgDepthMap.size()); + std::vector channels; + cv::split(imgDepthMap, channels); + RangeToDepthMap(channels[0], imageData.camera, depthMap); + } + const NormalMap normalMap; + const ConfidenceMap confMap; + const ViewsMap viewsMap; + const IIndexArr IDs = {imageID}; + double dMin, dMax; + cv::minMaxIdx(depthMap, &dMin, &dMax, NULL, NULL, depthMap > 0); + const String dmapPath(strPath + String::FormatString("depth%04u.dmap", imageID)); + if (!ExportDepthDataRaw(dmapPath, + imageData.name, IDs, resolution, + K, pose.R, pose.C, + (float)dMin, (float)dMax, + depthMap, normalMap, confMap, viewsMap)) + { + VERBOSE("Unable to save dmap: %s", dmapPath.c_str()); + continue; + } + } + if (scene.images.size() < 2) + return false; + scene.nCalibratedImages = (unsigned)scene.images.size(); + return true; +} + +bool ParseScene(Scene& scene) +{ + #if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) + String strPath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)); + Util::ensureValidFolderPath(strPath); + const std::filesystem::path path(static_cast(strPath)); + enum Type { + MVSNet = 0, + NERFSTUDIO, + RTMV, + } sceneType = MVSNet; + for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(path)) { + if (entry.path().extension() == RTMV_CAMERAS_EXT) { + if (entry.path().filename() == NERFSTUDIO_TRANSFORMS) { + sceneType = NERFSTUDIO; + break; + } + sceneType = RTMV; + } + } + switch (sceneType) { + case NERFSTUDIO: return ParseSceneNerfstudio(scene, strPath); + case RTMV: return ParseSceneRTMV(scene, strPath); + default: return ParseSceneMVSNet(scene, strPath); + } + #else + VERBOSE("error: C++17 is required to parse MVSNet format"); + return false; + #endif // _SUPPORT_CPP17 +} + } // unnamed namespace int main(int argc, LPCTSTR* argv) @@ -344,7 +683,8 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; TD_TIMER_START(); @@ -352,7 +692,7 @@ int main(int argc, LPCTSTR* argv) Scene scene(OPT::nMaxThreads); // convert data from MVSNet format to OpenMVS - if (!ParseSceneMVSNet(scene)) + if (!ParseScene(scene)) return EXIT_FAILURE; // write OpenMVS input data @@ -361,8 +701,6 @@ int main(int argc, LPCTSTR* argv) VERBOSE("Imported data: %u platforms, %u images, %u vertices (%s)", scene.platforms.size(), scene.images.size(), scene.pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str()); - - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/InterfaceMetashape/InterfaceMetashape.cpp b/apps/InterfaceMetashape/InterfaceMetashape.cpp index 068e9505c..03a80ce87 100644 --- a/apps/InterfaceMetashape/InterfaceMetashape.cpp +++ b/apps/InterfaceMetashape/InterfaceMetashape.cpp @@ -61,8 +61,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -74,7 +83,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "imports SfM scene stored either in Metashape Agisoft/BlocksExchange or ContextCapture BlocksExchange XML format") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -148,27 +157,14 @@ bool Initialize(size_t argc, LPCTSTR* argv) if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFileName(OPT::strInputFileName) + MVS_EXT; - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -605,9 +601,9 @@ bool ParseBlocksExchangeXML(tinyxml2::XMLDocument& doc, Scene& scene, PlatformDi scene.pointcloud.points.back() -= Cast(center); if ((elem=tiepoint->FirstChildElement("Color")) != NULL) scene.pointcloud.colors.emplace_back( - (uint8_t)std::clamp(elem->FirstChildElement("Blue")->DoubleText()*255, 0.0, 255.0), - (uint8_t)std::clamp(elem->FirstChildElement("Green")->DoubleText()*255, 0.0, 255.0), - (uint8_t)std::clamp(elem->FirstChildElement("Red")->DoubleText()*255, 0.0, 255.0)); + (uint8_t)CLAMP(elem->FirstChildElement("Red")->DoubleText()*255, 0.0, 255.0), + (uint8_t)CLAMP(elem->FirstChildElement("Green")->DoubleText()*255, 0.0, 255.0), + (uint8_t)CLAMP(elem->FirstChildElement("Blue")->DoubleText()*255, 0.0, 255.0)); PointCloud::ViewArr views; for (const tinyxml2::XMLElement* view=tiepoint->FirstChildElement("Measurement"); view!=NULL; view=view->NextSiblingElement()) views.emplace_back(mapImageID.at(view->FirstChildElement("PhotoId")->UnsignedText())); @@ -678,7 +674,7 @@ bool UndistortBrown(Image& imageData, uint32_t ID, const DistCoeff& dc, const St // save undistorted image imageData.image = imgUndist; - imageData.name = pathData + String::FormatString(_T("%05u.png"), ID); + imageData.name = pathData + String::FormatString(_T("%05u.jpg"), ID); Util::ensureFolder(imageData.name); return imageData.image.Save(imageData.name); } @@ -786,7 +782,8 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; TD_TIMER_START(); @@ -885,8 +882,6 @@ int main(int argc, LPCTSTR* argv) scene.platforms.size(), nCameras, nPoses, scene.images.size(), scene.pointcloud.GetSize(), acc.minVal, acc.GetMean(), acc.GetStdDev(), acc.maxVal, TD_TIMER_GET_FMT().c_str()); - - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp b/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp index 6902a0974..fa3dc6b33 100644 --- a/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp +++ b/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp @@ -339,8 +339,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -446,27 +455,14 @@ bool Initialize(size_t argc, LPCTSTR* argv) OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + MVS_EXT; } - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -482,7 +478,8 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; TD_TIMER_START(); @@ -753,7 +750,6 @@ int main(int argc, LPCTSTR* argv) TD_TIMER_GET_FMT().c_str()); } - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/InterfacePolycam/InterfacePolycam.cpp b/apps/InterfacePolycam/InterfacePolycam.cpp index 9100b1b5b..46ba1b602 100644 --- a/apps/InterfacePolycam/InterfacePolycam.cpp +++ b/apps/InterfacePolycam/InterfacePolycam.cpp @@ -31,6 +31,7 @@ #include "../../libs/MVS/Common.h" #include "../../libs/MVS/Scene.h" +#define JSON_NOEXCEPTION #include "../../libs/IO/json.hpp" #include @@ -59,8 +60,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -72,7 +82,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "imports SfM scene stored Polycam format") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -142,27 +152,14 @@ bool Initialize(size_t argc, LPCTSTR* argv) if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = "scene" MVS_EXT; - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -236,56 +233,59 @@ bool ParseImage(Scene& scene, const String& imagePath, const String& cameraPath, const String neighborName = std::to_string(timestamp); const IIndex neighborID = mapImageName.at(neighborName); if (neighborID != imageData.ID) - imageData.neighbors.emplace_back(ViewScore{ViewInfo{neighborID, 0, 1.f, FD2R(15.f), 0.5f}, 3.f}); + imageData.neighbors.emplace_back(ViewScore{neighborID, 0, 1.f, FD2R(15.f), 0.5f, 3.f}); } } // load and convert depth-map - DepthMap depthMap; { - constexpr double depthScale{1000.0}; - const cv::Mat imgDepthMap = cv::imread(depthPath, cv::IMREAD_ANYDEPTH); - if (imgDepthMap.empty()) + if (!depthPath.empty()) { + DepthMap depthMap; { + constexpr double depthScale{1000.0}; + const cv::Mat imgDepthMap = cv::imread(depthPath, cv::IMREAD_ANYDEPTH); + if (imgDepthMap.empty()) + return false; + imgDepthMap.convertTo(depthMap, CV_32FC1, 1.0/depthScale); + } + IIndexArr IDs = {imageData.ID}; + IDs.JoinFunctor(imageData.neighbors.size(), [&imageData](IIndex i) { + return imageData.neighbors[i].ID; + }); + double dMin, dMax; + cv::minMaxIdx(depthMap, &dMin, &dMax, NULL, NULL, depthMap > 0); + const NormalMap normalMap; + const ConfidenceMap confMap; + const ViewsMap viewsMap; + if (!ExportDepthDataRaw(MAKE_PATH(String::FormatString("depth%04u.dmap", imageData.ID)), + imageData.name, IDs, resolution, + camera.K, pose.R, pose.C, + (float)dMin, (float)dMax, + depthMap, normalMap, confMap, viewsMap)) return false; - imgDepthMap.convertTo(depthMap, CV_32FC1, 1.0/depthScale); } - IIndexArr IDs = {imageData.ID}; - IDs.JoinFunctor(imageData.neighbors.size(), [&imageData](IIndex i) { - return imageData.neighbors[i].idx.ID; - }); - double dMin, dMax; - cv::minMaxIdx(depthMap, &dMin, &dMax, NULL, NULL, depthMap > 0); - const NormalMap normalMap; - const ConfidenceMap confMap; - const ViewsMap viewsMap; - if (!ExportDepthDataRaw(MAKE_PATH(String::FormatString("depth%04u.dmap", imageData.ID)), - imageData.name, IDs, resolution, - camera.K, pose.R, pose.C, - (float)dMin, (float)dMax, - depthMap, normalMap, confMap, viewsMap)) - return false; return true; } // parse scene stored in Polycam format bool ParseScene(Scene& scene, const String& scenePath) { - #ifdef _SUPPORT_CPP17 - size_t numCorrectedFolders(0), numFolders(0); + #if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) + size_t numCorrectedFolders(0), numCorrectedDepthFolders(0), numFolders(0), numDepthFolders(0); for (const auto& file: std::filesystem::directory_iterator(scenePath.c_str())) { if (file.path().stem() == "corrected_cameras" || - file.path().stem() == "corrected_depth" || file.path().stem() == "corrected_images") ++numCorrectedFolders; - else - if (file.path().stem() == "cameras" || - file.path().stem() == "depth" || + else if (file.path().stem() == "corrected_depth") + ++numCorrectedDepthFolders; + else if (file.path().stem() == "cameras" || file.path().stem() == "images") ++numFolders; + else if (file.path().stem() == "depth") + ++numDepthFolders; } - if (numFolders != 3) { + if (numFolders != 2) { VERBOSE("Invalid scene folder"); return false; } - if (numCorrectedFolders == 3) { + if (numCorrectedFolders == 2) { // corrected data CLISTDEFIDX(String, IIndex) imagePaths; for (const auto& file: std::filesystem::directory_iterator((scenePath + "corrected_images").c_str())) @@ -300,7 +300,7 @@ bool ParseScene(Scene& scene, const String& scenePath) for (const String& imagePath: imagePaths) { const String imageName = Util::getFileName(imagePath); const String cameraPath(scenePath + "corrected_cameras" + PATH_SEPARATOR_STR + imageName + JSON_EXT); - const String depthPath(scenePath + "corrected_depth" + PATH_SEPARATOR_STR + imageName + DEPTH_EXT); + const String depthPath(numCorrectedDepthFolders ? scenePath + "corrected_depth" + PATH_SEPARATOR_STR + imageName + DEPTH_EXT : String()); if (!ParseImage(scene, imagePath, cameraPath, depthPath, mapImageName)) return false; } @@ -319,7 +319,7 @@ bool ParseScene(Scene& scene, const String& scenePath) for (const String& imagePath: imagePaths) { const String imageName = Util::getFileName(imagePath); const String cameraPath(scenePath + "cameras" + PATH_SEPARATOR_STR + imageName + JSON_EXT); - const String depthPath(scenePath + "depth" + PATH_SEPARATOR_STR + imageName + DEPTH_EXT); + const String depthPath(numDepthFolders ? scenePath + "depth" + PATH_SEPARATOR_STR + imageName + DEPTH_EXT : String()); if (!ParseImage(scene, imagePath, cameraPath, depthPath, mapImageName)) return false; } @@ -339,7 +339,8 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; TD_TIMER_START(); @@ -356,8 +357,6 @@ int main(int argc, LPCTSTR* argv) VERBOSE("Exported data: %u platforms, %u cameras, %u poses, %u images (%s)", scene.platforms.size(), scene.images.size(), scene.images.size(), scene.images.size(), TD_TIMER_GET_FMT().c_str()); - - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp b/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp index 3bab64dc1..0f3d832d4 100644 --- a/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp +++ b/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp @@ -62,8 +62,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -160,29 +169,14 @@ bool Initialize(size_t argc, LPCTSTR* argv) OPT::strOutputImageFolder = Util::getRelativePath(Util::getFilePath(OPT::strOutputFileName), Util::getFilePath(OPT::strInputFileName)+OPT::strOutputImageFolder); } - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -590,7 +584,8 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; if (OPT::bFromOpenMVS) { @@ -607,7 +602,6 @@ int main(int argc, LPCTSTR* argv) } } - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/ReconstructMesh/ReconstructMesh.cpp b/apps/ReconstructMesh/ReconstructMesh.cpp index 43586e20c..f7c0f6d83 100644 --- a/apps/ReconstructMesh/ReconstructMesh.cpp +++ b/apps/ReconstructMesh/ReconstructMesh.cpp @@ -52,6 +52,7 @@ namespace { namespace OPT { String strInputFileName; +String strPointCloudFileName; String strOutputFileName; String strMeshFileName; String strImportROIFileName; @@ -81,8 +82,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -95,7 +105,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("ply")), "file type used to export the 3D scene (ply or obj)") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -116,6 +126,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description config_main("Reconstruct options"); config_main.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("pointcloud-file,p", boost::program_options::value(&OPT::strPointCloudFileName), "dense point-cloud with views file name to reconstruct (overwrite existing point-cloud)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") ("min-point-distance,d", boost::program_options::value(&OPT::fDistInsert)->default_value(2.5f), "minimum distance in pixels between the projection of two 3D points to consider them different while triangulating (0 - disabled)") ("integrate-only-roi", boost::program_options::value(&OPT::bUseOnlyROI)->default_value(false), "use only the points inside the ROI") @@ -183,45 +194,34 @@ bool Initialize(size_t argc, LPCTSTR* argv) // validate input Util::ensureValidPath(OPT::strInputFileName); - if (OPT::vm.count("help") || OPT::strInputFileName.IsEmpty()) { + if (OPT::vm.count("help") || OPT::strInputFileName.empty()) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(config_main).add(config_clean); GET_LOG() << visible; } - if (OPT::strInputFileName.IsEmpty()) + if (OPT::strInputFileName.empty()) return false; OPT::strExportType = OPT::strExportType.ToLower() == _T("obj") ? _T(".obj") : _T(".ply"); // initialize optional options + Util::ensureValidPath(OPT::strPointCloudFileName); Util::ensureValidPath(OPT::strOutputFileName); Util::ensureValidPath(OPT::strImportROIFileName); Util::ensureValidPath(OPT::strImagePointsFileName); - if (OPT::strOutputFileName.IsEmpty()) + Util::ensureValidPath(OPT::strMeshFileName); + if (OPT::strPointCloudFileName.empty() && (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + OPT::strPointCloudFileName = Util::getFileFullName(OPT::strInputFileName) + _T(".ply"); + if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_mesh.mvs"); - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -340,20 +340,32 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; Scene scene(OPT::nMaxThreads); // load project - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), OPT::fSplitMaxArea > 0 || OPT::fDecimateMesh < 1 || OPT::nTargetFaceNum > 0 || !OPT::strImportROIFileName.empty())) + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), + OPT::fSplitMaxArea > 0 || OPT::fDecimateMesh < 1 || OPT::nTargetFaceNum > 0 || !OPT::strImportROIFileName.empty())); + if (sceneType == Scene::SCENE_NA) + return EXIT_FAILURE; + if (!OPT::strPointCloudFileName.empty() && (File::isFile(MAKE_PATH_SAFE(OPT::strPointCloudFileName)) ? + !scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointCloudFileName)) : + !scene.pointcloud.IsValid())) { + VERBOSE("error: cannot load point-cloud file"); + return EXIT_FAILURE; + } + if (!OPT::strMeshFileName.empty() && !scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName))) { + VERBOSE("error: cannot load mesh file"); return EXIT_FAILURE; + } const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); if (OPT::fSplitMaxArea > 0) { // split mesh using max-area constraint Mesh::FacesChunkArr chunks; if (scene.mesh.Split(chunks, OPT::fSplitMaxArea)) scene.mesh.Save(chunks, baseFileName); - Finalize(); return EXIT_SUCCESS; } @@ -370,7 +382,6 @@ int main(int argc, LPCTSTR* argv) VERBOSE("Mesh trimmed to ROI: %u vertices and %u faces removed (%s)", numVertices-scene.mesh.vertices.size(), numFaces-scene.mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); scene.mesh.Save(baseFileName+OPT::strExportType); - Finalize(); return EXIT_SUCCESS; } } @@ -397,13 +408,13 @@ int main(int argc, LPCTSTR* argv) scene.obb.EnlargePercent(OPT::fBorderROI); else if (OPT::fBorderROI < 0) scene.obb.Enlarge(-OPT::fBorderROI); - if (OPT::strMeshFileName.IsEmpty() && scene.mesh.IsEmpty()) { + if (OPT::strMeshFileName.empty() && scene.mesh.IsEmpty()) { // reset image resolution to the original size and // make sure the image neighbors are initialized before deleting the point-cloud #ifdef RECMESH_USE_OPENMP bool bAbort(false); #pragma omp parallel for - for (int_t idx=0; idx<(int_t)scene.images.GetSize(); ++idx) { + for (int_t idx=0; idx<(int_t)scene.images.size(); ++idx) { #pragma omp flush (bAbort) if (bAbort) continue; @@ -426,7 +437,7 @@ int main(int argc, LPCTSTR* argv) } imageData.UpdateCamera(scene.platforms); // select neighbor views - if (imageData.neighbors.IsEmpty()) { + if (imageData.neighbors.empty()) { IndexArr points; scene.SelectNeighborViews(idxImage, points); } @@ -448,7 +459,7 @@ int main(int argc, LPCTSTR* argv) scene.mesh.Save(baseFileName+_T("_raw")+OPT::strExportType); } #endif - } else if (!OPT::strMeshFileName.IsEmpty()) { + } else if (!OPT::strMeshFileName.empty()) { // load existing mesh to clean scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName)); } @@ -469,20 +480,19 @@ int main(int argc, LPCTSTR* argv) scene.obb = initialOBB; // save the final mesh - scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); scene.mesh.Save(baseFileName+OPT::strExportType); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType); #endif + if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS || sceneType != Scene::SCENE_INTERFACE) + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); } if (!OPT::strImagePointsFileName.empty()) { Export3DProjections(scene, MAKE_PATH_SAFE(OPT::strImagePointsFileName)); return EXIT_SUCCESS; } - - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/RefineMesh/RefineMesh.cpp b/apps/RefineMesh/RefineMesh.cpp index c0960935d..3aa6230c5 100644 --- a/apps/RefineMesh/RefineMesh.cpp +++ b/apps/RefineMesh/RefineMesh.cpp @@ -47,8 +47,8 @@ namespace { namespace OPT { String strInputFileName; -String strOutputFileName; String strMeshFileName; +String strOutputFileName; unsigned nResolutionLevel; unsigned nMinResolution; unsigned nMaxViews; @@ -72,8 +72,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -86,7 +95,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("ply")), "file type used to export the 3D scene (ply or obj)") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -107,6 +116,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description config("Refine options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("mesh-file,m", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to refine (overwrite existing mesh)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") ("resolution-level", boost::program_options::value(&OPT::nResolutionLevel)->default_value(0), "how many times to scale down the images before mesh refinement") ("min-resolution", boost::program_options::value(&OPT::nMinResolution)->default_value(640), "do not scale images lower than this resolution") @@ -125,18 +135,11 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("reduce-memory", boost::program_options::value(&OPT::nReduceMemory)->default_value(1), "recompute some data in order to reduce memory requirements") ; - // hidden options, allowed both on command line and - // in config file, but will not be shown to the user - boost::program_options::options_description hidden("Hidden options"); - hidden.add_options() - ("mesh-file", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to refine (overwrite the existing mesh)") - ; - boost::program_options::options_description cmdline_options; - cmdline_options.add(generic).add(config).add(hidden); + cmdline_options.add(generic).add(config); boost::program_options::options_description config_file_options; - config_file_options.add(config).add(hidden); + config_file_options.add(config); boost::program_options::positional_options_description p; p.add("input-file", -1); @@ -167,43 +170,31 @@ bool Initialize(size_t argc, LPCTSTR* argv) // validate input Util::ensureValidPath(OPT::strInputFileName); - if (OPT::vm.count("help") || OPT::strInputFileName.IsEmpty()) { + if (OPT::vm.count("help") || OPT::strInputFileName.empty()) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(config); GET_LOG() << visible; } - if (OPT::strInputFileName.IsEmpty()) + if (OPT::strInputFileName.empty()) return false; OPT::strExportType = OPT::strExportType.ToLower() == _T("obj") ? _T(".obj") : _T(".ply"); // initialize optional options + Util::ensureValidPath(OPT::strMeshFileName); Util::ensureValidPath(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) + if (OPT::strMeshFileName.empty() && (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + OPT::strMeshFileName = Util::getFileFullName(OPT::strInputFileName) + _T(".ply"); + if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_refine.mvs"); - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -219,16 +210,18 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; Scene scene(OPT::nMaxThreads); // load and refine the coarse mesh - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))); + if (sceneType == Scene::SCENE_NA) + return EXIT_FAILURE; + if (!OPT::strMeshFileName.empty() && !scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName))) { + VERBOSE("error: cannot load mesh file"); return EXIT_FAILURE; - if (!OPT::strMeshFileName.IsEmpty()) { - // load given coarse mesh - scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName)); } if (scene.mesh.IsEmpty()) { VERBOSE("error: empty initial mesh"); @@ -261,14 +254,13 @@ int main(int argc, LPCTSTR* argv) // save the final mesh const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); - scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); scene.mesh.Save(baseFileName+OPT::strExportType); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType); #endif - - Finalize(); + if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS || sceneType != Scene::SCENE_INTERFACE) + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/Tests/Tests.cpp b/apps/Tests/Tests.cpp index 218437b7a..f0f8365b8 100644 --- a/apps/Tests/Tests.cpp +++ b/apps/Tests/Tests.cpp @@ -37,6 +37,8 @@ using namespace MVS; // D E F I N E S /////////////////////////////////////////////////// +#define APPNAME _T("Tests") + // S T R U C T S /////////////////////////////////////////////////// @@ -113,7 +115,7 @@ int main(int argc, LPCTSTR* argv) { OPEN_LOG(); OPEN_LOGCONSOLE(); - Util::Init(); + MVS::Initialize(APPNAME); WORKING_FOLDER = _DATA_PATH; INIT_WORKING_FOLDER; if (argc < 2 || std::atoi(argv[1]) == 0) { @@ -123,6 +125,7 @@ int main(int argc, LPCTSTR* argv) if (!PipelineTest()) return EXIT_FAILURE; } + MVS::Finalize(); CLOSE_LOGCONSOLE(); CLOSE_LOG(); return EXIT_SUCCESS; diff --git a/apps/TextureMesh/TextureMesh.cpp b/apps/TextureMesh/TextureMesh.cpp index 8409a2ef2..703e4b1a8 100644 --- a/apps/TextureMesh/TextureMesh.cpp +++ b/apps/TextureMesh/TextureMesh.cpp @@ -47,8 +47,8 @@ namespace { namespace OPT { String strInputFileName; -String strOutputFileName; String strMeshFileName; +String strOutputFileName; String strViewsFileName; float fDecimateMesh; unsigned nCloseHoles; @@ -68,13 +68,23 @@ unsigned nOrthoMapResolution; unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; +int nMaxTextureSize; String strExportType; String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -87,7 +97,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("ply")), "file type used to export the 3D scene (ply, obj, glb or gltf)") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -108,6 +118,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description config("Texture options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("mesh-file,m", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to texture (overwrite existing mesh)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(1.f), "decimation factor in range [0..1] to be applied to the input surface before refinement (0 - auto, 1 - disabled)") ("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the input surface (0 - disabled)") @@ -124,13 +135,13 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("sharpness-weight", boost::program_options::value(&OPT::fSharpnessWeight)->default_value(0.5f), "amount of sharpness to be applied on the texture (0 - disabled)") ("orthographic-image-resolution", boost::program_options::value(&OPT::nOrthoMapResolution)->default_value(0), "orthographic image resolution to be generated from the textured mesh - the mesh is expected to be already geo-referenced or at least properly oriented (0 - disabled)") ("ignore-mask-label", boost::program_options::value(&OPT::nIgnoreMaskLabel)->default_value(-1), "label value to ignore in the image mask, stored in the MVS scene or next to each image with '.mask.png' extension (-1 - auto estimate mask for lens distortion, -2 - disabled)") + ("max-texture-size", boost::program_options::value(&OPT::nMaxTextureSize)->default_value(8192), "maximum texture size, split it in multiple textures of this size if needed (0 - unbounded)") ; // hidden options, allowed both on command line and // in config file, but will not be shown to the user boost::program_options::options_description hidden("Hidden options"); hidden.add_options() - ("mesh-file", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to texture (overwrite the existing mesh)") ("views-file", boost::program_options::value(&OPT::strViewsFileName), "file name containing the list of views to be used for texturing (optional)") ; @@ -169,12 +180,12 @@ bool Initialize(size_t argc, LPCTSTR* argv) // validate input Util::ensureValidPath(OPT::strInputFileName); - if (OPT::vm.count("help") || OPT::strInputFileName.IsEmpty()) { + if (OPT::vm.count("help") || OPT::strInputFileName.empty()) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(config); GET_LOG() << visible; } - if (OPT::strInputFileName.IsEmpty()) + if (OPT::strInputFileName.empty()) return false; OPT::strExportType = OPT::strExportType.ToLower(); if (OPT::strExportType == _T("obj")) @@ -189,34 +200,22 @@ bool Initialize(size_t argc, LPCTSTR* argv) OPT::strExportType = _T(".ply"); // initialize optional options + Util::ensureValidPath(OPT::strMeshFileName); Util::ensureValidPath(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) - OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_texture.mvs"); Util::ensureValidPath(OPT::strViewsFileName); + if (OPT::strMeshFileName.empty() && (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + OPT::strMeshFileName = Util::getFileFullName(OPT::strInputFileName) + _T(".ply"); + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_texture.mvs"); - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -266,23 +265,25 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; Scene scene(OPT::nMaxThreads); // load and texture the mesh - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))); + if (sceneType == Scene::SCENE_NA) + return EXIT_FAILURE; + if (!OPT::strMeshFileName.empty() && !scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName))) { + VERBOSE("error: cannot load mesh file"); return EXIT_FAILURE; - if (!OPT::strMeshFileName.IsEmpty()) { - // load given mesh - scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName)); } if (scene.mesh.IsEmpty()) { VERBOSE("error: empty initial mesh"); return EXIT_FAILURE; } const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); - if (OPT::nOrthoMapResolution && !scene.mesh.textureDiffuse.empty()) { + if (OPT::nOrthoMapResolution && !scene.mesh.HasTexture()) { // the input mesh is already textured and an orthographic projection was requested goto ProjectOrtho; } @@ -307,17 +308,18 @@ int main(int argc, LPCTSTR* argv) TD_TIMER_START(); if (!scene.TextureMesh(OPT::nResolutionLevel, OPT::nMinResolution, OPT::minCommonCameras, OPT::fOutlierThreshold, OPT::fRatioDataSmoothness, OPT::bGlobalSeamLeveling, OPT::bLocalSeamLeveling, OPT::nTextureSizeMultiple, OPT::nRectPackingHeuristic, Pixel8U(OPT::nColEmpty), - OPT::fSharpnessWeight, OPT::nIgnoreMaskLabel, views)) + OPT::fSharpnessWeight, OPT::nIgnoreMaskLabel, OPT::nMaxTextureSize, views)) return EXIT_FAILURE; VERBOSE("Mesh texturing completed: %u vertices, %u faces (%s)", scene.mesh.vertices.GetSize(), scene.mesh.faces.GetSize(), TD_TIMER_GET_FMT().c_str()); // save the final mesh - scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); scene.mesh.Save(baseFileName+OPT::strExportType); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType); #endif + if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS || sceneType != Scene::SCENE_INTERFACE) + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); } if (OPT::nOrthoMapResolution) { @@ -336,7 +338,6 @@ int main(int argc, LPCTSTR* argv) sml.Save(baseFileName+_T("_orthomap.txt")); } - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/TransformScene/TransformScene.cpp b/apps/TransformScene/TransformScene.cpp index ec293a7af..b4dab28bc 100644 --- a/apps/TransformScene/TransformScene.cpp +++ b/apps/TransformScene/TransformScene.cpp @@ -48,6 +48,8 @@ namespace { namespace OPT { String strInputFileName; + String strPointCloudFileName; + String strMeshFileName; String strOutputFileName; String strAlignFileName; String strTransformFileName; @@ -56,6 +58,7 @@ namespace OPT { bool bComputeVolume; float fPlaneThreshold; float fSampleMesh; + unsigned nMaxResolution; unsigned nUpAxis; unsigned nArchiveType; int nProcessPriority; @@ -65,8 +68,17 @@ namespace OPT { boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -79,7 +91,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("ply")), "file type used to export the 3D scene (ply, obj, glb or gltf)") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -97,6 +109,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description config("Main options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input scene filename") + ("pointcloud-file,p", boost::program_options::value(&OPT::strPointCloudFileName), "dense point-cloud with views file name to transform (overwrite existing point-cloud)") + ("mesh-file,m", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to transform (overwrite existing mesh)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the scene") ("align-file,a", boost::program_options::value(&OPT::strAlignFileName), "input scene filename to which the scene will be cameras aligned") ("transform-file,t", boost::program_options::value(&OPT::strTransformFileName), "input transform filename by which the scene will transformed") @@ -105,6 +119,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("compute-volume", boost::program_options::value(&OPT::bComputeVolume)->default_value(false), "compute the volume of the given watertight mesh, or else try to estimate the ground plane and assume the mesh is bounded by it") ("plane-threshold", boost::program_options::value(&OPT::fPlaneThreshold)->default_value(0.f), "threshold used to estimate the ground plane (<0 - disabled, 0 - auto, >0 - desired threshold)") ("sample-mesh", boost::program_options::value(&OPT::fSampleMesh)->default_value(-300000.f), "uniformly samples points on a mesh (0 - disabled, <0 - number of points, >0 - sample density per square unit)") + ("max-resolution", boost::program_options::value(&OPT::nMaxResolution)->default_value(0), "make sure image resolution are not not larger than this (0 - disabled)") ("up-axis", boost::program_options::value(&OPT::nUpAxis)->default_value(2), "scene axis considered to point upwards (0 - x, 1 - y, 2 - z)") ; @@ -170,31 +185,22 @@ bool Initialize(size_t argc, LPCTSTR* argv) OPT::strExportType = _T(".ply"); // initialize optional options + Util::ensureValidPath(OPT::strPointCloudFileName); + Util::ensureValidPath(OPT::strMeshFileName); Util::ensureValidPath(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) - OPT::strOutputFileName = Util::getFileName(OPT::strInputFileName) + "_transformed" MVS_EXT; - - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif + if (OPT::strMeshFileName.empty() && (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS && strInputFileNameExt == MVS_EXT) + OPT::strMeshFileName = Util::getFileFullName(OPT::strInputFileName) + _T(".ply"); + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = Util::getFileName(OPT::strInputFileName) + _T("_transformed") MVS_EXT; - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -210,7 +216,8 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; TD_TIMER_START(); @@ -218,8 +225,19 @@ int main(int argc, LPCTSTR* argv) Scene scene(OPT::nMaxThreads); // load given scene - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), !OPT::strTransformFileName.empty() || !OPT::strTransferTextureFileName.empty() || OPT::bComputeVolume)) + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), + !OPT::strTransformFileName.empty() || !OPT::strTransferTextureFileName.empty() || OPT::bComputeVolume)); + if (sceneType == Scene::SCENE_NA) + return EXIT_FAILURE; + if (!OPT::strPointCloudFileName.empty() && !scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointCloudFileName))) { + VERBOSE("error: cannot load point-cloud file"); + return EXIT_FAILURE; + } + if (!OPT::strMeshFileName.empty() && !scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName))) { + VERBOSE("error: cannot load mesh file"); return EXIT_FAILURE; + } + const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); if (!OPT::strAlignFileName.empty()) { // transform this scene such that it best aligns with the given scene based on the camera positions @@ -234,16 +252,18 @@ int main(int argc, LPCTSTR* argv) if (!OPT::strTransformFileName.empty()) { // transform this scene by the given transform matrix std::ifstream file(MAKE_PATH_SAFE(OPT::strTransformFileName)); - std::string strLine; + std::string value; std::vector transformValues; - while (std::getline(file, strLine)) { - errno = 0; - char* strEnd{}; - const double v = std::strtod(strLine.c_str(), &strEnd); - if (errno == ERANGE || strEnd == strLine.c_str()) - continue; - transformValues.push_back(v); - } + while (file >> value) { + double v; + try { + v = std::stod(value); + } + catch (...) { + continue; + } + transformValues.push_back(v); + } if (transformValues.size() != 12 && (transformValues.size() != 16 || transformValues[12] != 0 || transformValues[13] != 0 || transformValues[14] != 0 || transformValues[15] != 1)) { VERBOSE("error: invalid transform"); @@ -275,30 +295,35 @@ int main(int argc, LPCTSTR* argv) } if (!scene.mesh.TransferTexture(newMesh, faceSubsetIndices)) return EXIT_FAILURE; - newMesh.Save(Util::getFileFullName(MAKE_PATH_SAFE(OPT::strOutputFileName)) + OPT::strExportType); + newMesh.Save(baseFileName + OPT::strExportType); VERBOSE("Texture transfered (%s)", TD_TIMER_GET_FMT().c_str()); return EXIT_SUCCESS; } + if (OPT::nMaxResolution > 0) { + // scale scene images + const String folderName(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)) + String::FormatString("images%u" PATH_SEPARATOR_STR, OPT::nMaxResolution)); + if (!scene.ScaleImages(OPT::nMaxResolution, 0, folderName)) { + DEBUG("error: can not scale scene images to '%s'", folderName.c_str()); + return EXIT_FAILURE; + } + } + if (OPT::bComputeVolume && !scene.mesh.IsEmpty()) { // compute the mesh volume const REAL volume(scene.ComputeLeveledVolume(OPT::fPlaneThreshold, OPT::fSampleMesh, OPT::nUpAxis)); VERBOSE("Mesh volume: %g (%s)", volume, TD_TIMER_GET_FMT().c_str()); - scene.mesh.Save(Util::getFileFullName(MAKE_PATH_SAFE(OPT::strOutputFileName)) + OPT::strExportType); - if (scene.images.empty()) - return EXIT_SUCCESS; - OPT::nArchiveType = ARCHIVE_DEFAULT; } // write transformed scene if (scene.IsValid()) scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); - else if (!scene.pointcloud.IsEmpty()) - scene.pointcloud.Save(Util::getFileFullName(MAKE_PATH_SAFE(OPT::strOutputFileName)) + ".ply"); - else if (!scene.mesh.IsEmpty()) - scene.mesh.Save(Util::getFileFullName(MAKE_PATH_SAFE(OPT::strOutputFileName)) + ".ply"); - - Finalize(); + if (!scene.IsValid() || (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) { + if (!scene.pointcloud.IsEmpty()) + scene.pointcloud.Save(baseFileName + _T(".ply"), (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS); + if (!scene.mesh.IsEmpty()) + scene.mesh.Save(baseFileName + OPT::strExportType); + } return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/Viewer/Scene.cpp b/apps/Viewer/Scene.cpp index 69683dc75..161c33e17 100644 --- a/apps/Viewer/Scene.cpp +++ b/apps/Viewer/Scene.cpp @@ -122,10 +122,10 @@ void* Scene::ThreadWorker(void*) { SEACAVE::EventQueue Scene::events; SEACAVE::Thread Scene::thread; -Scene::Scene() +Scene::Scene(ARCHIVE_TYPE _nArchiveType) : - listPointCloud(0), - listMesh(0) + nArchiveType(_nArchiveType), + listPointCloud(0) { } Scene::~Scene() @@ -147,7 +147,7 @@ void Scene::Empty() images.Release(); scene.Release(); sceneName.clear(); - meshName.clear(); + geometryName.clear(); } void Scene::Release() { @@ -170,13 +170,14 @@ void Scene::ReleasePointCloud() } void Scene::ReleaseMesh() { - if (listMesh) { - glDeleteLists(listMesh, 1); - listMesh = 0; + if (!listMeshes.empty()) { + for (GLuint listMesh: listMeshes) + glDeleteLists(listMesh, 1); + listMeshes.Release(); } } -bool Scene::Init(const cv::Size& size, LPCTSTR windowName, LPCTSTR fileName, LPCTSTR meshFileName) +bool Scene::Init(const cv::Size& size, LPCTSTR windowName, LPCTSTR fileName, LPCTSTR geometryFileName) { ASSERT(scene.IsEmpty()); @@ -215,29 +216,36 @@ bool Scene::Init(const cv::Size& size, LPCTSTR windowName, LPCTSTR fileName, LPC // open scene or init empty scene window.SetCamera(Camera()); if (fileName != NULL) - Open(fileName, meshFileName); + Open(fileName, geometryFileName); window.SetVisible(true); return true; } -bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) +bool Scene::Open(LPCTSTR fileName, LPCTSTR geometryFileName) { ASSERT(fileName); DEBUG_EXTRA("Loading: '%s'", Util::getFileNameExt(fileName).c_str()); Empty(); sceneName = fileName; - if (meshFileName) - meshName = meshFileName; // load the scene WORKING_FOLDER = Util::getFilePath(fileName); INIT_WORKING_FOLDER; if (!scene.Load(fileName, true)) return false; - if (meshFileName) { - // load given mesh - if (!scene.mesh.Load(meshFileName)) { - // try to load as a point-cloud - scene.pointcloud.Load(meshFileName); + if (geometryFileName) { + // try to load given mesh + MVS::Mesh mesh; + MVS::PointCloud pointcloud; + if (mesh.Load(geometryFileName)) { + scene.mesh.Swap(mesh); + geometryName = geometryFileName; + geometryMesh = true; + } else + // try to load as a point-cloud + if (pointcloud.Load(geometryFileName)) { + scene.pointcloud.Swap(pointcloud); + geometryName = geometryFileName; + geometryMesh = false; } } if (!scene.pointcloud.IsEmpty()) @@ -286,18 +294,21 @@ bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) // init and load texture if (scene.mesh.HasTexture()) { - Image& image = textures.AddEmpty(); - ASSERT(image.idx == NO_ID); - #if 0 - cv::flip(scene.mesh.textureDiffuse, scene.mesh.textureDiffuse, 0); - image.SetImage(scene.mesh.textureDiffuse); - scene.mesh.textureDiffuse.release(); - #else // preserve texture, used only to be able to export the mesh - Image8U3 textureDiffuse; - cv::flip(scene.mesh.textureDiffuse, textureDiffuse, 0); - image.SetImage(textureDiffuse); - #endif - image.GenerateMipmap(); + FOREACH(i, scene.mesh.texturesDiffuse) { + Image& image = textures.emplace_back(); + ASSERT(image.idx == NO_ID); + #if 0 + Image8U3& textureDiffuse = scene.mesh.texturesDiffuse[i]; + cv::flip(textureDiffuse, textureDiffuse, 0); + image.SetImage(textureDiffuse); + textureDiffuse.release(); + #else // preserve texture, used only to be able to export the mesh + Image8U3 textureDiffuse; + cv::flip(scene.mesh.texturesDiffuse[i], textureDiffuse, 0); + image.SetImage(textureDiffuse); + #endif + image.GenerateMipmap(); + } } // init display lists @@ -320,6 +331,7 @@ bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) window.clbkCompilePointCloud = DELEGATEBINDCLASS(Window::ClbkCompilePointCloud, &Scene::CompilePointCloud, this); window.clbkCompileMesh = DELEGATEBINDCLASS(Window::ClbkCompileMesh, &Scene::CompileMesh, this); window.clbkTogleSceneBox = DELEGATEBINDCLASS(Window::ClbkTogleSceneBox, &Scene::TogleSceneBox, this); + window.clbkCropToBounds = DELEGATEBINDCLASS(Window::ClbkCropToBounds, &Scene::CropToBounds, this); if (scene.IsBounded()) window.clbkCompileBounds = DELEGATEBINDCLASS(Window::ClbkCompileBounds, &Scene::CompileBounds, this); if (!scene.IsEmpty()) @@ -345,8 +357,11 @@ bool Scene::Save(LPCTSTR _fileName, bool bRescaleImages) } const String fileName(_fileName != NULL ? String(_fileName) : Util::insertBeforeFileExt(sceneName, _T("_new"))); MVS::Mesh mesh; - if (!scene.mesh.IsEmpty() && !meshName.empty()) + if (!scene.mesh.IsEmpty() && !geometryName.empty() && geometryMesh) mesh.Swap(scene.mesh); + MVS::PointCloud pointcloud; + if (!scene.pointcloud.IsEmpty() && !geometryName.empty() && !geometryMesh) + pointcloud.Swap(scene.pointcloud); if (imageScale > 0 && imageScale < 1) { // scale and save images const String folderName(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, fileName)) + String::FormatString("images%d" PATH_SEPARATOR_STR, ROUND2INT(imageScale*100))); @@ -355,12 +370,14 @@ bool Scene::Save(LPCTSTR _fileName, bool bRescaleImages) return false; } } - if (!scene.Save(fileName, scene.mesh.IsEmpty() ? ARCHIVE_MVS : ARCHIVE_DEFAULT)) { + if (!scene.Save(fileName, nArchiveType)) { DEBUG("error: can not save scene to '%s'", fileName.c_str()); return false; } if (!mesh.IsEmpty()) scene.mesh.Swap(mesh); + if (!pointcloud.IsEmpty()) + scene.pointcloud.Swap(pointcloud); sceneName = fileName; return true; } @@ -374,7 +391,7 @@ bool Scene::Export(LPCTSTR _fileName, LPCTSTR exportType) const String lastFileName; const String fileName(_fileName != NULL ? String(_fileName) : sceneName); const String baseFileName(Util::getFileFullName(fileName)); - const bool bPoints(scene.pointcloud.Save(lastFileName=(baseFileName+_T("_pointcloud.ply")))); + const bool bPoints(scene.pointcloud.Save(lastFileName=(baseFileName+_T("_pointcloud.ply")), nArchiveType==ARCHIVE_MVS)); const bool bMesh(scene.mesh.Save(lastFileName=(baseFileName+_T("_mesh")+(exportType?exportType:(Util::getFileExt(fileName)==_T(".obj")?_T(".obj"):_T(".ply")))), cList(), true)); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2 && (bPoints || bMesh)) @@ -410,15 +427,15 @@ void Scene::CompilePointCloud() glNewList(listPointCloud, GL_COMPILE); ASSERT((window.sparseType&(Window::SPR_POINTS|Window::SPR_LINES)) != 0); // compile point-cloud - if (!scene.pointcloud.IsEmpty() && (window.sparseType&Window::SPR_POINTS) != 0) { + if ((window.sparseType&Window::SPR_POINTS) != 0) { ASSERT_ARE_SAME_TYPE(float, MVS::PointCloud::Point::Type); glBegin(GL_POINTS); glColor3f(1.f,1.f,1.f); FOREACH(i, scene.pointcloud.points) { - if (!scene.pointcloud.pointViews.IsEmpty() && + if (!scene.pointcloud.pointViews.empty() && scene.pointcloud.pointViews[i].size() < window.minViews) continue; - if (!scene.pointcloud.colors.IsEmpty()) { + if (!scene.pointcloud.colors.empty()) { const MVS::PointCloud::Color& c = scene.pointcloud.colors[i]; glColor3ub(c.r,c.g,c.b); } @@ -439,31 +456,37 @@ void Scene::CompileMesh() scene.mesh.ComputeNormalFaces(); // translate, normalize and flip Y axis of the texture coordinates MVS::Mesh::TexCoordArr normFaceTexcoords; - if (scene.mesh.HasTexture()) + if (scene.mesh.HasTexture() && window.bRenderTexture) scene.mesh.FaceTexcoordsNormalize(normFaceTexcoords, true); - listMesh = glGenLists(1); - glNewList(listMesh, GL_COMPILE); - // compile mesh - ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::Vertex::Type); - ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::Normal::Type); - ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::TexCoord::Type); - glColor3f(1.f, 1.f, 1.f); - glBegin(GL_TRIANGLES); - FOREACH(i, scene.mesh.faces) { - const MVS::Mesh::Face& face = scene.mesh.faces[i]; - const MVS::Mesh::Normal& n = scene.mesh.faceNormals[i]; - glNormal3fv(n.ptr()); - for (int j = 0; j < 3; ++j) { - if (!normFaceTexcoords.empty() && window.bRenderTexture) { - const MVS::Mesh::TexCoord& t = normFaceTexcoords[i * 3 + j]; - glTexCoord2fv(t.ptr()); + MVS::Mesh::TexIndex texIdx(0); + do { + GLuint& listMesh = listMeshes.emplace_back(glGenLists(1)); + listMesh = glGenLists(1); + glNewList(listMesh, GL_COMPILE); + // compile mesh + ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::Vertex::Type); + ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::Normal::Type); + ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::TexCoord::Type); + glColor3f(1.f, 1.f, 1.f); + glBegin(GL_TRIANGLES); + FOREACH(idxFace, scene.mesh.faces) { + if (!scene.mesh.faceTexindices.empty() && scene.mesh.faceTexindices[idxFace] != texIdx) + continue; + const MVS::Mesh::Face& face = scene.mesh.faces[idxFace]; + const MVS::Mesh::Normal& n = scene.mesh.faceNormals[idxFace]; + glNormal3fv(n.ptr()); + for (int j = 0; j < 3; ++j) { + if (!normFaceTexcoords.empty()) { + const MVS::Mesh::TexCoord& t = normFaceTexcoords[idxFace*3 + j]; + glTexCoord2fv(t.ptr()); + } + const MVS::Mesh::Vertex& p = scene.mesh.vertices[face[j]]; + glVertex3fv(p.ptr()); } - const MVS::Mesh::Vertex& p = scene.mesh.vertices[face[j]]; - glVertex3fv(p.ptr()); } - } - glEnd(); - glEndList(); + glEnd(); + glEndList(); + } while (++texIdx < scene.mesh.texturesDiffuse.size()); } void Scene::CompileBounds() @@ -489,6 +512,17 @@ void Scene::CompileBounds() } } +void Scene::CropToBounds() +{ + if (!IsOpen()) + return; + if (!scene.IsBounded()) + return; + scene.pointcloud.RemovePointsOutside(scene.obb); + scene.mesh.RemoveFacesOutside(scene.obb); + Center(); +} + void Scene::Draw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -500,17 +534,20 @@ void Scene::Draw() glCallList(listPointCloud); } // render mesh - if (listMesh) { + if (!listMeshes.empty()) { glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); if (!scene.mesh.faceTexcoords.empty() && window.bRenderTexture) { glEnable(GL_TEXTURE_2D); - textures.front().Bind(); - glCallList(listMesh); + FOREACH(i, listMeshes) { + textures[i].Bind(); + glCallList(listMeshes[i]); + } glDisable(GL_TEXTURE_2D); } else { glEnable(GL_LIGHTING); - glCallList(listMesh); + for (GLuint listMesh: listMeshes) + glCallList(listMesh); glDisable(GL_LIGHTING); } } diff --git a/apps/Viewer/Scene.h b/apps/Viewer/Scene.h index d1e360833..9315b35e8 100644 --- a/apps/Viewer/Scene.h +++ b/apps/Viewer/Scene.h @@ -52,10 +52,12 @@ class Scene typedef MVS::Mesh::Octree OctreeMesh; public: + ARCHIVE_TYPE nArchiveType; String name; String sceneName; - String meshName; + String geometryName; + bool geometryMesh; MVS::Scene scene; Window window; ImageArr images; // scene photos @@ -66,14 +68,14 @@ class Scene Point3fArr obbPoints; GLuint listPointCloud; - GLuint listMesh; + CLISTDEF0IDX(GLuint,MVS::Mesh::TexIndex) listMeshes; // multi-threading static SEACAVE::EventQueue events; // internal events queue (processed by the working threads) static SEACAVE::Thread thread; // worker thread public: - Scene(); + explicit Scene(ARCHIVE_TYPE _nArchiveType = ARCHIVE_MVS); ~Scene(); void Empty(); @@ -84,13 +86,14 @@ class Scene inline bool IsOpen() const { return IsValid() && !scene.IsEmpty(); } inline bool IsOctreeValid() const { return !octPoints.IsEmpty() || !octMesh.IsEmpty(); } - bool Init(const cv::Size&, LPCTSTR windowName, LPCTSTR fileName=NULL, LPCTSTR meshFileName=NULL); - bool Open(LPCTSTR fileName, LPCTSTR meshFileName=NULL); + bool Init(const cv::Size&, LPCTSTR windowName, LPCTSTR fileName=NULL, LPCTSTR geometryFileName=NULL); + bool Open(LPCTSTR fileName, LPCTSTR geometryFileName=NULL); bool Save(LPCTSTR fileName=NULL, bool bRescaleImages=false); bool Export(LPCTSTR fileName, LPCTSTR exportType=NULL) const; void CompilePointCloud(); void CompileMesh(); void CompileBounds(); + void CropToBounds(); void Draw(); void Loop(); diff --git a/apps/Viewer/Viewer.cpp b/apps/Viewer/Viewer.cpp index 27aa73d93..5bff3c2bf 100644 --- a/apps/Viewer/Viewer.cpp +++ b/apps/Viewer/Viewer.cpp @@ -48,8 +48,8 @@ namespace { namespace OPT { String strInputFileName; +String strGeometryFileName; String strOutputFileName; -String strMeshFileName; unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -62,8 +62,17 @@ bool bLogFile; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -76,7 +85,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") ("export-type", boost::program_options::value(&OPT::strExportType), "file type used to export the 3D scene (ply or obj)") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(0), "process priority (normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads that this process should use (0 - use all available cores)") ("max-memory", boost::program_options::value(&OPT::nMaxMemory)->default_value(0), "maximum amount of memory in MB that this process should use (0 - use all available memory)") @@ -96,21 +105,15 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description config("Viewer options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input project filename containing camera poses and scene (point-cloud/mesh)") + ("geometry-file,g", boost::program_options::value(&OPT::strGeometryFileName), "mesh or point-cloud with views file name (overwrite existing geometry)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") ; - // hidden options, allowed both on command line and - // in config file, but will not be shown to the user - boost::program_options::options_description hidden("Hidden options"); - hidden.add_options() - ("mesh-file", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to texture (overwrite the existing mesh)") - ; - boost::program_options::options_description cmdline_options; - cmdline_options.add(generic).add(config).add(hidden); + cmdline_options.add(generic).add(config); boost::program_options::options_description config_file_options; - config_file_options.add(config).add(hidden); + config_file_options.add(config); boost::program_options::positional_options_description p; p.add("input-file", -1); @@ -171,36 +174,21 @@ bool Initialize(size_t argc, LPCTSTR* argv) "\n") << visible; } - if (!OPT::strExportType.IsEmpty()) + if (!OPT::strExportType.empty()) OPT::strExportType = OPT::strExportType.ToLower() == _T("obj") ? _T(".obj") : _T(".ply"); // initialize optional options + Util::ensureValidPath(OPT::strGeometryFileName); Util::ensureValidPath(OPT::strOutputFileName); - Util::ensureValidPath(OPT::strMeshFileName); - - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); if (OPT::bLogFile) CLOSE_LOGFILE(); @@ -217,23 +205,22 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; // create viewer Scene viewer; if (!viewer.Init(cv::Size(1280, 720), APPNAME, - OPT::strInputFileName.IsEmpty() ? NULL : MAKE_PATH_SAFE(OPT::strInputFileName).c_str(), - OPT::strMeshFileName.IsEmpty() ? NULL : MAKE_PATH_SAFE(OPT::strMeshFileName).c_str())) + OPT::strInputFileName.empty() ? NULL : MAKE_PATH_SAFE(OPT::strInputFileName).c_str(), + OPT::strGeometryFileName.empty() ? NULL : MAKE_PATH_SAFE(OPT::strGeometryFileName).c_str())) return EXIT_FAILURE; - if (viewer.IsOpen() && !OPT::strOutputFileName.IsEmpty()) { + if (viewer.IsOpen() && !OPT::strOutputFileName.empty()) { // export the scene - viewer.Export(MAKE_PATH_SAFE(OPT::strOutputFileName), OPT::strExportType.IsEmpty()?LPCTSTR(NULL):OPT::strExportType.c_str()); + viewer.Export(MAKE_PATH_SAFE(OPT::strOutputFileName), OPT::strExportType.empty()?LPCTSTR(NULL):OPT::strExportType.c_str()); } // enter viewer loop viewer.Loop(); - - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/Viewer/Window.cpp b/apps/Viewer/Window.cpp index 63bbe1a19..cfd2f12ef 100644 --- a/apps/Viewer/Window.cpp +++ b/apps/Viewer/Window.cpp @@ -77,6 +77,7 @@ void Window::ReleaseClbk() clbkCompileMesh.reset(); clbkCompileBounds.reset(); clbkTogleSceneBox.reset(); + clbkCropToBounds.reset(); } bool Window::Init(const cv::Size& _size, LPCTSTR name) @@ -268,7 +269,10 @@ void Window::Key(int k, int /*scancode*/, int action, int mod) break; case GLFW_KEY_B: if (action == GLFW_RELEASE) { - if (mod & GLFW_MOD_SHIFT) { + if (mod & GLFW_MOD_CONTROL) { + if (clbkCropToBounds != NULL) + clbkCropToBounds(); + } else if (mod & GLFW_MOD_SHIFT) { if (clbkTogleSceneBox != NULL) clbkTogleSceneBox(); } else { @@ -454,9 +458,9 @@ void Window::Drop(int count, const char** paths) String fileName(paths[0]); Util::ensureUnifySlash(fileName); if (count > 1) { - String meshFileName(paths[1]); - Util::ensureUnifySlash(meshFileName); - clbkOpenScene(fileName, meshFileName); + String geometryFileName(paths[1]); + Util::ensureUnifySlash(geometryFileName); + clbkOpenScene(fileName, geometryFileName); } else { clbkOpenScene(fileName, NULL); } diff --git a/apps/Viewer/Window.h b/apps/Viewer/Window.h index 34195ae31..ad9a957d0 100644 --- a/apps/Viewer/Window.h +++ b/apps/Viewer/Window.h @@ -110,6 +110,8 @@ class Window ClbkCompileBounds clbkCompileBounds; typedef DELEGATE ClbkTogleSceneBox; ClbkTogleSceneBox clbkTogleSceneBox; + typedef DELEGATE ClbkCropToBounds; + ClbkCropToBounds clbkCropToBounds; typedef std::unordered_map WindowsMap; static WindowsMap g_mapWindows; diff --git a/build/Utils.cmake b/build/Utils.cmake index 9c2430faa..86d3430d4 100644 --- a/build/Utils.cmake +++ b/build/Utils.cmake @@ -407,10 +407,13 @@ macro(optimize_default_compiler_settings) else() set(CXX_CHECK_PREFIX "--std=") endif() + check_cxx_compiler_flag("${CXX_CHECK_PREFIX}c++20" SUPPORTS_STD_CXX20) check_cxx_compiler_flag("${CXX_CHECK_PREFIX}c++17" SUPPORTS_STD_CXX17) check_cxx_compiler_flag("${CXX_CHECK_PREFIX}c++14" SUPPORTS_STD_CXX14) check_cxx_compiler_flag("${CXX_CHECK_PREFIX}c++11" SUPPORTS_STD_CXX11) - if(SUPPORTS_STD_CXX17) + if(SUPPORTS_STD_CXX20) + set(CMAKE_CXX_STANDARD 20) + elseif(SUPPORTS_STD_CXX17) set(CMAKE_CXX_STANDARD 17) elseif(SUPPORTS_STD_CXX14) set(CMAKE_CXX_STANDARD 14) @@ -418,22 +421,27 @@ macro(optimize_default_compiler_settings) set(CMAKE_CXX_STANDARD 11) endif() else() - list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_17" CXX_STD_INDEX) + list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_20" CXX_STD_INDEX) if(${CXX_STD_INDEX} GREATER -1) - set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 20) else() - list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_14" CXX_STD_INDEX) + list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_17" CXX_STD_INDEX) if(${CXX_STD_INDEX} GREATER -1) - set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_STANDARD 17) else() - list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_11" CXX_STD_INDEX) + list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_14" CXX_STD_INDEX) if(${CXX_STD_INDEX} GREATER -1) - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 14) + else() + list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_11" CXX_STD_INDEX) + if(${CXX_STD_INDEX} GREATER -1) + set(CMAKE_CXX_STANDARD 11) + endif() endif() endif() endif() endif() - if(CLANG AND (CMAKE_CXX_STANDARD EQUAL 11 OR CMAKE_CXX_STANDARD EQUAL 14 OR CMAKE_CXX_STANDARD EQUAL 17)) + if(CLANG AND (CMAKE_CXX_STANDARD EQUAL 11 OR CMAKE_CXX_STANDARD EQUAL 14 OR CMAKE_CXX_STANDARD EQUAL 17 OR CMAKE_CXX_STANDARD EQUAL 20)) set(CMAKE_EXE_LINKER_FLAGS "-stdlib=libc++") add_extra_compiler_option(-stdlib=libc++) endif() @@ -487,6 +495,7 @@ macro(optimize_default_compiler_settings) add_extra_compiler_option(-Wno-delete-incomplete) add_extra_compiler_option(-Wno-unnamed-type-template-args) add_extra_compiler_option(-Wno-int-in-bool-context) + add_extra_compiler_option(-Wno-deprecated-declarations) endif() add_extra_compiler_option(-fdiagnostics-show-option) add_extra_compiler_option(-ftemplate-backtrace-limit=0) diff --git a/libs/Common/File.h b/libs/Common/File.h index 9c4be9a7f..2e431bccd 100644 --- a/libs/Common/File.h +++ b/libs/Common/File.h @@ -75,17 +75,27 @@ #include #define _taccess access #endif +#ifdef __APPLE__ +#define fdatasync fsync +#endif // D E F I N E S /////////////////////////////////////////////////// -/* size of the stored file size variable */ +// size of the stored file size variable #ifdef LARGEFILESIZE #define FILESIZE size_f_t #else #define FILESIZE size_t #endif +// invalid file handle +#ifdef _MSC_VER +#define FILE_INVALID_HANDLE INVALID_HANDLE_VALUE +#else +#define FILE_INVALID_HANDLE int(-1) +#endif + namespace SEACAVE { @@ -106,44 +116,72 @@ class GENERAL_API File : public IOStream { TRUNCATE = 0x04 } FMCREATE; - typedef enum FMFALGS_TYPE { - NOBUFFER = 0x01, - RANDOM = 0x02, - SEQUENTIAL = 0x03 - } FMFALGS; - -#ifdef _MSC_VER - typedef enum FMACCESS_TYPE { - READ = GENERIC_READ, - WRITE = GENERIC_WRITE, - RW = READ | WRITE - } FMACCESS; - - typedef enum FMCHECKACCESS_TYPE { - CA_EXIST = 0, // existence - CA_WRITE = 2, // write - CA_READ = 4, // read - CA_RW = CA_READ | CA_WRITE - } FMCHECKACCESS; + typedef enum FMFLAGS_TYPE { + SYNC = 0x01, + NOBUFFER = 0x02, + RANDOM = 0x04, + SEQUENTIAL = 0x08 + } FMFLAGS; - File() : h(INVALID_HANDLE_VALUE) { + inline File() : h(FILE_INVALID_HANDLE) { #ifndef _RELEASE breakRead = -1; breakWrite = -1; #endif } - - File(LPCTSTR aFileName, int access, int mode, int flags=0) : h(INVALID_HANDLE_VALUE) { + inline File(LPCTSTR aFileName, int access, int mode, int flags=0) : h(FILE_INVALID_HANDLE) { #ifndef _RELEASE breakRead = -1; breakWrite = -1; #endif - open(aFileName, access, mode, flags); + File::open(aFileName, access, mode, flags); + } + + virtual ~File() { + File::close(); } + #ifdef _SUPPORT_CPP11 + inline File(File&& rhs) : h(rhs.h) { + #ifndef _RELEASE + breakRead = rhs.breakRead; + breakWrite = rhs.breakWrite; + #endif + rhs.h = FILE_INVALID_HANDLE; + } + + inline File& operator=(File&& rhs) { + h = rhs.h; + #ifndef _RELEASE + breakRead = rhs.breakRead; + breakWrite = rhs.breakWrite; + #endif + rhs.h = FILE_INVALID_HANDLE; + return *this; + } + #endif + + bool isOpen() const { + return h != FILE_INVALID_HANDLE; + } + +#ifdef _MSC_VER + typedef enum FMACCESS_TYPE { + READ = GENERIC_READ, + WRITE = GENERIC_WRITE, + RW = READ | WRITE + } FMACCESS; + + typedef enum FMCHECKACCESS_TYPE { + CA_EXIST = F_OK, // existence + CA_WRITE = W_OK, // write + CA_READ = R_OK, // read + CA_RW = R_OK | W_OK, + } FMCHECKACCESS; + /** * Open the file specified. - * If there are errors, h is set to INVALID_HANDLE_VALUE. + * If there are errors, h is set to FILE_INVALID_HANDLE. * Use isOpen() to check. */ virtual void open(LPCTSTR aFileName, int access, int mode, int flags=0) { @@ -164,6 +202,8 @@ class GENERAL_API File : public IOStream { } DWORD f = 0; + if (flags & SYNC) + f |= FILE_FLAG_WRITE_THROUGH; if (flags & NOBUFFER) f |= FILE_FLAG_NO_BUFFERING; if (flags & RANDOM) @@ -174,17 +214,16 @@ class GENERAL_API File : public IOStream { h = ::CreateFile(aFileName, access, FILE_SHARE_READ, NULL, m, f, NULL); } - bool isOpen() { return h != INVALID_HANDLE_VALUE; }; - virtual void close() { if (isOpen()) { FlushFileBuffers(h); CloseHandle(h); - h = INVALID_HANDLE_VALUE; + h = FILE_INVALID_HANDLE; } } uint32_t getLastModified() { + ASSERT(isOpen()); FILETIME f = {0}; ::GetFileTime(h, NULL, NULL, &f); return convertTime(&f); @@ -203,7 +242,8 @@ class GENERAL_API File : public IOStream { return 0; } - virtual size_f_t getSize() const { + size_f_t getSize() const override { + ASSERT(isOpen()); DWORD x; DWORD l = ::GetFileSize(h, &x); if ((l == INVALID_FILE_SIZE) && (GetLastError() != NO_ERROR)) @@ -224,7 +264,8 @@ class GENERAL_API File : public IOStream { return true; } - virtual size_f_t getPos() const { + size_f_t getPos() const override { + ASSERT(isOpen()); LONG x = 0; const DWORD l = ::SetFilePointer(h, 0, &x, FILE_CURRENT); if (l == INVALID_SET_FILE_POINTER) @@ -232,22 +273,26 @@ class GENERAL_API File : public IOStream { return (size_f_t)l | ((size_f_t)x)<<32; } - virtual bool setPos(size_f_t pos) { + bool setPos(size_f_t pos) override { + ASSERT(isOpen()); LONG x = (LONG) (pos>>32); return (::SetFilePointer(h, (DWORD)(pos & 0xffffffff), &x, FILE_BEGIN) != INVALID_SET_FILE_POINTER); } virtual bool setEndPos(size_f_t pos) { + ASSERT(isOpen()); LONG x = (LONG) (pos>>32); return (::SetFilePointer(h, (DWORD)(pos & 0xffffffff), &x, FILE_END) != INVALID_SET_FILE_POINTER); } virtual bool movePos(size_f_t pos) { + ASSERT(isOpen()); LONG x = (LONG) (pos>>32); return (::SetFilePointer(h, (DWORD)(pos & 0xffffffff), &x, FILE_CURRENT) != INVALID_SET_FILE_POINTER); } - virtual size_t read(void* buf, size_t len) { + size_t read(void* buf, size_t len) override { + ASSERT(isOpen()); #ifndef _RELEASE if (breakRead != (size_t)(-1)) { if (breakRead <= len) { @@ -264,7 +309,8 @@ class GENERAL_API File : public IOStream { return x; } - virtual size_t write(const void* buf, size_t len) { + size_t write(const void* buf, size_t len) override { + ASSERT(isOpen()); #ifndef _RELEASE if (breakWrite != (size_t)(-1)) { if (breakWrite <= len) { @@ -286,15 +332,13 @@ class GENERAL_API File : public IOStream { return (SetEndOfFile(h) != FALSE); } - virtual size_t flush() { - if (isOpen()) - return 0; - if (!FlushFileBuffers(h)) - return STREAM_ERROR; - return 0; + size_t flush() override { + ASSERT(isOpen()); + return (FlushFileBuffers(h) ? 0 : STREAM_ERROR); } virtual bool getInfo(BY_HANDLE_FILE_INFORMATION* fileInfo) { + ASSERT(isOpen()); return (GetFileInformationByHandle(h, fileInfo) != FALSE); } @@ -320,7 +364,7 @@ class GENERAL_API File : public IOStream { static size_f_t getSize(LPCTSTR aFileName) { const HANDLE fh = ::CreateFile(aFileName, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL); - if (fh == INVALID_HANDLE_VALUE) + if (fh == FILE_INVALID_HANDLE) return SIZE_NA; DWORD x; DWORD l = ::GetFileSize(fh, &x); @@ -331,17 +375,16 @@ class GENERAL_API File : public IOStream { } - static size_f_t findFiles(const String& _strPath, const String& strMask, bool bProcessSubdir, FileInfoArr& arrFiles) - { // List all the files. + static size_f_t findFiles(const String& _strPath, const String& strMask, bool bProcessSubdir, FileInfoArr& arrFiles) { + // List all the files. WIN32_FIND_DATA fd; HANDLE hFind; size_f_t totalSize = 0; String strPath(_strPath); Util::ensureFolderSlash(strPath); - // Find all the files in this folder. + //Find all the files in this folder. hFind = FindFirstFile((strPath + strMask).c_str(), &fd); - if (hFind != INVALID_HANDLE_VALUE) - { + if (hFind != FILE_INVALID_HANDLE) { do { // this is a file that can be used if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) @@ -356,16 +399,14 @@ class GENERAL_API File : public IOStream { #endif fileInfo.attrib = fd.dwFileAttributes; totalSize += fileInfo.size; - } - while (FindNextFile(hFind, &fd)); + } while (FindNextFile(hFind, &fd)); FindClose(hFind); } - // Process the subfolders also... + //Process the subfolders also... if (!bProcessSubdir) return totalSize; hFind = FindFirstFile((strPath + '*').c_str(), &fd); - if (hFind != INVALID_HANDLE_VALUE) - { + if (hFind != FILE_INVALID_HANDLE) { do { // if SUBDIR then process that too if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) @@ -376,8 +417,7 @@ class GENERAL_API File : public IOStream { continue; // Process all subfolders recursively totalSize += findFiles(strPath + fd.cFileName + PATH_SEPARATOR, strMask, true, arrFiles); - } - while (FindNextFile(hFind, &fd)); + } while (FindNextFile(hFind, &fd)); FindClose(hFind); } return totalSize; @@ -399,24 +439,9 @@ class GENERAL_API File : public IOStream { CA_EXEC = X_OK, // execute } FMCHECKACCESS; - File() : h(-1) { - #ifndef _RELEASE - breakRead = -1; - breakWrite = -1; - #endif - } - - File(LPCTSTR aFileName, int access, int mode, int flags=0) : h(-1) { - #ifndef _RELEASE - breakRead = -1; - breakWrite = -1; - #endif - open(aFileName, access, mode, flags); - } - /** * Open the file specified. - * If there are errors, h is set to -1. + * If there are errors, h is set to FILE_INVALID_HANDLE. * Use isOpen() to check. */ virtual void open(LPCTSTR aFileName, int access, int mode, int flags=0) { @@ -437,47 +462,49 @@ class GENERAL_API File : public IOStream { if (mode & TRUNCATE) m |= O_TRUNC; + if (flags & SYNC) + m |= O_DSYNC; #ifndef __APPLE__ if (flags & NOBUFFER) m |= O_DIRECT; #endif - h = ::open(aFileName, m, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + h = ::open(aFileName, m, S_IRUSR | S_IWUSR); } - bool isOpen() { return h != -1; }; - virtual void close() { - if (h != -1) { + if (h != FILE_INVALID_HANDLE) { ::close(h); - h = -1; + h = FILE_INVALID_HANDLE; } } uint32_t getLastModified() { + ASSERT(isOpen()); struct stat s; if (::fstat(h, &s) == -1) return 0; - return (uint32_t)s.st_mtime; } - virtual size_f_t getSize() const { + size_f_t getSize() const override { + ASSERT(isOpen()); struct stat s; if (::fstat(h, &s) == -1) return SIZE_NA; - return (size_f_t)s.st_size; } - virtual size_f_t getPos() const { - return (size_f_t) lseek(h, 0, SEEK_CUR); + size_f_t getPos() const override { + ASSERT(isOpen()); + return (size_f_t)lseek(h, 0, SEEK_CUR); } - virtual bool setPos(size_f_t pos) { return lseek(h, (off_t)pos, SEEK_SET) != (off_t)-1; }; - virtual void setEndPos(size_f_t pos) { lseek(h, (off_t)pos, SEEK_END); }; - virtual void movePos(size_f_t pos) { lseek(h, (off_t)pos, SEEK_CUR); }; + bool setPos(size_f_t pos) override { ASSERT(isOpen()); return lseek(h, (off_t)pos, SEEK_SET) != (off_t)-1; } + virtual bool setEndPos(size_f_t pos) { ASSERT(isOpen()); return lseek(h, (off_t)pos, SEEK_END) != (off_t)-1; } + virtual bool movePos(size_f_t pos) { ASSERT(isOpen()); return lseek(h, (off_t)pos, SEEK_CUR) != (off_t)-1; } - virtual size_t read(void* buf, size_t len) { + size_t read(void* buf, size_t len) override { + ASSERT(isOpen()); #ifndef _RELEASE if (breakRead != (size_t)(-1)) { if (breakRead <= len) { @@ -494,7 +521,8 @@ class GENERAL_API File : public IOStream { return (size_t)x; } - virtual size_t write(const void* buf, size_t len) { + size_t write(const void* buf, size_t len) override { + ASSERT(isOpen()); #ifndef _RELEASE if (breakWrite != (size_t)(-1)) { if (breakWrite <= len) { @@ -514,14 +542,17 @@ class GENERAL_API File : public IOStream { } virtual bool setEOF() { + ASSERT(isOpen()); return (ftruncate(h, (off_t)getPos()) != -1); } virtual bool setSize(size_f_t newSize) { + ASSERT(isOpen()); return (ftruncate(h, (off_t)newSize) != -1); } - virtual size_t flush() { - return fsync(h); + size_t flush() override { + ASSERT(isOpen()); + return fdatasync(h); } static void deleteFile(LPCTSTR aFileName) { ::remove(aFileName); } @@ -538,10 +569,10 @@ class GENERAL_API File : public IOStream { } static size_f_t getSize(LPCTSTR aFileName) { - struct stat s; - if (stat(aFileName, &s) == -1) + struct stat buf; + if (stat(aFileName, &buf) != 0) return SIZE_NA; - return s.st_size; + return buf.st_size; } #endif // _MSC_VER @@ -576,6 +607,38 @@ class GENERAL_API File : public IOStream { return S_ISREG(buf.st_mode) || S_ISLNK(buf.st_mode) || S_ISSOCK(buf.st_mode) || S_ISFIFO(buf.st_mode); } + // time the file was originally created + static time_t getCreated(LPCTSTR path) { + struct stat buf; + if (stat(path, &buf) != 0) return 0; + return buf.st_ctime; + } + // time the file was last modified + static time_t getModified(LPCTSTR path) { + struct stat buf; + if (stat(path, &buf) != 0) return 0; + return buf.st_mtime; + } + // time the file was accessed + static time_t getAccessed(LPCTSTR path) { + struct stat buf; + if (stat(path, &buf) != 0) return 0; + return buf.st_atime; + } + + // set the current folder + static bool setCurrentFolder(LPCTSTR path) { + if (!isFolder(path)) + return false; + #ifdef _MSC_VER + // Windows implementation - this returns non-zero for success + return (SetCurrentDirectory(path) != 0); + #else + // Unix implementation - this returns zero for success + return (chdir(path) == 0); + #endif + } + template inline size_t write(const VECTOR& arr) { const typename VECTOR::IDX nSize(arr.GetSize()); @@ -593,12 +656,10 @@ class GENERAL_API File : public IOStream { return nBytes; } - virtual ~File() { - File::close(); - } - - static LPCSTR getClassType() { return "File"; } - virtual LPCSTR getClassName() const { return File::getClassType(); } + enum { LAYER_ID_IN=3 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : IOStream::getInputStream(typ)); } + enum { LAYER_ID_OUT=3 }; + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return (typ == LAYER_ID_OUT ? static_cast(this) : IOStream::getOutputStream(typ)); } protected: #ifdef _MSC_VER diff --git a/libs/Common/Filters.h b/libs/Common/Filters.h index e84f3547c..ae3d385a6 100644 --- a/libs/Common/Filters.h +++ b/libs/Common/Filters.h @@ -21,76 +21,82 @@ namespace SEACAVE { // S T R U C T S /////////////////////////////////////////////////// -#define LAYER_MASK 1 - -template -class MaskInputStream : public LayerInputStream { +template +class BufferedInputStream : public LayerInputStream { public: - MaskInputStream(InputStream* aStream, size_f_t nPos, size_f_t nSize) : LayerInputStream(aStream), startPos(nPos), size(nSize), pos(0) { } - virtual ~MaskInputStream() { } + typedef LayerInputStream Base; - size_t read(void* wbuf, size_t len) { - if (pos >= size) - return 0; - if (!this->s->setPos(startPos + pos)) - return STREAM_ERROR; - if (pos+(size_f_t)len > size) - len = (size_t)(size - pos); - const size_t r = this->s->read(wbuf, len); - if (r == STREAM_ERROR) - return STREAM_ERROR; - pos += r; - return r; + BufferedInputStream(InputStream* aStream, size_t aBufSize) + : Base(aStream), buf(new uint8_t[aBufSize]), bufSize(aBufSize), cache(0), pos(0) { ASSERT(aBufSize > 0); } + virtual ~BufferedInputStream() { + delete[] buf; } - size_f_t getSize() const { - return size; + size_t read(void* wbuf, size_t len) override { + uint8_t* b = (uint8_t*)wbuf; + const size_t l2 = len; + do { + ASSERT(pos <= cache); + if (pos == cache) { + if (len >= bufSize) { + const size_t r = Base::read(b, len); + if (r == STREAM_ERROR) + return STREAM_ERROR; + return l2 - len + r; + } + pos = 0; + switch (cache = Base::read(buf, bufSize)) { + case 0: + return l2 - len; + case STREAM_ERROR: + return STREAM_ERROR; + } + } + const size_t n = MINF(cache - pos, len); + memcpy(b, buf + pos, n); + b += n; + pos += n; + len -= n; + } while (len > 0); + return l2; } - bool setPos(size_f_t wpos) { - if (wpos > size) - pos = size; - else - pos = wpos; - return true; + bool setPos(size_f_t wpos) override { + pos = cache = 0; + return Base::setPos(wpos); } - size_f_t getPos() const { - return pos; + size_f_t getPos() const override { + const size_f_t r = Base::getPos(); + if (r == SIZE_NA) + return SIZE_NA; + return r-(cache-pos); } - InputStream* getInputStream(int typ) { return (typ == LAYER_MASK ? this : this->s->getInputStream(typ)); } + enum { LAYER_ID_IN=1 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : Base::getInputStream(typ)); } private: - const size_f_t size; - const size_f_t startPos; - size_f_t pos; + uint8_t* const buf; + const size_t bufSize; + size_t cache; + size_t pos; }; -#define LAYER_BUFFER 2 - -template +template class BufferedOutputStream : public LayerOutputStream { public: - BufferedOutputStream(OutputStream* aStream, size_t aBufSize) : LayerOutputStream(aStream), buf(new uint8_t[aBufSize]), bufSize(aBufSize), pos(0) { } + typedef LayerOutputStream Base; + + BufferedOutputStream(OutputStream* aStream, size_t aBufSize) + : Base(aStream), buf(new uint8_t[aBufSize]), bufSize(aBufSize), pos(0) { } virtual ~BufferedOutputStream() { flush(); delete[] buf; } - size_t flush() { - size_t ret = 0; - if (pos > 0) { - ret = this->s->write(buf, pos); - pos = 0; - if (ret == STREAM_ERROR) - return STREAM_ERROR; - } - return ret + this->s->flush(); - } - - size_t write(const void* wbuf, size_t len) { + size_t write(const void* wbuf, size_t len) override { if (len < bufSize - pos) { memcpy(buf + pos, (const uint8_t*)wbuf, len); pos += len; @@ -100,11 +106,11 @@ class BufferedOutputStream : public LayerOutputStream { const size_t l2 = len; do { if (pos == bufSize) { - if (this->s->write(buf, bufSize) == STREAM_ERROR) + if (Base::write(buf, bufSize) == STREAM_ERROR) return STREAM_ERROR; pos = 0; if (len < bufSize) { - if (this->s->write(b, len) == STREAM_ERROR) + if (Base::write(b, len) == STREAM_ERROR) return STREAM_ERROR; break; } @@ -118,128 +124,134 @@ class BufferedOutputStream : public LayerOutputStream { return l2; } - OutputStream* getOutputStream(int typ) { return (typ == LAYER_BUFFER ? this : this->s->getOutputStream(typ)); } + size_f_t getSize() const override { + const size_f_t r = Base::getSize(); + if (r == SIZE_NA) + return SIZE_NA; + return r + pos; + } + + bool setPos(size_f_t wpos) override { + if (pos > 0) { + const size_t ret = Base::write(buf, pos); + pos = 0; + if (ret == STREAM_ERROR) + return false; + } + return Base::setPos(wpos); + } + + size_f_t getPos() const override { + const size_f_t r = Base::getPos(); + if (r == SIZE_NA) + return SIZE_NA; + return r + pos; + } + + size_t flush() override { + size_t ret = 0; + if (pos > 0) { + ret = Base::write(buf, pos); + pos = 0; + if (ret == STREAM_ERROR) + return STREAM_ERROR; + } + return ret + Base::flush(); + } + + enum { LAYER_ID_OUT=1 }; + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return (typ == LAYER_ID_OUT ? static_cast(this) : Base::getOutputStream(typ)); } private: uint8_t* const buf; const size_t bufSize; size_t pos; }; +/*----------------------------------------------------------------*/ -template -class BufferedInputStream : public LayerInputStream { + +template +class FilteredInputStream : public LayerInputStream { public: - BufferedInputStream(InputStream* aStream, size_t aBufSize) : LayerInputStream(aStream), buf(new uint8_t[aBufSize]), bufSize(aBufSize), maxCache(0), caching(0), pos(0) { } - virtual ~BufferedInputStream() { + typedef LayerInputStream Base; + + FilteredInputStream(InputStream* pFile, size_t nFilteredSize, size_t nBufSize, void* pData) + : Base(pFile), buf(new uint8_t[nBufSize]), filteredSize(nFilteredSize), bufSize(nBufSize), valid(0), pos(0), filter(pData), more(true) {} + virtual ~FilteredInputStream() { delete[] buf; } - size_t read(void* wbuf, size_t len) { - uint8_t* b = (uint8_t*)wbuf; + /** + * Read data through filter, keep calling until len returns 0. + * @param rbuf Data buffer + * @param len Buffer size on entry, bytes actually read on exit + * @return Length of data in buffer + */ + size_t read(void* rbuf, size_t len) override { + uint8_t* rb = (uint8_t*)rbuf; + const size_t l2 = len; - do { - if (caching < maxCache) { - size_t sliceLen = bufSize-caching; - if (sliceLen > FILE_READ_MINBUF_SIZE+(FILE_READ_MINBUF_SIZE/2)) - sliceLen = FILE_READ_MINBUF_SIZE; - const size_t r = this->s->read(buf+caching, sliceLen); - if (r == STREAM_ERROR) + while (more && len) { + if (pos == valid) { + valid = Base::read(buf, bufSize); + if (valid == STREAM_ERROR) return STREAM_ERROR; - caching += r; - if (r != sliceLen) { - maxCache = caching; - } - } - else if (pos == maxCache) { - if (len >= bufSize) { - const size_t r = this->s->read(b, len); - if (r == STREAM_ERROR) - return STREAM_ERROR; - return l2 - len + r; - } pos = 0; - switch (caching = this->s->read(buf, FILE_READ_MINBUF_SIZE)) { - case 0: - maxCache = 0; - return l2 - len; - case FILE_READ_MINBUF_SIZE: - maxCache = bufSize; - break; - case STREAM_ERROR: - return STREAM_ERROR; - default: // < FILE_READ_MINBUF_SIZE - maxCache = caching; - } } - const size_t n = MINF(caching - pos, len); - memcpy(b, buf + pos, n); - b += n; - pos += n; + size_t m = valid - pos; + size_t n = len; + more = filter(buf + pos, m, rb, n); + pos += m; + rb += n; len -= n; - } while (len > 0); - return l2; + } + return l2-len; } - bool setPos(size_f_t wpos) { - pos = caching = maxCache = 0; - return this->s->setPos(wpos); + size_f_t getSize() const override { + return filteredSize; + } + + bool setPos(size_f_t wpos) override { + valid = pos = 0; + return Base::setPos(wpos); } - size_f_t getPos() const { - const size_f_t r = this->s->getPos(); + size_f_t getPos() const override { + const size_f_t r = Base::getPos(); if (r == SIZE_NA) return SIZE_NA; - return r-(caching-pos); + return r-(valid-pos); } - InputStream* getInputStream(int typ) { return (typ == LAYER_BUFFER ? this : this->s->getInputStream(typ)); } + enum { LAYER_ID_IN=2 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : Base::getInputStream(typ)); } private: uint8_t* const buf; + const size_t filteredSize; const size_t bufSize; - size_t maxCache; - size_t caching; + size_t valid; size_t pos; + Filter filter; + bool more; }; -#define LAYER_ZIP 3 - -template +template class FilteredOutputStream : public LayerOutputStream { public: - using OutputStream::write; + typedef LayerOutputStream Base; - FilteredOutputStream(OutputStream* aFile, size_t aBufSize, void* pData) : LayerOutputStream(aFile), buf(new uint8_t[aBufSize]), bufSize(aBufSize), filter(pData), flushed(false) {} + FilteredOutputStream(OutputStream* aFile, size_t aBufSize, void* pData) + : Base(aFile), buf(new uint8_t[aBufSize]), bufSize(aBufSize), filter(pData), flushed(false) {} virtual ~FilteredOutputStream() { + flush(); delete[] buf; } - size_t flush() { - if (flushed) - return this->s->flush(); - - flushed = true; - size_t written = 0; - - for (;;) { - size_t n = bufSize; - size_t zero = 0; - bool more = filter(NULL, zero, buf, n); - - written += this->s->write(buf, n); - - if (!more) - break; - } - const size_t r = this->s->flush(); - if (r == STREAM_ERROR) - return STREAM_ERROR; - return written + r; - } - - size_t write(const void* wbuf, size_t len) { + size_t write(const void* wbuf, size_t len) override { if (flushed) return STREAM_ERROR; @@ -253,7 +265,7 @@ class FilteredOutputStream : public LayerOutputStream { wb += m; len -= m; - const size_t r = this->s->write(buf, n); + const size_t r = Base::write(buf, n); if (r == STREAM_ERROR) return STREAM_ERROR; written += r; @@ -268,92 +280,41 @@ class FilteredOutputStream : public LayerOutputStream { return written; } - OutputStream* getOutputStream(int typ) { return (typ == LAYER_ZIP ? this : this->s->getOutputStream(typ)); } - -private: - uint8_t* const buf; - const size_t bufSize; - Filter filter; - bool flushed; -}; - -template -class FilteredInputStream : public LayerInputStream { -public: - FilteredInputStream(InputStream* pFile, size_t nFilteredSize, size_t nBufSize, void* pData) : LayerInputStream(pFile), buf(new uint8_t[nBufSize]), filteredSize(nFilteredSize), bufSize(nBufSize), valid(0), pos(0), filter(pData), more(true) {} - virtual ~FilteredInputStream() { - delete[] buf; - } + size_t flush() override { + if (flushed) + return Base::flush(); - /** - * Read data through filter, keep calling until len returns 0. - * @param rbuf Data buffer - * @param len Buffer size on entry, bytes actually read on exit - * @return Length of data in buffer - */ - size_t read(void* rbuf, size_t len) { - uint8_t* rb = (uint8_t*)rbuf; + flushed = true; + size_t written = 0; - const size_t l2 = len; - while (more && len) { - #if ZIP_MODE == BZIP2_MODE - size_t n = len; - filter.flush(rb, n); - rb += n; - len -= n; - #endif + while (true) { + size_t n = bufSize; + size_t zero = 0; + bool more = filter(NULL, zero, buf, n); - if (pos == valid) { - valid = this->s->read(buf, bufSize); - if (valid == STREAM_ERROR) - return STREAM_ERROR; - pos = 0; - } + written += Base::write(buf, n); - size_t m = valid - pos; - #if ZIP_MODE == BZIP2_MODE - n = len; - #else - size_t n = len; - #endif - more = filter(buf + pos, m, rb, n); - pos += m; - rb += n; - len -= n; + if (!more) + break; } - return l2-len; - } - - size_f_t getSize() const { - return filteredSize; - } - - bool setPos(size_f_t wpos) { - valid = pos = 0; - return this->s->setPos(wpos); - } - - size_f_t getPos() const { - const size_f_t r = this->s->getPos(); - if (r == SIZE_NA) - return SIZE_NA; - return r-(valid-pos); + const size_t r = Base::flush(); + if (r == STREAM_ERROR) + return STREAM_ERROR; + return written + r; } - InputStream* getInputStream(int typ) { return (typ == LAYER_ZIP ? this : this->s->getInputStream(typ)); } + enum { LAYER_ID_OUT=2 }; + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return (typ == LAYER_ID_OUT ? static_cast(this) : Base::getOutputStream(typ)); } private: uint8_t* const buf; - const size_t filteredSize; const size_t bufSize; - size_t valid; - size_t pos; Filter filter; - bool more; + bool flushed; }; +/*----------------------------------------------------------------*/ -#define LAYER_TOKEN 4 #ifdef _UNICODE #define TOKEN_SIZE 2 //in bytes @@ -362,12 +323,14 @@ class FilteredInputStream : public LayerInputStream { #endif #define TOKEN_MAXBUF (2048*TOKEN_SIZE) //in bytes #define TOKEN_MAXIGN 32 //in TCHARs -template + +template class TokenInputStream : public LayerInputStream { public: typedef LayerInputStream Base; - TokenInputStream(InputStream* aStream, TCHAR aToken=_T('\n')) : LayerInputStream(aStream), pos(TOKEN_MAXBUF), eos(false), token(aToken) { arrIgnore[0] = _T('\0'); } + TokenInputStream(InputStream* aStream, TCHAR aToken=_T('\n')) + : Base(aStream), pos(TOKEN_MAXBUF), eos(false), token(aToken) { arrIgnore[0] = _T('\0'); } virtual ~TokenInputStream() { } @@ -504,9 +467,9 @@ class TokenInputStream : public LayerInputStream { return l2-len; } // if end of stream return - if (n < len / TOKEN_SIZE) { + if (n < len * TOKEN_SIZE) { eos = true; - return l2-len; + return n / TOKEN_SIZE; } // we reached the end of the buffer, signal it return l2+1; @@ -546,7 +509,7 @@ class TokenInputStream : public LayerInputStream { return ret; } - size_t read(void* wbuf, size_t len) { + size_t read(void* wbuf, size_t len) override { size_t n = 0; if (pos < TOKEN_MAXBUF) { n = TOKEN_MAXBUF-pos; @@ -558,20 +521,20 @@ class TokenInputStream : public LayerInputStream { } if (len == 0) return n; - const size_t r = this->s->read((uint8_t*)wbuf+n, len); + const size_t r = Base::read((uint8_t*)wbuf+n, len); if (r == STREAM_ERROR) return STREAM_ERROR; return n+r; } - bool setPos(size_f_t wpos) { + bool setPos(size_f_t wpos) override { eos = false; pos = TOKEN_MAXBUF; - return this->s->setPos(wpos); + return Base::setPos(wpos); } - size_f_t getPos() const { - const size_f_t r = this->s->getPos(); + size_f_t getPos() const override { + const size_f_t r = Base::getPos(); if (r == SIZE_NA) return SIZE_NA; return r-(TOKEN_MAXBUF-pos); @@ -581,7 +544,8 @@ class TokenInputStream : public LayerInputStream { return eos; } - InputStream* getInputStream(int typ) { return (typ == LAYER_TOKEN ? this : this->s->getInputStream(typ)); } + enum { LAYER_ID_IN=5 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : Base::getInputStream(typ)); } private: uint8_t buf[TOKEN_MAXBUF+TOKEN_SIZE]; @@ -592,6 +556,57 @@ class TokenInputStream : public LayerInputStream { }; /*----------------------------------------------------------------*/ + + +template +class MaskInputStream : public LayerInputStream { +public: + typedef LayerInputStream Base; + + MaskInputStream(InputStream* aStream, size_f_t nPos, size_f_t nSize) + : Base(aStream), startPos(nPos), size(nSize), pos(0) { } + virtual ~MaskInputStream() { } + + size_t read(void* wbuf, size_t len) override { + if (pos >= size) + return 0; + if (!Base::setPos(startPos + pos)) + return STREAM_ERROR; + if (pos+(size_f_t)len > size) + len = (size_t)(size - pos); + const size_t r = Base::read(wbuf, len); + if (r == STREAM_ERROR) + return STREAM_ERROR; + pos += r; + return r; + } + + size_f_t getSize() const override { + return size; + } + + bool setPos(size_f_t wpos) override { + if (wpos > size) + pos = size; + else + pos = wpos; + return true; + } + + size_f_t getPos() const override { + return pos; + } + + enum { LAYER_ID_IN=6 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : Base::getInputStream(typ)); } + +private: + const size_f_t size; + const size_f_t startPos; + size_f_t pos; +}; +/*----------------------------------------------------------------*/ + } // namespace SEACAVE #endif // __SEACAVE_FILTERS_H__ diff --git a/libs/Common/List.h b/libs/Common/List.h index 4e2906381..fbe108dfb 100644 --- a/libs/Common/List.h +++ b/libs/Common/List.h @@ -61,11 +61,11 @@ // constructs a cList reference to a given raw data array #ifndef CLISTREFRAW -#define CLISTREFRAW(CLIST, var, arr, sz) uint8_t _ArrData##var[sizeof(CLIST)]; new(_ArrData##var) CLIST(sz, arr); const CLIST& var(*((const CLIST*)_ArrData##var)) +#define CLISTREFRAW(CLIST, var, arr, sz) uint8_t _ArrData##var[sizeof(CLIST)]; new(_ArrData##var) CLIST(sz, const_cast(arr)); const CLIST& var(*reinterpret_cast(_ArrData##var)) #endif // constructs a cList reference to a given std::_vector #ifndef CLISTREFVECTOR -#define CLISTREFVECTOR(CLIST, var, vec) uint8_t _ArrData##var[sizeof(CLIST)]; new(_ArrData##var) CLIST(vec.size(), &vec[0]); const CLIST& var(*((const CLIST*)_ArrData##var)) +#define CLISTREFVECTOR(CLIST, var, vec) uint8_t _ArrData##var[sizeof(CLIST)]; new(_ArrData##var) CLIST(vec.size(), const_cast(&vec[0])); const CLIST& var(*reinterpret_cast(_ArrData##var)) #endif #define CLISTDEF0(TYPE) SEACAVE::cList< TYPE, const TYPE&, 0 > diff --git a/libs/Common/Octree.h b/libs/Common/Octree.h index aabbd336d..de1bbadbf 100644 --- a/libs/Common/Octree.h +++ b/libs/Common/Octree.h @@ -160,11 +160,10 @@ class TOctree inline const CELL_TYPE& GetRoot() const { return m_root; } inline TYPE GetRadius() const { return m_radius; } inline AABB_TYPE GetAabb() const { return m_root.GetAabb(m_radius); } - inline bool IsEmpty() const { return m_indices.IsEmpty(); } - inline size_t GetNumItems() const { return m_indices.GetSize(); } + inline bool IsEmpty() const { return m_indices.empty(); } + inline size_t GetNumItems() const { return m_indices.size(); } inline const IDXARR_TYPE& GetIndexArr() const { return m_indices; } inline const ITEM_TYPE* GetItems() const { return m_items; } - inline const POINT_TYPE& GetItem(IDX_TYPE idx) const { ASSERT(m_items != NULL); return m_items[idx]; } inline void ResetItems() { m_items = NULL; } protected: diff --git a/libs/Common/Octree.inl b/libs/Common/Octree.inl index e51d1feb9..0a5592981 100644 --- a/libs/Common/Octree.inl +++ b/libs/Common/Octree.inl @@ -183,7 +183,7 @@ void TOctree::_Insert(CELL_TYPE& cell, const P } childD; IDX_TYPE idx(start); for (IDX_TYPE i=0; i::_Collect(const CELL_TYPE& cell, // add all items contained by the bounding-box for (IDX_TYPE i=0; i::Collect(IDX_TYPE maxNeigh const POINT_TYPE center(aabb.GetCenter()); FOREACH(i, indices) { const IDX_TYPE& idx = indices[i]; - const TYPE score(-(center-GetItem(idx)).squaredNorm()); + const TYPE score(-(center-m_items[idx]).squaredNorm()); indexscores[i] = ItemIndexScore(idx,score); } indices.Empty(); diff --git a/libs/Common/Plane.h b/libs/Common/Plane.h index 8623928e9..7b396e961 100644 --- a/libs/Common/Plane.h +++ b/libs/Common/Plane.h @@ -44,11 +44,13 @@ class TPlane inline TPlane(const VECTOR&, const POINT&); inline TPlane(const POINT&, const POINT&, const POINT&); inline TPlane(const TYPE p[DIMS+1]); + inline TPlane(const Eigen::Matrix&); inline void Set(const VECTOR&, TYPE); inline void Set(const VECTOR&, const POINT&); inline void Set(const POINT&, const POINT&, const POINT&); inline void Set(const TYPE p[DIMS+1]); + inline void Set(const Eigen::Matrix&); int Optimize(const POINT*, size_t, int maxIters=100); template @@ -103,7 +105,7 @@ struct FitPlaneOnline { /*----------------------------------------------------------------*/ -// Basic frustum class +// Basic 3D frustum class // (represented as 6 planes oriented toward outside the frustum volume) template class TFrustum @@ -113,27 +115,25 @@ class TFrustum public: typedef Eigen::Matrix MATRIX4x4; typedef Eigen::Matrix MATRIX3x4; - typedef Eigen::Matrix VECTOR4; typedef Eigen::Matrix VECTOR; typedef Eigen::Matrix POINT; typedef SEACAVE::TPlane PLANE; typedef SEACAVE::TSphere SPHERE; typedef SEACAVE::TAABB AABB; + enum { numCorners = (1<<3) }; PLANE m_planes[DIMS]; // left, right, top, bottom, near and far planes //--------------------------------------- inline TFrustum() {} - inline TFrustum(const MATRIX4x4&); - inline TFrustum(const MATRIX3x4&); - inline TFrustum(const MATRIX4x4&, TYPE width, TYPE height, TYPE near=TYPE(0.0001), TYPE far=TYPE(1000)); - inline TFrustum(const MATRIX3x4&, TYPE width, TYPE height, TYPE near=TYPE(0.0001), TYPE far=TYPE(1000)); - - template void Set(const MATRIX4x4&); - template void Set(const MATRIX3x4&); - void Set(const MATRIX4x4&, TYPE width, TYPE height, TYPE near=TYPE(0.0001), TYPE far=TYPE(1000)); - void Set(const MATRIX3x4&, TYPE width, TYPE height, TYPE near=TYPE(0.0001), TYPE far=TYPE(1000)); + inline TFrustum(const MATRIX4x4&, TYPE width, TYPE height, TYPE nearZ=TYPE(0.0001), TYPE farZ=TYPE(1000)); + inline TFrustum(const MATRIX3x4&, TYPE width, TYPE height, TYPE nearZ=TYPE(0.0001), TYPE farZ=TYPE(1000)); + + void Set(const MATRIX4x4&, TYPE width, TYPE height, TYPE nearZ=TYPE(0.0001), TYPE farZ=TYPE(1000)); + void Set(const MATRIX3x4&, TYPE width, TYPE height, TYPE nearZ=TYPE(0.0001), TYPE farZ=TYPE(1000)); + void Set(const VECTOR corners[numCorners]); + void SetProjectionGL(const MATRIX4x4&); GCLASS Classify(const POINT&) const; GCLASS Classify(const SPHERE&) const; diff --git a/libs/Common/Plane.inl b/libs/Common/Plane.inl index bd7e61083..e1956cb87 100644 --- a/libs/Common/Plane.inl +++ b/libs/Common/Plane.inl @@ -36,6 +36,12 @@ template inline TPlane::TPlane(const TYPE p[DIMS+1]) { Set(p); +} +// Construct plane given its standard equation: Ax + By + Cz + D = 0 +template +inline TPlane::TPlane(const Eigen::Matrix& v) +{ + Set(v); } // constructors /*----------------------------------------------------------------*/ @@ -64,7 +70,14 @@ inline void TPlane::Set(const TYPE p[DIMS+1]) { const Eigen::Map vN(p); const TYPE invD(INVERT(vN.norm())); - Set(vN*invD, p[3]*invD); + Set(vN*invD, p[DIMS]*invD); +} +template +inline void TPlane::Set(const Eigen::Matrix& v) +{ + const VECTOR vN = v.template topLeftCorner<3,1>(); + const TYPE invD(INVERT(vN.norm())); + Set(vN*invD, v(DIMS)*invD); } // Set /*----------------------------------------------------------------*/ @@ -437,16 +450,6 @@ TYPE FitPlane(const TPoint3* points, size_t size, TPlane& plane) { // Construct frustum given a projection matrix. template -inline TFrustum::TFrustum(const MATRIX4x4& m) -{ - Set<0>(m); -} -template -inline TFrustum::TFrustum(const MATRIX3x4& m) -{ - Set<0>(m); -} -template inline TFrustum::TFrustum(const MATRIX4x4& m, TYPE w, TYPE h, TYPE n, TYPE f) { Set(m, w, h, n, f); @@ -459,124 +462,66 @@ inline TFrustum::TFrustum(const MATRIX3x4& m, TYPE w, TYPE h, TYPE n, /*----------------------------------------------------------------*/ -/** - * Retrieve active frustum planes, normals pointing outwards. - * -> IN/OUT: RDFRUSTUM - address to 6 planes - */ -template -template -void TFrustum::Set(const MATRIX4x4& m) -{ - // left plane - m_planes[0].Set(-(m.col(3)+m.col(0))); - // right plane - if (DIMS > 1) - m_planes[1].Set(-(m.col(3)-m.col(0))); - // top plane - if (DIMS > 2) - m_planes[2].Set(-(m.col(3)+m.col(1))); - // bottom plane - if (DIMS > 3) - m_planes[3].Set(-(m.col(3)-m.col(1))); - // near plane - if (DIMS > 4) - m_planes[4].Set(MODE ? -(m.col(3)+m.col(2)) : -m.col(2)); - // far plane - if (DIMS > 5) - m_planes[5].Set(-(m.col(3)-m.col(2))); -} -// same as above, but the last row of the matrix is (0,0,0,1) -template -template -void TFrustum::Set(const MATRIX3x4& m) -{ - // left plane - m_planes[0].Set(VECTOR4( - -(m(0,3)+m(0,0)), - -(m(1,3)+m(1,0)), - -(m(2,3)+m(2,0)), - -TYPE(1) - )); - // right plane - if (DIMS > 1) - m_planes[1].Set(VECTOR4( - -(m(0,3)-m(0,0)), - -(m(1,3)-m(1,0)), - -(m(2,3)-m(2,0)), - -TYPE(1) - )); - // top plane - if (DIMS > 2) - m_planes[2].Set(VECTOR4( - -(m(0,3)+m(0,1)), - -(m(1,3)+m(1,1)), - -(m(2,3)+m(2,1)), - -TYPE(1) - )); - // bottom plane - if (DIMS > 3) - m_planes[3].Set(VECTOR4( - -(m(0,3)-m(0,1)), - -(m(1,3)-m(1,1)), - -(m(2,3)-m(2,1)), - -TYPE(1) - )); - // near plane - if (DIMS > 4) - m_planes[4].Set(MODE ? - VECTOR4( - -(m(0,3)+m(0,2)), - -(m(1,3)+m(1,2)), - -(m(2,3)+m(2,2)), - -TYPE(1)) - : - VECTOR4( - -m(0,2), - -m(1,2), - -m(2,2), - TYPE(0)) - ); - // far plane - if (DIMS > 5) - m_planes[5].Set(VECTOR4( - -(m(0,3)-m(0,2)), - -(m(1,3)-m(1,2)), - -(m(2,3)-m(2,2)), - -TYPE(1) - )); -} -// same as above, but from SfM projection matrix and image plane details +// Set frustum planes, normals pointing outwards, from SfM projection matrix and image plane details template void TFrustum::Set(const MATRIX4x4& m, TYPE w, TYPE h, TYPE n, TYPE f) { - const VECTOR4 ltn(0,0,n,1), rtn(w*n,0,n,1), lbn(0,h*n,n,1), rbn(w*n,h*n,n,1); - const VECTOR4 ltf(0,0,f,1), rtf(w*f,0,f,1), lbf(0,h*f,f,1), rbf(w*f,h*f,f,1); + const VECTOR ltn(0,0,n), rtn(w*n,0,n), lbn(0,h*n,n), rbn(w*n,h*n,n); + const VECTOR ltf(0,0,f), rtf(w*f,0,f), lbf(0,h*f,f), rbf(w*f,h*f,f); const MATRIX4x4 inv(m.inverse()); - const VECTOR4 ltn3D(inv*ltn), rtn3D(inv*rtn), lbn3D(inv*lbn), rbn3D(inv*rbn); - const VECTOR4 ltf3D(inv*ltf), rtf3D(inv*rtf), lbf3D(inv*lbf), rbf3D(inv*rbf); - m_planes[0].Set(ltn3D.template topRows<3>(), ltf3D.template topRows<3>(), lbf3D.template topRows<3>()); - if (DIMS > 1) - m_planes[1].Set(rtn3D.template topRows<3>(), rbf3D.template topRows<3>(), rtf3D.template topRows<3>()); - if (DIMS > 2) - m_planes[2].Set(ltn3D.template topRows<3>(), rtf3D.template topRows<3>(), ltf3D.template topRows<3>()); - if (DIMS > 3) - m_planes[3].Set(lbn3D.template topRows<3>(), lbf3D.template topRows<3>(), rbf3D.template topRows<3>()); - if (DIMS > 4) - m_planes[4].Set(ltn3D.template topRows<3>(), lbn3D.template topRows<3>(), rbn3D.template topRows<3>()); - if (DIMS > 5) - m_planes[5].Set(ltf3D.template topRows<3>(), rtf3D.template topRows<3>(), rbf3D.template topRows<3>()); + const VECTOR corners[] = { + (inv*ltn.homogeneous()).template topRows<3>(), + (inv*rtn.homogeneous()).template topRows<3>(), + (inv*lbn.homogeneous()).template topRows<3>(), + (inv*rbn.homogeneous()).template topRows<3>(), + (inv*ltf.homogeneous()).template topRows<3>(), + (inv*rtf.homogeneous()).template topRows<3>(), + (inv*lbf.homogeneous()).template topRows<3>(), + (inv*rbf.homogeneous()).template topRows<3>() + }; + Set(corners); } template void TFrustum::Set(const MATRIX3x4& m, TYPE w, TYPE h, TYPE n, TYPE f) { MATRIX4x4 M(MATRIX4x4::Identity()); - #ifdef __GNUC__ - M.topLeftCorner(3,4) = m; - #else M.template topLeftCorner<3,4>() = m; - #endif Set(M, w, h, n, f); +} +// Set frustum planes, normals pointing outwards, from the given corners +template +void TFrustum::Set(const VECTOR corners[numCorners]) +{ + // left clipping plane + m_planes[0].Set(corners[0], corners[4], corners[6]); + if (DIMS > 1) // right clipping plane + m_planes[1].Set(corners[1], corners[7], corners[5]); + if (DIMS > 2) // top clipping plane + m_planes[2].Set(corners[0], corners[5], corners[4]); + if (DIMS > 3) // bottom clipping plane + m_planes[3].Set(corners[2], corners[6], corners[7]); + if (DIMS > 4) // near clipping plane + m_planes[4].Set(corners[0], corners[2], corners[3]); + if (DIMS > 5) // far clipping plane + m_planes[5].Set(corners[4], corners[5], corners[7]); } // Set +// Set frustum planes, normals pointing outwards, from the OpenGL projection-view matrix +template +void TFrustum::SetProjectionGL(const MATRIX4x4& mProjectionView) +{ + // left clipping plane + m_planes[0].Set(-mProjectionView.row(3) - mProjectionView.row(0)); + if (DIMS > 1) // right clipping plane + m_planes[1].Set(-mProjectionView.row(3) + mProjectionView.row(0)); + if (DIMS > 2) // top clipping plane + m_planes[2].Set(-mProjectionView.row(3) + mProjectionView.row(1)); + if (DIMS > 3) // bottom clipping plane + m_planes[3].Set(-mProjectionView.row(3) - mProjectionView.row(1)); + if (DIMS > 4) // near clipping plane + m_planes[4].Set(-mProjectionView.row(3) - mProjectionView.row(2)); + if (DIMS > 5) // far clipping plane + m_planes[5].Set(-mProjectionView.row(3) + mProjectionView.row(2)); +} /*----------------------------------------------------------------*/ diff --git a/libs/Common/Ray.inl b/libs/Common/Ray.inl index 1078f3086..34d4f455d 100644 --- a/libs/Common/Ray.inl +++ b/libs/Common/Ray.inl @@ -137,31 +137,35 @@ inline TRay& TRay::operator*=(const MATRIX& m) // test for intersection with triangle template template -bool TRay::Intersects(const TRIANGLE& tri, TYPE *t) const +bool TRay::Intersects(const TRIANGLE& tri, TYPE *pt) const { const VECTOR edge1(tri.b - tri.a); const VECTOR edge2(tri.c - tri.a); - - // if close to 0 ray is parallel const VECTOR pvec(m_vDir.cross(edge2)); const TYPE det(edge1.dot(pvec)); - if ((bCull && det < ZEROTOLERANCE()) || (!bCull && ISZERO(det))) + // check if ray and triangle plane are parallel + if ((bCull ? det : ABS(det)) < ZEROTOLERANCE() * TYPE(0.01)) return false; - - // distance to plane, < 0 means beyond plane + const TYPE invDet(TYPE(1) / det); const VECTOR tvec(m_pOrig - tri.a); - const TYPE u(tvec.dot(pvec)); - if ((bCull && !ISINSIDE(u, -ZEROTOLERANCE(), det+ZEROTOLERANCE())) || (!bCull && (det > TYPE(0) ? (u < -ZEROTOLERANCE() || u > det+ZEROTOLERANCE()) : (u > ZEROTOLERANCE() || u < det-ZEROTOLERANCE())))) + const TYPE u(tvec.dot(pvec) * invDet); + // check if intersection is outside of the line segment defined by points a and c + if (u < -ZEROTOLERANCE() * TYPE(10)) return false; - const VECTOR qvec(tvec.cross(edge1)); - const TYPE v(m_vDir.dot(qvec)); - if ((bCull && (v < -ZEROTOLERANCE() || u+v > det+ZEROTOLERANCE())) || (!bCull && (det > TYPE(0) ? (v < -ZEROTOLERANCE() || u+v > det+ZEROTOLERANCE()) : (v > ZEROTOLERANCE() || u+v < det-ZEROTOLERANCE())))) + const TYPE v(m_vDir.dot(qvec) * invDet); + // check if intersection is outside of the line segment defined by points a and b + if (v < -ZEROTOLERANCE() * TYPE(10)) return false; - - if (t) - *t = (edge2.dot(qvec)) / det; - + // check if intersection is outside of the line segment defined by points b and c + if (u + v > TYPE(1) + ZEROTOLERANCE() * TYPE(10)) + return false; + const TYPE t(edge2.dot(qvec) * invDet); + // check if intersection is behind the ray origin + if (bCull && t < -ZEROTOLERANCE()) + return false; + if (pt) + *pt = t; return true; } // Intersects(Tri) /*----------------------------------------------------------------*/ @@ -922,25 +926,25 @@ bool TestRayTriangleIntersection(unsigned iters) { typedef SEACAVE::TRay Ray; typedef typename Ray::POINT Point; typedef typename Point::Scalar Type; - constexpr Type zeroEps(ZEROTOLERANCE() * 1000); - for (unsigned iter=0; iter::value ? 0.01f : 0.00001f); + for (unsigned iter=0, lives=iters/100; iter(triangle, &t)) + if (!rayCenter.template Intersects(triangle, &t) && !lives--) return false; const Point _center(rayCenter.GetPoint(t)); - if ((_center-center).norm() > zeroEps) + if ((_center-center).norm() > zeroEps && !lives--) return false; const BYTE o((BYTE)(RAND()%3)); - const Point side((triangle[o]+triangle[(o+1)%3]) / TYPE(2)); + const Point side(((triangle[o].template cast()+triangle[(o+1)%3].template cast()) / 2.0).template cast()); const Ray raySide(rayCenter.m_pOrig, side, true); - if (!raySide.template Intersects(triangle, &t)) + if (!raySide.template Intersects(triangle, &t) && !lives--) return false; const Point _side(raySide.GetPoint(t)); - if ((_side-side).norm() > zeroEps) + if ((_side-side).norm() > zeroEps && !lives--) return false; } return true; diff --git a/libs/Common/Streams.h b/libs/Common/Streams.h index b732ea528..3c02237b8 100644 --- a/libs/Common/Streams.h +++ b/libs/Common/Streams.h @@ -11,19 +11,14 @@ // I N C L U D E S ///////////////////////////////////////////////// +#include "AutoPtr.h" + // D E F I N E S /////////////////////////////////////////////////// #define SIZE_NA ((size_f_t)-1) #define STREAM_ERROR ((size_t)-1) -#define FILE_WRITE_MINBUF_SIZE (128*1024) // The min size the file write buffer should allocate. -#define FILE_WRITE_MAXBUF_SIZE (100*1024*1024) // The max size the file write buffer should allocate. -#define FILE_READ_MINBUF_SIZE (512*1024) // The min size the file read buffer should allocate. -#define FILE_READ_MAXBUF_SIZE (200*1024*1024) // The max size the file read buffer should allocate. - -#define LAYER_BASE 0 - namespace SEACAVE { @@ -41,6 +36,12 @@ typedef IOStream IOSTREAM; class GENERAL_API NOINITVTABLE Stream { public: virtual ~Stream() { } + + // Identify stream type + virtual InputStream* getInputStream(int) { return NULL; } + virtual OutputStream* getOutputStream(int) { return NULL; } + virtual IOStream* getIOStream(int, int) { return NULL; } + // Get the length of the stream. // SIZE_NA if there were errors. virtual size_f_t getSize() const = 0; @@ -51,6 +52,21 @@ class GENERAL_API NOINITVTABLE Stream { virtual size_f_t getPos() const = 0; }; +class GENERAL_API NOINITVTABLE InputStream : public Stream { +public: + virtual ~InputStream() { } + /** + * Call this function until it returns 0 to get all bytes. + * @return The number of bytes read. len reflects the number of bytes + * actually read from the stream source in this call. + * STREAM_ERROR if there were errors. + */ + virtual size_t read(void* buf, size_t len) = 0; + + enum { LAYER_ID_IN=0 }; + InputStream* getInputStream(int typ=LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? this : (InputStream*)NULL); } +}; + class GENERAL_API NOINITVTABLE OutputStream : public Stream { public: virtual ~OutputStream() { } @@ -70,12 +86,10 @@ class GENERAL_API NOINITVTABLE OutputStream : public Stream { if (len > 2048) { const size_t count((size_t)_vsctprintf(szFormat, args)); ASSERT(count != (size_t)-1); - TCHAR* const buffer(new TCHAR[count]); - _vsntprintf(buffer, count, szFormat, args); + CAutoPtrArr szBufferDyn(new TCHAR[count+1]); + _vsntprintf(szBufferDyn, count+1, szFormat, args); va_end(args); - const size_t size(write(buffer, count)); - delete[] buffer; - return size; + return write(szBufferDyn, count); } va_end(args); return write(szBuffer, len); @@ -100,75 +114,128 @@ class GENERAL_API NOINITVTABLE OutputStream : public Stream { */ virtual size_t flush() = 0; - virtual OutputStream* getOutputStream(int typ) { return (typ == LAYER_BASE ? this : NULL); } + enum { LAYER_ID_OUT=0 }; + OutputStream* getOutputStream(int typ=LAYER_ID_OUT) override { return (typ == LAYER_ID_OUT ? this : (OutputStream*)NULL); } }; -class GENERAL_API NOINITVTABLE InputStream : public Stream { +class GENERAL_API NOINITVTABLE IOStream : public InputStream, public OutputStream { public: - virtual ~InputStream() { } - /** - * Call this function until it returns 0 to get all bytes. - * @return The number of bytes read. len reflects the number of bytes - * actually read from the stream source in this call. - * STREAM_ERROR if there were errors. - */ - virtual size_t read(void* buf, size_t len) = 0; - - virtual InputStream* getInputStream(int typ) { return (typ == LAYER_BASE ? this : NULL); } + InputStream* getInputStream(int typ=LAYER_ID_IN) override { return InputStream::getInputStream(typ); } + OutputStream* getOutputStream(int typ=LAYER_ID_OUT) override { return OutputStream::getOutputStream(typ); } + IOStream* getIOStream(int typIn=LAYER_ID_IN, int typOut=LAYER_ID_OUT) override { + return ((InputStream*)this)->getInputStream(typIn) != NULL && + ((OutputStream*)this)->getOutputStream(typOut) != NULL ? this : (IOStream*)NULL; + } }; +/*----------------------------------------------------------------*/ -class GENERAL_API NOINITVTABLE IOStream : public InputStream, public OutputStream { + + +template +class LayerInputStream : public InputStream { public: - static LPCSTR getClassType() { return "IOStream"; } - virtual LPCSTR getClassName() const { return IOStream::getClassType(); } + LayerInputStream(InputStream* aStream) : s(aStream) { ASSERT(s != NULL); } + virtual ~LayerInputStream() noexcept { if (managed) delete s; } + + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return s->getInputStream(typ); } + + size_t read(void* wbuf, size_t len) override { + return s->read(wbuf, len); + } + + size_f_t getSize() const override { + return s->getSize(); + } + + bool setPos(size_f_t wpos) override { + return s->setPos(wpos); + } + + size_f_t getPos() const override { + return s->getPos(); + } + +protected: + InputStream* const s; }; -template +template class LayerOutputStream : public OutputStream { public: LayerOutputStream(OutputStream* aStream) : s(aStream) {} - virtual ~LayerOutputStream() { if (managed) delete s; } + virtual ~LayerOutputStream() noexcept { if (managed) delete s; } - OutputStream* getOutputStream(int typ) { return s->getOutputStream(typ); } + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return s->getOutputStream(typ); } - size_f_t getSize() const { + size_t write(const void* buf, size_t len) override { + return s->write(buf, len); + } + + size_t flush() override { + return s->flush(); + } + + size_f_t getSize() const override { return s->getSize(); } - bool setPos(size_f_t wpos) { + bool setPos(size_f_t wpos) override { return s->setPos(wpos); } - size_f_t getPos() const { + size_f_t getPos() const override { return s->getPos(); } protected: OutputStream* const s; }; +/*----------------------------------------------------------------*/ -template -class LayerInputStream : public InputStream { + + +template +class LayerIOStream : public IOStream { public: - LayerInputStream(InputStream* aStream) : s(aStream) { ASSERT(s != NULL); } - virtual ~LayerInputStream() { if (managed) delete s; } + LayerIOStream(InputStream* aStream) : s(aStream), sIn(aStream), sOut(NULL) { ASSERT(aStream != NULL); } + LayerIOStream(OutputStream* aStream) : s(aStream), sIn(NULL), sOut(aStream) { ASSERT(aStream != NULL); } + LayerIOStream(InputStream* streamIn, OutputStream* streamOut) : s(NULL), sIn(streamIn), sOut(streamOut) { ASSERT(sIn != NULL || sOut != NULL); } + virtual ~LayerIOStream() noexcept { if (managed) { delete sIn; delete sOut; } } - InputStream* getInputStream(int typ) { return s->getInputStream(typ); } + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (sIn ? sIn->getInputStream(typ) : NULL); } + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return (sOut ? sOut->getOutputStream(typ) : NULL); } - size_f_t getSize() const { + size_f_t getSize() const override { return s->getSize(); } - bool setPos(size_f_t wpos) { + bool setPos(size_f_t wpos) override { return s->setPos(wpos); } - size_f_t getPos() const { + size_f_t getPos() const override { return s->getPos(); } + size_t read(void* wbuf, size_t len) override { + return sIn->read(wbuf, len); + } + + size_t write(const void* buf, size_t len) override { + return sOut->write(buf, len); + } + + size_t flush() override { + return sOut->flush(); + } + + operator InputStream* () const { return sIn; } + operator OutputStream* () const { return sOut; } + protected: - InputStream* const s; + Stream* const s; + InputStream* const sIn; + OutputStream* const sOut; }; /*----------------------------------------------------------------*/ diff --git a/libs/Common/Strings.h b/libs/Common/Strings.h index 187f32d0e..aae49c8d3 100644 --- a/libs/Common/Strings.h +++ b/libs/Common/Strings.h @@ -41,10 +41,10 @@ class GENERAL_API String : public std::string inline String& operator=(Base&& rhs) { Base::operator=(std::forward(rhs)); return *this; } inline String& operator=(String&& rhs) { Base::operator=(std::forward(rhs)); return *this; } + #endif inline String& operator=(TCHAR rhs) { Base::operator=(rhs); return *this; } inline String& operator=(LPCTSTR rhs) { Base::operator=(rhs); return *this; } inline String& operator=(const String& rhs) { Base::operator=(rhs); return *this; } - #endif inline String& operator+=(TCHAR rhs) { *this = (*this) + rhs; return *this; } inline String& operator+=(LPCTSTR rhs) { *this = (*this) + rhs; return *this; } diff --git a/libs/Common/Timer.cpp b/libs/Common/Timer.cpp index 332c6e41b..56e6af0b7 100644 --- a/libs/Common/Timer.cpp +++ b/libs/Common/Timer.cpp @@ -120,7 +120,7 @@ LPTSTR Timer::GetClock(uint8_t* nHH, uint8_t* nMM, LPTSTR szChar) const if (szChar == NULL) return NULL; - _stprintf(szChar, "%.2d:%.2d:%.2d", m_nHH, m_nMM, m_nSS); + _sntprintf(szChar, 32, "%.2d:%.2d:%.2d", m_nHH, m_nMM, m_nSS); return szChar; } // GetClock /*----------------------------------------------------------------*/ diff --git a/libs/Common/Types.cpp b/libs/Common/Types.cpp index a07e91652..7d7b8c96e 100644 --- a/libs/Common/Types.cpp +++ b/libs/Common/Types.cpp @@ -55,7 +55,7 @@ String cvMat2String(const TYPE* M, uint32_t rows, uint32_t cols, uint32_t step, for (uint32_t i=0; i inline bool ISFINITE(const _Tp* x, size_t n) { for (size_t i=0; i #ifdef _USE_EIGEN EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF_VECTORIZABLE_FIXED_SIZE(TYPE,2) typedef Eigen::Matrix EVec; + typedef Eigen::Map CEVecMap; typedef Eigen::Map EVecMap; #endif @@ -1283,7 +1284,7 @@ class TPoint2 : public cv::Point_ template inline TPoint2(const cv::Matx& rhs) : Base(rhs(0),rhs(1)) {} template inline TPoint2(const cv::Matx& rhs) : Base(rhs(0),rhs(1)) {} #ifdef _USE_EIGEN - inline TPoint2(const EVec& rhs) { operator EVec& () = rhs; } + inline TPoint2(const EVec& rhs) { operator EVecMap () = rhs; } #endif explicit inline TPoint2(const TYPE& _x) : Base(_x,_x) {} inline TPoint2(const TYPE& _x, const TYPE& _y) : Base(_x,_y) {} @@ -1293,11 +1294,11 @@ class TPoint2 : public cv::Point_ template inline TPoint2& operator = (const cv::Matx& rhs) { operator Vec& () = rhs; return *this; } template inline TPoint2& operator = (const cv::Matx& rhs) { operator VecT& () = rhs; return *this; } #ifdef _USE_EIGEN - inline TPoint2& operator = (const EVec& rhs) { operator EVec& () = rhs; return *this; } + inline TPoint2& operator = (const EVec& rhs) { operator EVecMap () = rhs; return *this; } #endif // conversion to another data type - template inline operator TPoint2 () const { return TPoint2(x,y); } + template inline operator TPoint2 () const { return TPoint2((T)x,(T)y); } // pointer to the first element access inline const TYPE* ptr() const { return &x; } @@ -1321,10 +1322,9 @@ class TPoint2 : public cv::Point_ #ifdef _USE_EIGEN // Access point as Eigen equivalent - inline operator const EVec& () const { return *((const EVec*)this); } - inline operator EVec& () { return *((EVec*)this); } + inline operator EVec () const { return CEVecMap((const TYPE*)this); } // Access point as Eigen::Map equivalent - inline operator const EVecMap () const { return EVecMap((TYPE*)this); } + inline operator const CEVecMap () const { return CEVecMap((const TYPE*)this); } inline operator EVecMap () { return EVecMap((TYPE*)this); } #endif @@ -1359,6 +1359,7 @@ class TPoint3 : public cv::Point3_ #ifdef _USE_EIGEN EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF_VECTORIZABLE_FIXED_SIZE(TYPE,3) typedef Eigen::Matrix EVec; + typedef Eigen::Map CEVecMap; typedef Eigen::Map EVecMap; #endif @@ -1375,7 +1376,7 @@ class TPoint3 : public cv::Point3_ template inline TPoint3(const cv::Matx& rhs) : Base(rhs(0),rhs(1),rhs(2)) {} template inline TPoint3(const cv::Matx& rhs) : Base(rhs(0),rhs(1),rhs(2)) {} #ifdef _USE_EIGEN - inline TPoint3(const EVec& rhs) { operator EVec& () = rhs; } + inline TPoint3(const EVec& rhs) { operator EVecMap () = rhs; } #endif explicit inline TPoint3(const TYPE& _x) : Base(_x,_x,_x) {} inline TPoint3(const TYPE& _x, const TYPE& _y, const TYPE& _z) : Base(_x,_y,_z) {} @@ -1386,11 +1387,11 @@ class TPoint3 : public cv::Point3_ template inline TPoint3& operator = (const cv::Matx& rhs) { operator Vec& () = rhs; return *this; } template inline TPoint3& operator = (const cv::Matx& rhs) { operator VecT& () = rhs; return *this; } #ifdef _USE_EIGEN - inline TPoint3& operator = (const EVec& rhs) { operator EVec& () = rhs; return *this; } + inline TPoint3& operator = (const EVec& rhs) { operator EVecMap () = rhs; return *this; } #endif // conversion to another data type - template inline operator TPoint3 () const { return TPoint3(x,y,z); } + template inline operator TPoint3 () const { return TPoint3((T)x,(T)y,(T)z); } // pointer to the first element access inline const TYPE* ptr() const { return &x; } @@ -1401,19 +1402,18 @@ class TPoint3 : public cv::Point3_ inline TYPE& operator [](BYTE i) { ASSERT(i<3); return ptr()[i]; } // Access point as vector equivalent - inline operator const Vec& () const { return *((const Vec*)this); } - inline operator Vec& () { return *((Vec*)this); } + inline operator const Vec& () const { return *reinterpret_cast(this); } + inline operator Vec& () { return *reinterpret_cast(this); } // Access point as transposed vector equivalent - inline operator const VecT& () const { return *((const VecT*)this); } - inline operator VecT& () { return *((VecT*)this); } + inline operator const VecT& () const { return *reinterpret_cast(this); } + inline operator VecT& () { return *reinterpret_cast(this); } #ifdef _USE_EIGEN // Access point as Eigen equivalent - inline operator const EVec& () const { return *((const EVec*)this); } - inline operator EVec& () { return *((EVec*)this); } + inline operator EVec () const { return CEVecMap((const TYPE*)this); } // Access point as Eigen::Map equivalent - inline operator const EVecMap () const { return EVecMap((TYPE*)this); } + inline operator const EVecMap () const { return CEVecMap((const TYPE*)this); } inline operator EVecMap () { return EVecMap((TYPE*)this); } #endif @@ -1470,7 +1470,7 @@ class TMatrix : public cv::Matx template inline TMatrix(const cv::Point3_& rhs) : Base(rhs.x, rhs.y, rhs.z) {} inline TMatrix(const cv::Mat& rhs) : Base(rhs) {} #ifdef _USE_EIGEN - inline TMatrix(const EMat& rhs) { operator EMat& () = rhs; } + inline TMatrix(const EMat& rhs) { operator EMatMap () = rhs; } #endif TMatrix(TYPE v0); //!< 1x1 matrix @@ -1507,7 +1507,7 @@ class TMatrix : public cv::Matx template inline TMatrix& operator = (const cv::Matx& rhs) { Base::operator = (rhs); return *this; } inline TMatrix& operator = (const cv::Mat& rhs) { Base::operator = (rhs); return *this; } #ifdef _USE_EIGEN - inline TMatrix& operator = (const EMat& rhs) { operator EMat& () = rhs; return *this; } + inline TMatrix& operator = (const EMat& rhs) { operator EMatMap () = rhs; return *this; } #endif inline bool IsEqual(const Base&) const; @@ -1518,13 +1518,12 @@ class TMatrix : public cv::Matx inline TYPE& operator [](size_t i) { ASSERT(i(this); } + inline operator Vec& () { return *reinterpret_cast(this); } #ifdef _USE_EIGEN // Access point as Eigen equivalent - inline operator const EMat& () const { return *((const EMat*)this); } - inline operator EMat& () { return *((EMat*)this); } + inline operator EMat () const { return CEMatMap((const TYPE*)val); } // Access point as Eigen::Map equivalent inline operator CEMatMap() const { return CEMatMap((const TYPE*)val); } inline operator EMatMap () { return EMatMap((TYPE*)val); } @@ -1560,6 +1559,7 @@ class TDMatrix : public cv::Mat_ typedef cv::Size Size; #ifdef _USE_EIGEN typedef Eigen::Matrix EMat; + typedef Eigen::Map CEMatMap; typedef Eigen::Map EMatMap; #endif @@ -1739,8 +1739,10 @@ class TDMatrix : public cv::Mat_ inline TYPE* getData() { ASSERT(cv::Mat::empty() || cv::Mat::isContinuous()); return (TYPE*)data; } #ifdef _USE_EIGEN + // Access point as Eigen equivalent + inline operator EMat () const { return CEMatMap(getData(), rows, cols); } // Access point as Eigen::Map equivalent - inline operator const EMatMap () const { return EMatMap(getData(), rows, cols); } + inline operator const CEMatMap () const { return CEMatMap(getData(), rows, cols); } inline operator EMatMap () { return EMatMap(getData(), rows, cols); } #endif @@ -2143,7 +2145,11 @@ class TImage : public TDMatrix inline TImage(const Size& sz) : Base(sz) {} inline TImage(const Size& sz, const TYPE& v) : Base(sz, v) {} inline TImage(const Size& sz, TYPE* _data, size_t _step=Base::AUTO_STEP) : Base(sz.height, sz.width, _data, _step) {} + #ifdef _SUPPORT_CPP11 + inline TImage(cv::Mat&& rhs) : Base(std::forward(rhs)) {} + inline TImage& operator = (cv::Mat&& rhs) { BaseBase::operator=(std::forward(rhs)); return *this; } + #endif inline TImage& operator = (const Base& rhs) { BaseBase::operator=(rhs); return *this; } inline TImage& operator = (const BaseBase& rhs) { BaseBase::operator=(rhs); return *this; } inline TImage& operator = (const cv::MatExpr& rhs) { BaseBase::operator=(rhs); return *this; } @@ -2184,7 +2190,7 @@ class TImage : public TDMatrix template static void RasterizeTriangle(const TPoint2& v1, const TPoint2& v2, const TPoint2& v3, PARSER& parser); - template + template static void RasterizeTriangleBary(const TPoint2& v1, const TPoint2& v2, const TPoint2& v3, PARSER& parser); template static void RasterizeTriangleDepth(TPoint3 p1, TPoint3 p2, TPoint3 p3, PARSER& parser); @@ -2399,7 +2405,7 @@ struct TAccumulator { AccumType value; WeightType weight; - inline TAccumulator() : value(0), weight(0) {} + inline TAccumulator() : value(INITTO(static_cast(NULL), 0)), weight(0) {} inline TAccumulator(const Type& v, const WeightType& w) : value(v), weight(w) {} inline bool IsEmpty() const { return weight <= 0; } // adds the given weighted value to the internal value diff --git a/libs/Common/Types.inl b/libs/Common/Types.inl index d4129e1ad..b2d6ed292 100644 --- a/libs/Common/Types.inl +++ b/libs/Common/Types.inl @@ -574,33 +574,33 @@ FORCEINLINE bool ISEQUAL(const cv::Matx& v1, const cv::Matx& template FORCEINLINE SEACAVE::TPoint2 Floor2Int(const cv::Point_& v) { - return SEACAVE::TPoint2(FLOOR2INT(v.x), FLOOR2INT(v.y)); + return SEACAVE::TPoint2(FLOOR2INT(v.x), FLOOR2INT(v.y)); } template FORCEINLINE SEACAVE::TPoint2 Ceil2Int(const cv::Point_& v) { - return SEACAVE::TPoint2(CEIL2INT(v.x), CEIL2INT(v.y)); + return SEACAVE::TPoint2(CEIL2INT(v.x), CEIL2INT(v.y)); } template FORCEINLINE SEACAVE::TPoint2 Round2Int(const cv::Point_& v) { - return SEACAVE::TPoint2(ROUND2INT(v.x), ROUND2INT(v.y)); + return SEACAVE::TPoint2(ROUND2INT(v.x), ROUND2INT(v.y)); } template FORCEINLINE SEACAVE::TPoint3 Floor2Int(const cv::Point3_& v) { - return SEACAVE::TPoint3(FLOOR2INT(v.x), FLOOR2INT(v.y), FLOOR2INT(v.z)); + return SEACAVE::TPoint3(FLOOR2INT(v.x), FLOOR2INT(v.y), FLOOR2INT(v.z)); } template FORCEINLINE SEACAVE::TPoint3 Ceil2Int(const cv::Point3_& v) { - return SEACAVE::TPoint3(CEIL2INT(v.x), CEIL2INT(v.y), CEIL2INT(v.z)); + return SEACAVE::TPoint3(CEIL2INT(v.x), CEIL2INT(v.y), CEIL2INT(v.z)); } template FORCEINLINE SEACAVE::TPoint3 Round2Int(const cv::Point3_& v) { - return SEACAVE::TPoint3(ROUND2INT(v.x), ROUND2INT(v.y), ROUND2INT(v.z)); + return SEACAVE::TPoint3(ROUND2INT(v.x), ROUND2INT(v.y), ROUND2INT(v.z)); } template @@ -608,7 +608,7 @@ FORCEINLINE SEACAVE::TMatrix Floor2Int(const cv::Matx& v) { SEACAVE::TMatrix nv; for (int i=0; i(v.val[i]); return nv; } template @@ -616,7 +616,7 @@ FORCEINLINE SEACAVE::TMatrix Ceil2Int(const cv::Matx& v) { SEACAVE::TMatrix nv; for (int i=0; i(v.val[i]); return nv; } template @@ -624,7 +624,7 @@ FORCEINLINE SEACAVE::TMatrix Round2Int(const cv::Matx& v) { SEACAVE::TMatrix nv; for (int i=0; i(v.val[i]); return nv; } @@ -666,6 +666,29 @@ FORCEINLINE bool ISFINITE(const Eigen::Matrix& m) { return ISFINITE(m.data(), m.size()); } + + +// initializing both scalar and matrix variables +template +FORCEINLINE Scalar INITTO(const Scalar*, Value v) +{ + return static_cast(v); +} +template +FORCEINLINE TPoint2 INITTO(const TPoint2*, Value v) +{ + return TPoint2(static_cast(v)); +} +template +FORCEINLINE TPoint3 INITTO(const TPoint3*, Value v) +{ + return TPoint3(static_cast(v)); +} +template +FORCEINLINE Eigen::Matrix INITTO(const Eigen::Matrix*, Value v) +{ + return Eigen::Matrix::Constant(static_cast(v)); +} /*----------------------------------------------------------------*/ template @@ -2602,7 +2625,7 @@ void TImage::RasterizeTriangle(const TPoint2& v1, const TPoint2& v2, // same as above, but raster a triangle using barycentric coordinates: // https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation template -template +template void TImage::RasterizeTriangleBary(const TPoint2& v1, const TPoint2& v2, const TPoint2& v3, PARSER& parser) { // compute bounding-box fully containing the triangle @@ -2613,30 +2636,34 @@ void TImage::RasterizeTriangleBary(const TPoint2& v1, const TPoint2& if (boxMax.x < T(0) || boxMin.x > T(size.width - 1) || boxMax.y < T(0) || boxMin.y > T(size.height - 1)) return; - // ignore back oriented triangles (negative area) - const T area(EdgeFunction(v1, v2, v3)); - if (area <= 0) - return; // clip bounding-box to be fully contained by the image ImageRef boxMinI(FLOOR2INT(boxMin)); ImageRef boxMaxI(CEIL2INT(boxMax)); Base::clip(boxMinI, boxMaxI, size); + // ignore back oriented triangles (negative area) + const T area(EdgeFunction(v1, v2, v3)); + if (CULL && area <= 0) + return; // parse all pixels inside the bounding-box const T invArea(T(1) / area); for (int y = boxMinI.y; y <= boxMaxI.y; ++y) { for (int x = boxMinI.x; x <= boxMaxI.x; ++x) { const ImageRef pt(x, y); const TPoint2 p(Cast(pt)); - const T b1(EdgeFunction(v2, v3, p)); + // discard point if not in triangle; + // testing only for negative barycentric coordinates + // guarantees all will be in [0,1] at the end of all checks + const T b1(EdgeFunction(v2, v3, p) * invArea); if (b1 < 0) continue; - const T b2(EdgeFunction(v3, v1, p)); + const T b2(EdgeFunction(v3, v1, p) * invArea); if (b2 < 0) continue; - const T b3(EdgeFunction(v1, v2, p)); + const T b3(EdgeFunction(v1, v2, p) * invArea); if (b3 < 0) continue; - parser(pt, TPoint3(b1, b2, b3) * invArea); + // output pixel + parser(pt, TPoint3(b1, b2, b3)); } } } @@ -3020,6 +3047,8 @@ bool TImage::Load(const String& fileName) cv::cvtColor(img, img, cv::COLOR_BGRA2GRAY); else if (img.channels() == 1 && Base::channels() == 4) cv::cvtColor(img, img, cv::COLOR_GRAY2BGRA); + else if (img.channels() == 4 && Base::channels() == 3) + cv::cvtColor(img, img, cv::COLOR_BGRA2BGR); } if (img.type() == Base::type()) cv::swap(img, *this); @@ -3040,7 +3069,7 @@ bool TImage::Save(const String& fileName) const } else if (ext == ".jpg") { compression_params.push_back(cv::IMWRITE_JPEG_QUALITY); - compression_params.push_back(80); + compression_params.push_back(95); } else if (ext == ".pfm") { if (Base::depth() != CV_32F) diff --git a/libs/Common/Util.h b/libs/Common/Util.h index 871f92835..fd313f903 100644 --- a/libs/Common/Util.h +++ b/libs/Common/Util.h @@ -205,9 +205,9 @@ class GENERAL_API Util public: static String getAppName() { #ifdef _MSC_VER - TCHAR buf[MAX_PATH+1]; - GetModuleFileName(NULL, buf, MAX_PATH); - return ensureUnifySlash(String(buf)); + String buf(MAX_PATH+1, '\0'); + GetModuleFileName(NULL, &buf.front(), MAX_PATH); + return ensureUnifySlash(buf); #else // _MSC_VER LPTSTR home = getenv("HOME"); if (home == NULL) @@ -457,7 +457,10 @@ class GENERAL_API Util } static String getFilePath(const String& path) { - const String::size_type i = path.rfind(PATH_SEPARATOR); + const String::size_type size = path.size(); + if (size < 3) + return String(); + const String::size_type i = path.rfind(PATH_SEPARATOR, size-2); return (i != String::npos) ? path.substr(0, i+1) : String(); } static String getFileFullName(const String& path) { @@ -595,34 +598,34 @@ class GENERAL_API Util uint32_t rez = (uint32_t)(sTime / ((int64_t)24*3600*1000)); if (rez) { ++nrNumbers; - len += _stprintf(buf+len, "%ud", rez); + len += _sntprintf(buf+len, 128, "%ud", rez); } if (nAproximate > 3 && nrNumbers > 0) return buf; rez = (uint32_t)((sTime%((int64_t)24*3600*1000)) / (3600*1000)); if (rez) { ++nrNumbers; - len += _stprintf(buf+len, "%uh", rez); + len += _sntprintf(buf+len, 128, "%uh", rez); } if (nAproximate > 2 && nrNumbers > 0) return buf; rez = (uint32_t)((sTime%((int64_t)3600*1000)) / (60*1000)); if (rez) { ++nrNumbers; - len += _stprintf(buf+len, "%um", rez); + len += _sntprintf(buf+len, 128, "%um", rez); } if (nAproximate > 1 && nrNumbers > 0) return buf; rez = (uint32_t)((sTime%((int64_t)60*1000)) / (1*1000)); if (rez) { ++nrNumbers; - len += _stprintf(buf+len, "%us", rez); + len += _sntprintf(buf+len, 128, "%us", rez); } if (nAproximate > 0 && nrNumbers > 0) return buf; rez = (uint32_t)(sTime%((int64_t)1*1000)); if (rez || !nrNumbers) - len += _stprintf(buf+len, "%ums", rez); + len += _sntprintf(buf+len, 128, "%ums", rez); return String(buf, len); } diff --git a/libs/Common/UtilCUDA.cpp b/libs/Common/UtilCUDA.cpp index f6b202519..dddc6d5a4 100644 --- a/libs/Common/UtilCUDA.cpp +++ b/libs/Common/UtilCUDA.cpp @@ -50,6 +50,7 @@ int _convertSMVer2Cores(int major, int minor) {0x70, 64 }, // Volta Generation (SM 7.0) GV100 class {0x72, 64 }, // Volta Generation (SM 7.2) GV10B class {0x75, 64 }, // Turing Generation (SM 7.5) TU1xx class + {0x80, 64 }, // Ampere Generation (SM 8.0) GA100 class {-1, -1} }; diff --git a/libs/Common/UtilCUDA.h b/libs/Common/UtilCUDA.h index a558198b8..44d869d6b 100644 --- a/libs/Common/UtilCUDA.h +++ b/libs/Common/UtilCUDA.h @@ -316,7 +316,6 @@ class KernelRT inline KernelRT() : hKernel(NULL) {} inline KernelRT(const ModuleRTPtr& _ptrModule, LPCSTR functionName) : ptrModule(_ptrModule) { Reset(functionName); } inline KernelRT(LPCSTR program, LPCSTR functionName, int mode=JIT::AUTO) { Reset(program, functionName, mode); } - inline ~KernelRT() { Release(); } inline bool IsValid() const { ASSERT(hKernel == NULL || (ptrModule != NULL && ptrModule->IsValid())); diff --git a/libs/IO/PLY.cpp b/libs/IO/PLY.cpp index a1b4d94a6..4dbfa5e11 100644 --- a/libs/IO/PLY.cpp +++ b/libs/IO/PLY.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////// // PLY.cpp // -// Copyright 2007 cDc@seacave +// Copyright 2023 cDc@seacave // Distributed under the Boost Software License, Version 1.0 // (See http://www.boost.org/LICENSE_1_0.txt) @@ -65,9 +65,8 @@ Init PLY data as empty. PLY::PLY() : - mfp(NULL), f(NULL), ostream(NULL), which_elem(NULL), other_elems(NULL), current_rules(NULL), rule_list(NULL), - write_type_names(type_names) + istream(NULL), mfp(NULL), write_type_names(type_names) { } @@ -83,17 +82,28 @@ Free the memory used by a PLY file. void PLY::release() { - flush(); if (mfp) { delete mfp; mfp = NULL; + ostream = NULL; + } else + if (istream) { + if (static_cast(istream)->getInputStream(ISTREAM::LAYER_ID_IN)) { + IOSTREAM* const iostream(static_cast(istream)->getIOStream(ISTREAM::LAYER_ID_IN, OSTREAM::LAYER_ID_OUT)); + if (iostream) + delete iostream; + else + delete istream; + } else { + ASSERT(static_cast(ostream)->getOutputStream(OSTREAM::LAYER_ID_OUT)); + IOSTREAM* const iostream(static_cast(ostream)->getIOStream(ISTREAM::LAYER_ID_IN, OSTREAM::LAYER_ID_OUT)); + if (iostream) + delete iostream; + else + delete ostream; + } + istream = NULL; } - f = NULL; - if (!filename.empty()) { - filename.clear(); - delete ostream; - } - ostream = NULL; if (!elems.empty()) { for (size_t i=0; iother_list.size(); ++i) { OtherElem& elem = other_elems->other_list[i]; delete[] elem.other_data; delete elem.other_props; } - delete other_elems; + delete other_elems; other_elems = NULL; } comments.clear(); obj_info.clear(); @@ -131,46 +141,61 @@ void PLY::release() Given a file pointer, get ready to write PLY data to the file. Entry: -f - the given file pointer +fp - the given file pointer nelems - number of elements in object elem_names - list of element names file_type - file type, either ascii or binary +memBufferSize - memory file initial size (useful if the ply size is unknown) Exit: returns a pointer to a PlyFile, used to refer to this file, or NULL if error ******************************************************************************/ -bool PLY::write(LPCSTR filename, int nelems, LPCSTR* elem_names, int file_type, size_t bufferSize) +bool PLY::write(LPCSTR _filename, int nelems, LPCSTR* elem_names, int _file_type, size_t memBufferSize) { - this->filename = filename; - return write(ostream, nelems, elem_names, file_type, bufferSize); + filename = _filename; + if (memBufferSize == 0) { + // create output file now + File* const pf(new File(filename.c_str(), File::WRITE, File::CREATE | File::TRUNCATE)); + if (!pf->isOpen()) + return false; + return write(new BufferedOutputStream(pf, 64*1024), nelems, elem_names, _file_type, memBufferSize); + } + return write((OSTREAM*)NULL, nelems, elem_names, _file_type, memBufferSize); } -bool PLY::write(OSTREAM* f, int nelems, LPCSTR* elem_names, int file_type, size_t bufferSize) +bool PLY::write(OSTREAM* fp, int nelems, LPCSTR* elem_names, int _file_type, size_t memBufferSize) { - /* create a record for this object */ - this->file_type = file_type; - this->version = 1.0; - this->ostream = f; - this->other_elems = NULL; + // create a record for this object + file_type = _file_type; + version = 1.0; + other_elems = NULL; - /* init buffer if requested */ - if (bufferSize > 0) { - mfp = new MemFile(bufferSize); - this->f = mfp; + if (memBufferSize > 0) { + // write ply into a memory buffer, and save it to disk at the end; + // useful in case the number of ply elements is not know from the start + // in order to avoid an additional file copy operation + ASSERT(fp == NULL && !filename.empty()); + mfp = new MemFile(memBufferSize); + ostream = mfp; } else { - this->f = f; + // directly write ply data to disk; + // in case the number of ply elements is unknown from the start, + // they are written into a temporary file and the header is written + // at the end using a file copy operation + ASSERT(fp != NULL); + ostream = fp; + mfp = NULL; } - /* tuck aside the names of the elements */ - this->elems.resize(nelems); + // tuck aside the names of the elements + elems.resize(nelems); for (int i = 0; i < nelems; ++i) { PlyElement* elem = new PlyElement; elem->name = elem_names[i]; elem->num = 0; - this->elems[i] = elem; + elems[i] = elem; } - return true; } @@ -187,19 +212,19 @@ prop_list - list of properties ******************************************************************************/ void PLY::element_layout( - const char *elem_name, + const char* elem_name, int nelems, int nprops, - PlyProperty *prop_list + PlyProperty* prop_list ) { - /* look for appropriate element */ + // look for appropriate element PlyElement *elem = find_element(elem_name); if (elem == NULL) abort_ply("error: element_layout: can't find element '%s'", elem_name); elem->num = nelems; - /* copy the list of properties */ + // copy the list of properties elem->props.resize(nprops); elem->store_prop.resize(nprops); @@ -220,21 +245,21 @@ elem_name - name of element that information is being specified about prop - the new property ******************************************************************************/ -void PLY::describe_property(const char *elem_name, const PlyProperty& prop) +void PLY::describe_property(const char* elem_name, const PlyProperty& prop) { - /* look for appropriate element */ + // look for appropriate element put_element_setup(elem_name); - /* describe property */ + // describe property describe_property(prop); } -void PLY::describe_property(const char *elem_name, int nprops, const PlyProperty* props) +void PLY::describe_property(const char* elem_name, int nprops, const PlyProperty* props) { - /* look for appropriate element */ + // look for appropriate element put_element_setup(elem_name); - /* describe properties */ + // describe properties for (int i=0; iisOpen()) + std::string filenameTmp; + if (mfp != NULL) { + // ply data was written into a memory buffer, + // now write the header to disk and append the data in memory + ostream = NULL; + } else if (!filename.empty() && ostream->getPos() > 0) { + // close this file, rename it, and open a new file to write the header + delete ostream; ostream = NULL; + filenameTmp = filename+".tmp"; + if (!File::renameFile(filename.c_str(), filenameTmp.c_str())) return false; - ostream = new BufferedOutputStream(ostream, FILE_WRITE_MINBUF_SIZE); + } + if (ostream == NULL) { + File* const pf(new File(filename.c_str(), File::WRITE, File::CREATE | File::TRUNCATE)); + if (!pf->isOpen()) + return false; + ostream = new BufferedOutputStream(pf, 64*1024); } + // write header ostream->print("ply\n"); - switch (this->file_type) { + switch (file_type) { case ASCII: ostream->print("format ascii 1.0\n"); break; @@ -288,39 +327,38 @@ bool PLY::header_complete() ostream->print("format binary_little_endian 1.0\n"); break; default: - abort_ply("error: ply_header_complete: bad file type = %d\n", this->file_type); + abort_ply("error: ply_header_complete: bad file type = %d\n", file_type); } - /* write out the comments */ - for (size_t i = 0; i < this->comments.size(); ++i) - ostream->print("comment %s\n", this->comments[i].c_str()); + // write out the comments + for (size_t i = 0; i < comments.size(); ++i) + ostream->print("comment %s\n", comments[i].c_str()); - /* write out object information */ - for (size_t i = 0; i < this->obj_info.size(); ++i) - ostream->print("obj_info %s\n", this->obj_info[i].c_str()); + // write out object information + for (size_t i = 0; i < obj_info.size(); ++i) + ostream->print("obj_info %s\n", obj_info[i].c_str()); - /* write out information about each element */ - for (size_t i = 0; i < this->elems.size(); ++i) { - PlyElement *elem = this->elems[i]; + // write out information about each element + for (size_t i = 0; i < elems.size(); ++i) { + PlyElement *elem = elems[i]; + ASSERT(elem->num > 0); ostream->print("element %s %d\n", elem->name.c_str(), elem->num); - /* write out each property */ + // write out each property for (size_t j = 0; j < elem->props.size(); ++j) { PlyProperty *prop = elem->props[j]; if (prop->is_list == LIST) { ostream->print("property list "); - write_scalar_type(ostream, prop->count_external); + write_scalar_type(prop->count_external); ostream->print(" "); - write_scalar_type(ostream, prop->external_type); + write_scalar_type(prop->external_type); ostream->print(" %s\n", prop->name.c_str()); - } - else if (prop->is_list == STRING) { + } else if (prop->is_list == STRING) { ostream->print("property string"); ostream->print(" %s\n", prop->name.c_str()); - } - else { + } else { ostream->print("property "); - write_scalar_type(ostream, prop->external_type); + write_scalar_type(prop->external_type); ostream->print(" %s\n", prop->name.c_str()); } } @@ -328,12 +366,26 @@ bool PLY::header_complete() ostream->print("end_header\n"); - /* write the body also if fully buffered */ - if (mfp && mfp->getSize() > 0) { + if (mfp != NULL) { + // now write the ply data from memory to disk ostream->write(mfp->getBuffer(), mfp->getSize()); delete mfp; mfp = NULL; + } else if (!filenameTmp.empty()) { + // append the body of the ply from the temp file, and delete it + File ftmp(filenameTmp.c_str(), File::READ, File::OPEN); + if (!ftmp.isOpen()) + return false; + Unsigned8Arr buffer(256 * 1024); + size_t len; + while ((len = ftmp.read(buffer.data(), buffer.size())) > 0) + ostream->write(buffer.data(), len); + ftmp.close(); + File::deleteFile(filenameTmp.c_str()); + } else { + // element writing is starting next, reset counters + for (size_t i = 0; i < elems.size(); ++i) + elems[i]->num = 0; } - return true; } @@ -346,12 +398,12 @@ before a call to the routine ply_put_element(). elem_name - name of element we're talking about ******************************************************************************/ -void PLY::put_element_setup(const char *elem_name) +void PLY::put_element_setup(const char* elem_name) { PlyElement *elem = find_element(elem_name); if (elem == NULL) abort_ply("error: put_element_setup: can't find element '%s'", elem_name); - this->which_elem = elem; + which_elem = elem; } @@ -372,16 +424,16 @@ void PLY::put_element(const void* elem_ptr) ValueType val; char **other_ptr; - PlyElement *elem = this->which_elem; - elem_data = (char *)elem_ptr; - other_ptr = (char **)(((char *)elem_ptr) + elem->other_offset); + PlyElement *elem = which_elem; + elem_data = (char*)elem_ptr; + other_ptr = (char**)(elem_data + elem->other_offset); - /* write out either to an ascii or binary file */ - if (this->file_type == ASCII) { + // write out either to an ascii or binary file + if (file_type == ASCII) { - /* write an ascii file */ + // write an ascii file - /* write out each property of the element */ + // write out each property of the element for (size_t j = 0; j < elem->props.size(); ++j) { PlyProperty *prop = elem->props[j]; @@ -389,21 +441,21 @@ void PLY::put_element(const void* elem_ptr) if (elem->store_prop[j] == OTHER_PROP) elem_data = *other_ptr; else - elem_data = (char *)elem_ptr; + elem_data = (char*)elem_ptr; switch (prop->is_list) { - case SCALAR: { /* scalar */ + case SCALAR: { // scalar item = elem_data + prop->offset; get_stored_item((void*)item, prop->internal_type, val); write_ascii_item(val, prop->internal_type, prop->external_type); break; } - case LIST: { /* list */ + case LIST: { // list item = elem_data + prop->count_offset; get_stored_item((void*)item, prop->count_internal, val); write_ascii_item(val, prop->count_internal, prop->count_external); const int list_count(ValueType2Type(val, prop->count_external)); - item_ptr = (char **)(elem_data + prop->offset); + item_ptr = (char**)(elem_data + prop->offset); item = item_ptr[0]; const int item_size = ply_type_size[prop->internal_type]; for (int k = 0; k < list_count; k++) { @@ -413,10 +465,10 @@ void PLY::put_element(const void* elem_ptr) } break; } - case STRING: { /* string */ + case STRING: { // string item = elem_data + prop->offset; - char **str = (char **)item; - f->print("\"%s\"", *str); + char** str = (char**)item; + ostream->print("\"%s\"", *str); break; } default: @@ -424,33 +476,32 @@ void PLY::put_element(const void* elem_ptr) } } - f->print("\n"); - } - else { + ostream->print("\n"); + } else { - /* write a binary file */ + // write a binary file - /* write out each property of the element */ + // write out each property of the element for (size_t j = 0; j < elem->props.size(); ++j) { PlyProperty *prop = elem->props[j]; if (elem->store_prop[j] == OTHER_PROP) elem_data = *other_ptr; else - elem_data = (char *) elem_ptr; + elem_data = (char*)elem_ptr; switch (prop->is_list) { - case SCALAR: { /* scalar */ + case SCALAR: { // scalar item = elem_data + prop->offset; get_stored_item((void*)item, prop->internal_type, val); write_binary_item(val, prop->internal_type, prop->external_type); break; } - case LIST: { /* list */ + case LIST: { // list item = elem_data + prop->count_offset; int item_size = ply_type_size[prop->count_internal]; get_stored_item((void*)item, prop->count_internal, val); write_binary_item(val, prop->count_internal, prop->count_external); const int list_count(ValueType2Type(val, prop->count_external)); - item_ptr = (char **)(elem_data + prop->offset); + item_ptr = (char**)(elem_data + prop->offset); item = item_ptr[0]; item_size = ply_type_size[prop->internal_type]; for (int k = 0; k < list_count; k++) { @@ -460,16 +511,16 @@ void PLY::put_element(const void* elem_ptr) } break; } - case STRING: { /* string */ + case STRING: { // string item = elem_data + prop->offset; - char **str = (char **) item; + char** str = (char**)item; - /* write the length */ - const int len = (int)strlen(*str) + 1; - f->write(&len, sizeof(int)); + // write the length + const int len = (int)_tcslen(*str) + 1; + ostream->write(&len, sizeof(int)); - /* write the string, including the null character */ - f->write(*str, len); + // write the string, including the null character + ostream->write(*str, len); break; } default: @@ -478,17 +529,8 @@ void PLY::put_element(const void* elem_ptr) } } - /* if buffered mode, count element items */ - if (mfp) { - if (ostream != NULL) { - if (mfp->getSizeBuffer()-mfp->getSize() < 256) { - ostream->write(mfp->getBuffer(), mfp->getSize()); - mfp->setSize(0); - } - } else { - elem->num++; - } - } + // count element items + elem->num++; } @@ -502,7 +544,7 @@ void PLY::put_element(const void* elem_ptr) Given a file pointer, get ready to read PLY data from the file. Entry: -f - the given file pointer +fp - the given file pointer Exit: nelems - number of elements in object @@ -510,52 +552,51 @@ elem_names - list of element names returns a pointer to a PlyFile, used to refer to this file, or NULL if error ******************************************************************************/ -bool PLY::read(LPCSTR filename) +bool PLY::read(LPCSTR _filename) { - this->filename = filename; - istream = new File(filename, File::READ, File::OPEN); - if (!((File*)istream)->isOpen()) + filename = _filename; + File* const pf(new File(_filename, File::READ, File::OPEN)); + if (!pf->isOpen()) return false; - return read(new BufferedInputStream(istream, FILE_READ_MINBUF_SIZE)); + return read(new BufferedInputStream(pf, 64*1024)); } -bool PLY::read(ISTREAM* f) +bool PLY::read(ISTREAM* fp) { - /* create record for this object */ + // create record for this object ASSERT(elems.empty()); - this->istream = f; - this->other_elems = NULL; - this->rule_list = NULL; + other_elems = NULL; + rule_list = NULL; + istream = fp; - /* read and parse the file's header */ + // read and parse the file's header int nwords; char *orig_line; STRISTREAM sfp(istream); char **words = get_words(sfp, &nwords, &orig_line); if (words == NULL) return false; - if (!equal_strings (words[0], "ply")) { + if (!equal_strings(words[0], "ply")) { free(words); return false; } free(words); - /* parse words */ + // parse words while ((words = get_words(sfp, &nwords, &orig_line)) != NULL) { if (equal_strings(words[0], "format")) { if (nwords != 3) return false; if (equal_strings(words[1], "ascii")) - this->file_type = ASCII; + file_type = ASCII; else if (equal_strings(words[1], "binary_big_endian")) - this->file_type = BINARY_BE; + file_type = BINARY_BE; else if (equal_strings(words[1], "binary_little_endian")) - this->file_type = BINARY_LE; + file_type = BINARY_LE; else return false; - this->version = (float)atof(words[2]); - } - else if (equal_strings(words[0], "element")) + version = (float)atof(words[2]); + } else if (equal_strings(words[0], "element")) add_element((const char**)words, nwords); else if (equal_strings(words[0], "property")) add_property((const char**)words, nwords); @@ -571,16 +612,15 @@ bool PLY::read(ISTREAM* f) } sfp.emptyBuffer(); - /* create tags for each property of each element, to be used */ - /* later to say whether or not to store each property for the user */ - for (size_t i = 0; i < this->elems.size(); ++i) { - PlyElement *elem = this->elems[i]; + // create tags for each property of each element, to be used + // later to say whether or not to store each property for the user + for (size_t i = 0; i < elems.size(); ++i) { + PlyElement *elem = elems[i]; elem->store_prop.resize(elem->props.size()); for (size_t j = 0; j < elem->props.size(); ++j) elem->store_prop[j] = DONT_STORE_PROP; - elem->other_offset = NO_OTHER_PROPS; /* no "other" props by default */ + elem->other_offset = NO_OTHER_PROPS; // no "other" props by default } - return true; } @@ -596,14 +636,14 @@ props - the list of properties returned returns number of elements of this type in the file ******************************************************************************/ -int PLY::get_element_description(const char *elem_name, std::vector& prop_list) const +int PLY::get_element_description(const char* elem_name, std::vector& prop_list) const { - /* find information about the element */ + // find information about the element PlyElement *elem = find_element(elem_name); if (elem == NULL) return 0; - /* make a copy of the element's property list */ + // make a copy of the element's property list prop_list.resize(elem->props.size()); for (size_t i = 0; i < elem->props.size(); ++i) { PlyProperty *prop = new PlyProperty; @@ -626,32 +666,32 @@ prop_list - list of properties ******************************************************************************/ void PLY::get_element_setup( - const char *elem_name, + const char* elem_name, int nprops, - PlyProperty *prop_list + PlyProperty* prop_list ) { - /* find information about the element */ + // find information about the element PlyElement *elem = find_element(elem_name); - this->which_elem = elem; + which_elem = elem; - /* deposit the property information into the element's description */ + // deposit the property information into the element's description for (int i = 0; i < nprops; ++i) { - /* look for actual property */ + // look for actual property int index = find_property(elem, prop_list[i].name.c_str()); if (index == -1) { DEBUG("warning: Can't find property '%s' in element '%s'", prop_list[i].name.c_str(), elem_name); continue; } - /* store its description */ + // store its description PlyProperty *prop = elem->props[index]; prop->internal_type = prop_list[i].internal_type; prop->offset = prop_list[i].offset; prop->count_internal = prop_list[i].count_internal; prop->count_offset = prop_list[i].count_offset; - /* specify that the user wants this property */ + // specify that the user wants this property elem->store_prop[index] = STORE_PROP; } } @@ -668,19 +708,16 @@ elem_name - which element we're talking about prop - property to add to those that will be returned ******************************************************************************/ -void PLY::get_property( - const char *elem_name, - PlyProperty *prop - ) +void PLY::get_property(const char* elem_name, PlyProperty* prop) { - /* find information about the element */ + // find information about the element PlyElement *elem = find_element(elem_name); - this->which_elem = elem; + which_elem = elem; - /* deposit the property information into the element's description */ + // deposit the property information into the element's description int index = find_property(elem, prop->name.c_str()); if (index == -1) { - DEBUG("warning: Can't find property '%s' in element '%s'", prop->name.c_str(), elem_name); + DEBUG("warning: Can't find property '%s' in element '%s'", prop->name.c_str(), elem_name); return; } PlyProperty *prop_ptr = elem->props[index]; @@ -689,7 +726,7 @@ void PLY::get_property( prop_ptr->count_internal = prop->count_internal; prop_ptr->count_offset = prop->count_offset; - /* specify that the user wants this property */ + // specify that the user wants this property elem->store_prop[index] = STORE_PROP; } @@ -705,7 +742,7 @@ elem_ptr - pointer to location where the element information should be put void PLY::get_element(void* elem_ptr) { - if (this->file_type == ASCII) + if (file_type == ASCII) ascii_get_element((uint8_t*)elem_ptr); else binary_get_element((uint8_t*)elem_ptr); @@ -723,7 +760,7 @@ returns the list of comments std::vector& PLY::get_comments() { - return (this->comments); + return comments; } @@ -739,7 +776,7 @@ returns the list of object info lines std::vector& PLY::get_obj_info() { - return (this->obj_info); + return obj_info; } @@ -753,53 +790,53 @@ information. elem - element for which we want to save away other properties ******************************************************************************/ -void PLY::setup_other_props(PlyElement *elem) +void PLY::setup_other_props(PlyElement* elem) { int size = 0; - /* Examine each property in decreasing order of size. */ - /* We do this so that all data types will be aligned by */ - /* word, half-word, or whatever within the structure. */ + // Examine each property in decreasing order of size. + // We do this so that all data types will be aligned by + // word, half-word, or whatever within the structure. for (int type_size = 8; type_size > 0; type_size /= 2) { - /* add up the space taken by each property, and save this information */ - /* away in the property descriptor */ + // add up the space taken by each property, and save this information + // away in the property descriptor for (size_t i = 0; i < elem->props.size(); ++i) { - /* don't bother with properties we've been asked to store explicitly */ + // don't bother with properties we've been asked to store explicitly if (elem->store_prop[i]) continue; PlyProperty *prop = elem->props[i]; - /* internal types will be same as external */ + // internal types will be same as external prop->internal_type = prop->external_type; prop->count_internal = prop->count_external; - /* list case */ + // list case if (prop->is_list == LIST) { - /* pointer to list */ - if (type_size == sizeof (void *)) { + // pointer to list + if (type_size == sizeof(void *)) { prop->offset = size; - size += sizeof (void *); /* always use size of a pointer here */ + size += sizeof(void *); // always use size of a pointer here } - /* count of number of list elements */ + // count of number of list elements if (type_size == ply_type_size[prop->count_external]) { prop->count_offset = size; size += ply_type_size[prop->count_external]; } } - /* string */ + // string else if (prop->is_list == STRING) { - /* pointer to string */ - if (type_size == sizeof (char *)) { + // pointer to string + if (type_size == sizeof(char*)) { prop->offset = size; - size += sizeof (char *); + size += sizeof(char*); } } - /* scalar */ + // scalar else if (type_size == ply_type_size[prop->external_type]) { prop->offset = size; size += ply_type_size[prop->external_type]; @@ -808,7 +845,7 @@ void PLY::setup_other_props(PlyElement *elem) } - /* save the size for the other_props structure */ + // save the size for the other_props structure elem->other_size = size; } @@ -825,18 +862,18 @@ offset - offset to where other_props will be stored inside user's structure returns pointer to structure containing description of other_props ******************************************************************************/ -PLY::PlyOtherProp* PLY::get_other_properties(PlyElement *elem, int offset) +PLY::PlyOtherProp* PLY::get_other_properties(PlyElement* elem, int offset) { - /* remember that this is the "current" element */ - this->which_elem = elem; + // remember that this is the "current" element + which_elem = elem; - /* save the offset to where to store the other_props */ + // save the offset to where to store the other_props elem->other_offset = offset; - /* place the appropriate pointers, etc. in the element's property list */ + // place the appropriate pointers, etc. in the element's property list setup_other_props(elem); - /* create structure for describing other_props */ + // create structure for describing other_props PlyOtherProp *other = new PlyOtherProp; other->name = elem->name; #if 0 @@ -844,13 +881,13 @@ PLY::PlyOtherProp* PLY::get_other_properties(PlyElement *elem, int offset) other->size = 0; other->props = NULL; other->nprops = 0; - return (other); + return other; } #endif other->size = elem->other_size; other->props.reserve(elem->props.size()); - /* save descriptions of each "other" property */ + // save descriptions of each "other" property for (size_t i = 0; i < elem->props.size(); ++i) { if (elem->store_prop[i]) continue; @@ -859,11 +896,11 @@ PLY::PlyOtherProp* PLY::get_other_properties(PlyElement *elem, int offset) other->props.push_back(prop); } - /* set other_offset pointer appropriately if there are NO other properties */ + // set other_offset pointer appropriately if there are NO other properties if (other->props.empty()) elem->other_offset = NO_OTHER_PROPS; - /* return structure */ + // return structure return other; } @@ -881,20 +918,17 @@ offset - offset to where other_props will be stored inside user's structure returns pointer to structure containing description of other_props ******************************************************************************/ -PLY::PlyOtherProp* PLY::get_other_properties( - const char *elem_name, - int offset - ) +PLY::PlyOtherProp* PLY::get_other_properties(const char* elem_name, int offset) { - /* find information about the element */ + // find information about the element PlyElement *elem = find_element(elem_name); if (elem == NULL) { DEBUG("warning: get_other_properties: Can't find element '%s'", elem_name); - return (NULL); + return NULL; } PlyOtherProp *other = get_other_properties(elem, offset); - return (other); + return other; } @@ -920,36 +954,36 @@ returns pointer to ALL the "other" element data for this PLY file PLY::PlyOtherElems* PLY::get_other_element() { - PlyElement *elem = this->which_elem; + PlyElement *elem = which_elem; - /* create room for the new "other" element, initializing the */ - /* other data structure if necessary */ + // create room for the new "other" element, initializing the + // other data structure if necessary OtherElem other; - /* count of element instances in file */ + // count of element instances in file other.elem_count = elem->num; - /* save name of element */ + // save name of element other.elem_name = elem->name; - /* create a list to hold all the current elements */ + // create a list to hold all the current elements other.other_data = new OtherData*[other.elem_count]; - /* set up for getting elements */ + // set up for getting elements other.other_props = get_other_properties(elem->name.c_str(), offsetof(OtherData,other_props)); - /* grab all these elements */ + // grab all these elements for (int i = 0; i < other.elem_count; ++i) { - /* grab and element from the file */ + // grab and element from the file other.other_data[i] = new OtherData; get_element((uint8_t*)other.other_data[i]); } - /* return pointer to the other elements data */ + // return pointer to the other elements data if (other_elems == NULL) other_elems = new PlyOtherElems; other_elems->other_list.push_back(other); - return (other_elems); + return other_elems; } @@ -961,16 +995,16 @@ Write out the "other" elements specified for this PLY file. void PLY::put_other_elements() { - /* make sure we have other elements to write */ - if (this->other_elems == NULL) + // make sure we have other elements to write + if (other_elems == NULL) return; - /* write out the data for each "other" element */ - for (size_t i = 0; i < this->other_elems->other_list.size(); ++i) { - OtherElem *other = &(this->other_elems->other_list[i]); + // write out the data for each "other" element + for (size_t i = 0; i < other_elems->other_list.size(); ++i) { + OtherElem *other = &(other_elems->other_list[i]); put_element_setup(other->elem_name.c_str()); - /* write out each instance of the current element */ + // write out each instance of the current element for (int j = 0; j < other->elem_count; ++j) put_element(other->other_data[j]); } @@ -983,22 +1017,6 @@ void PLY::put_other_elements() /*******************/ -/****************************************************************************** -Flush a PLY file. -******************************************************************************/ - -void PLY::flush() -{ - if (ostream != NULL) { - if (mfp != NULL) { - ostream->write(mfp->getBuffer(), mfp->getSize()); - mfp->setSize(0); - } - ostream->flush(); - } -} - - /****************************************************************************** Use old PLY type names during writing for backward compatibility. ******************************************************************************/ @@ -1019,10 +1037,10 @@ version - version of the file file_type - PLY_ASCII, PLY_BINARY_BE, or PLY_BINARY_LE ******************************************************************************/ -void PLY::get_info(float *version, int *file_type) +void PLY::get_info(float* _version, int* _file_type) { - *version = this->version; - *file_type = this->file_type; + *_version = version; + *_file_type = file_type; } @@ -1036,7 +1054,7 @@ element - name of element we're looking for returns the element, or NULL if not found ******************************************************************************/ -PLY::PlyElement* PLY::find_element(const char *element) const +PLY::PlyElement* PLY::find_element(const char* element) const { for (size_t i=0; iname.c_str())) @@ -1056,7 +1074,7 @@ prop_name - name of property to find returns the index to position in list ******************************************************************************/ -int PLY::find_property(PlyElement *elem, const char *prop_name) const +int PLY::find_property(PlyElement* elem, const char* prop_name) const { for (size_t i=0; iprops.size(); ++i) if (equal_strings(prop_name, elem->props[i]->name.c_str())) @@ -1081,19 +1099,19 @@ void PLY::ascii_get_element(uint8_t* elem_ptr) char *other_data(NULL); int other_flag(0); - /* the kind of element we're reading currently */ - PlyElement *elem = this->which_elem; + // the kind of element we're reading currently + PlyElement *elem = which_elem; - /* do we need to setup for other_props? */ + // do we need to setup for other_props? if (elem->other_offset != NO_OTHER_PROPS) { other_flag = 1; - /* make room for other_props */ + // make room for other_props other_data = new char[elem->other_size]; - /* store pointer in user's structure to the other_props */ + // store pointer in user's structure to the other_props *((char**)(elem_ptr + elem->other_offset)) = other_data; } - /* read in the element */ + // read in the element int nwords; char **words; { @@ -1109,28 +1127,27 @@ void PLY::ascii_get_element(uint8_t* elem_ptr) PlyProperty *prop = elem->props[j]; const int store_it(elem->store_prop[j] | other_flag); - /* store either in the user's structure or in other_props */ + // store either in the user's structure or in other_props if (elem->store_prop[j]) elem_data = (char*)elem_ptr; else elem_data = other_data; - if (prop->is_list == LIST) { /* a list */ - /* get and store the number of items in the list */ + if (prop->is_list == LIST) { // a list + // get and store the number of items in the list get_ascii_item(words[which_word++], prop->count_external, val); if (store_it) { item = elem_data + prop->count_offset; store_item(item, prop->count_internal, val, prop->count_external); } - /* allocate space for an array of items and store a ptr to the array */ + // allocate space for an array of items and store a ptr to the array const int list_count(ValueType2Type(val, prop->count_external)); - char **store_array = (char**)(elem_data + prop->offset); + char** store_array = (char**)(elem_data + prop->offset); if (list_count == 0) { if (store_it) *store_array = NULL; - } - else { + } else { const int item_size(ply_type_size[prop->internal_type]); if (store_it) { @@ -1139,7 +1156,7 @@ void PLY::ascii_get_element(uint8_t* elem_ptr) *store_array = item_ptr; } - /* read items and store them into the array */ + // read items and store them into the array for (int k = 0; k < list_count; k++) { get_ascii_item(words[which_word++], prop->external_type, val); if (store_it) { @@ -1148,18 +1165,14 @@ void PLY::ascii_get_element(uint8_t* elem_ptr) } } } - - } - else if (prop->is_list == STRING) { /* a string */ + } else if (prop->is_list == STRING) { // a string if (store_it) { item = elem_data + prop->offset; - *((char **)item) = strdup(words[which_word++]); - } - else { + *((char**)item) = strdup(words[which_word++]); + } else { which_word++; } - } - else { /* a scalar */ + } else { // a scalar get_ascii_item(words[which_word++], prop->external_type, val); if (store_it) { item = elem_data + prop->offset; @@ -1188,76 +1201,71 @@ void PLY::binary_get_element(uint8_t* elem_ptr) char *other_data(NULL); int other_flag(0); - /* the kind of element we're reading currently */ - PlyElement *elem = this->which_elem; + // the kind of element we're reading currently + PlyElement* elem = which_elem; - /* do we need to setup for other_props? */ + // do we need to setup for other_props? if (elem->other_offset != NO_OTHER_PROPS) { other_flag = 1; - /* make room for other_props */ + // make room for other_props other_data = new char[elem->other_size]; - /* store pointer in user's structure to the other_props */ - *((char **)(elem_ptr + elem->other_offset)) = other_data; + // store pointer in user's structure to the other_props + *((char**)(elem_ptr + elem->other_offset)) = other_data; } - /* read in a number of elements */ + // read in a number of elements for (size_t j = 0; j < elem->props.size(); ++j) { PlyProperty *prop = elem->props[j]; const int store_it(elem->store_prop[j] | other_flag); - /* store either in the user's structure or in other_props */ + // store either in the user's structure or in other_props if (elem->store_prop[j]) elem_data = (char*)elem_ptr; else elem_data = other_data; - if (prop->is_list == LIST) { /* list */ - - /* get and store the number of items in the list */ - get_binary_item(istream, prop->count_external, val); + if (prop->is_list == LIST) { // list + // get and store the number of items in the list + get_binary_item(prop->count_external, val); if (store_it) { item = elem_data + prop->count_offset; store_item(item, prop->count_internal, val, prop->count_external); } - /* allocate space for an array of items and store a ptr to the array */ + // allocate space for an array of items and store a ptr to the array const int list_count(ValueType2Type(val, prop->count_external)); const int item_size(ply_type_size[prop->internal_type]); - char **store_array = (char**)(elem_data + prop->offset); + char** store_array = (char**)(elem_data + prop->offset); if (list_count == 0) { if (store_it) *store_array = NULL; - } - else { + } else { if (store_it) { item_ptr = new char[item_size * list_count]; item = item_ptr; *store_array = item_ptr; } - /* read items and store them into the array */ + // read items and store them into the array for (int k = 0; k < list_count; k++) { - get_binary_item(istream, prop->external_type, val); + get_binary_item(prop->external_type, val); if (store_it) { store_item(item, prop->internal_type, val, prop->external_type); item += item_size; } } } - - } - else if (prop->is_list == STRING) { /* string */ + } else if (prop->is_list == STRING) { // string int len; istream->read(&len, sizeof(int)); char *str = new char[len]; istream->read(str, len); if (store_it) { item = elem_data + prop->offset; - *((char **)item) = str; + *((char**)item) = str; } - } - else { /* scalar */ - get_binary_item(istream, prop->external_type, val); + } else { // scalar + get_binary_item(prop->external_type, val); if (store_it) { item = elem_data + prop->offset; store_item(item, prop->internal_type, val, prop->external_type); @@ -1271,18 +1279,17 @@ void PLY::binary_get_element(uint8_t* elem_ptr) Write to a file the word that represents a PLY data type. Entry: -fp - file pointer code - code for type ******************************************************************************/ -void PLY::write_scalar_type(OSTREAM* fp, int code) +void PLY::write_scalar_type(int code) { - /* make sure this is a valid code */ + // make sure this is a valid code if (code <= StartType || code >= EndType) abort_ply("error: write_scalar_type: bad data code = %d", code); - /* write the code to a file */ - fp->print("%s", write_type_names[code]); + // write the code to a file + ostream->print("%s", write_type_names[code]); } @@ -1293,7 +1300,7 @@ IMPORTANT: The calling routine should call "free" on the returned pointer once finished with it. Entry: -sfp - file to read from +sfp - string file to read from Exit: nwords - number of words returned @@ -1301,7 +1308,7 @@ orig_line - the original line of characters returns a list of words from the line, or NULL if end-of-file ******************************************************************************/ -char** PLY::get_words(STRISTREAM& sfp, int *nwords, char **orig_line) +char** PLY::get_words(STRISTREAM& sfp, int* nwords, char** orig_line) { const int BIG_STRING = 4096; char str[BIG_STRING]; @@ -1311,20 +1318,20 @@ char** PLY::get_words(STRISTREAM& sfp, int *nwords, char **orig_line) int num_words = 0; char *ptr, *ptr2; - char **words = (char **)malloc(sizeof (char *) * max_words); + char** words = (char**)malloc(sizeof(char*) * max_words); - /* read in a line */ + // read in a line size_t len(sfp.readLine(str, BIG_STRING-2)); if (len == 0 || len == STREAM_ERROR) { *nwords = 0; *orig_line = NULL; free(words); - return (NULL); + return NULL; } - /* convert line-feed and tabs into spaces */ - /* (this guarantees that there will be a space before the */ - /* null character at the end of the string) */ + // convert line-feed and tabs into spaces + // (this guarantees that there will be a space before the + // null character at the end of the string) if (str[len-1] == '\r') --len; str[len] = '\n'; @@ -1345,59 +1352,56 @@ char** PLY::get_words(STRISTREAM& sfp, int *nwords, char **orig_line) } EXIT_LOOP: - /* find the words in the line */ + // find the words in the line ptr = str; while (*ptr != '\0') { - /* jump over leading spaces */ + // jump over leading spaces while (*ptr == ' ') ptr++; - /* break if we reach the end */ + // break if we reach the end if (*ptr == '\0') break; - /* allocate more room for words if necessary */ + // allocate more room for words if necessary if (num_words >= max_words) { max_words += 10; - words = (char **) realloc (words, sizeof (char *) * max_words); + words = (char**)realloc(words, sizeof(char*) * max_words); } - if (*ptr == '\"') { /* a quote indicates that we have a string */ - - /* skip over leading quote */ + if (*ptr == '\"') { // a quote indicates that we have a string + // skip over leading quote ptr++; - /* save pointer to beginning of word */ + // save pointer to beginning of word words[num_words++] = ptr; - /* find trailing quote or end of line */ + // find trailing quote or end of line while (*ptr != '\"' && *ptr != '\0') ptr++; - /* replace quote with a null character to mark the end of the word */ - /* if we are not already at the end of the line */ + // replace quote with a null character to mark the end of the word + // if we are not already at the end of the line if (*ptr != '\0') *ptr++ = '\0'; - } - else { /* non-string */ - - /* save pointer to beginning of word */ + } else { // non-string + // save pointer to beginning of word words[num_words++] = ptr; - /* jump over non-spaces */ + // jump over non-spaces while (*ptr != ' ') ptr++; - /* place a null character here to mark the end of the word */ + // place a null character here to mark the end of the word *ptr++ = '\0'; } } - /* return the list of words */ + // return the list of words *nwords = num_words; *orig_line = str_copy; - return (words); + return words; } @@ -1419,35 +1423,35 @@ void PLY::write_binary_item( switch (to_type) { case Int8: { const int8_t v(ValueType2Type(val, from_type)); - f->write(&v, 1); + ostream->write(&v, 1); break; } case Int16: { const int16_t v(ValueType2Type(val, from_type)); - f->write(&v, 2); + ostream->write(&v, 2); break; } case Int32: { const int32_t v(ValueType2Type(val, from_type)); - f->write(&v, 4); + ostream->write(&v, 4); break; } case Uint8: { const uint8_t v(ValueType2Type(val, from_type)); - f->write(&v, 1); + ostream->write(&v, 1); break; } case Uint16: { const uint16_t v(ValueType2Type(val, from_type)); - f->write(&v, 2); + ostream->write(&v, 2); break; } case Uint32: { const uint32_t v(ValueType2Type(val, from_type)); - f->write(&v, 4); + ostream->write(&v, 4); break; } case Float32: { const float v(ValueType2Type(val, from_type)); - f->write(&v, 4); + ostream->write(&v, 4); break; } case Float64: { const double v(ValueType2Type(val, from_type)); - f->write(&v, 8); + ostream->write(&v, 8); break; } default: abort_ply("error: write_binary_item: bad type = %d", to_type); @@ -1474,16 +1478,16 @@ void PLY::write_ascii_item( case Int8: case Int16: case Int32: - f->print("%d ", ValueType2Type(val, from_type)); + ostream->print("%d ", ValueType2Type(val, from_type)); break; case Uint8: case Uint16: case Uint32: - f->print("%u ", ValueType2Type(val, from_type)); + ostream->print("%u ", ValueType2Type(val, from_type)); break; case Float32: case Float64: - f->print("%g ", ValueType2Type(val, from_type)); + ostream->print("%g ", ValueType2Type(val, from_type)); break; default: abort_ply("error: write_ascii_item: bad type = %d", to_type); @@ -1504,35 +1508,35 @@ val - extracted value ******************************************************************************/ void PLY::get_stored_item( - void *ptr, + const void* ptr, int type, ValueType& val ) { switch (type) { case Int8: - val.i8 = *((int8_t*)ptr); + val.i8 = *((const int8_t*)ptr); break; case Uint8: - val.u8 = *((uint8_t*)ptr); + val.u8 = *((const uint8_t*)ptr); break; case Int16: - val.i16 = *((int16_t*)ptr); + val.i16 = *((const int16_t*)ptr); break; case Uint16: - val.u16 = *((uint16_t*)ptr); + val.u16 = *((const uint16_t*)ptr); break; case Int32: - val.i32 = *((int32_t*)ptr); + val.i32 = *((const int32_t*)ptr); break; case Uint32: - val.u32 = *((uint32_t*)ptr); + val.u32 = *((const uint32_t*)ptr); break; case Float32: - val.f = *((float*)ptr); + val.f = *((const float*)ptr); break; case Float64: - val.d = *((double*)ptr); + val.d = *((const double*)ptr); break; default: abort_ply("error: get_stored_item: bad type = %d", type); @@ -1545,43 +1549,38 @@ Get the value of an item from a binary file, and place the result into an integer, an unsigned integer and a double. Entry: -fp - file to get item from type - data type supposedly in the word Exit: val - store value ******************************************************************************/ -void PLY::get_binary_item( - ISTREAM* fp, - int type, - ValueType& val - ) +void PLY::get_binary_item(int type, ValueType& val) { switch (type) { case Int8: - fp->read(&val.i8, 1); + istream->read(&val.i8, 1); break; case Uint8: - fp->read(&val.u8, 1); + istream->read(&val.u8, 1); break; case Int16: - fp->read(&val.i16, 2); + istream->read(&val.i16, 2); break; case Uint16: - fp->read(&val.u16, 2); + istream->read(&val.u16, 2); break; case Int32: - fp->read(&val.i32, 4); + istream->read(&val.i32, 4); break; case Uint32: - fp->read(&val.u32, 4); + istream->read(&val.u32, 4); break; case Float32: - fp->read(&val.f, 4); + istream->read(&val.f, 4); break; case Float64: - fp->read(&val.d, 8); + istream->read(&val.d, 8); break; default: abort_ply("error: get_binary_item: bad type = %d", type); @@ -1601,11 +1600,7 @@ type - data type supposedly in the word val - store value ******************************************************************************/ -void PLY::get_ascii_item( - const char* word, - int type, - ValueType& val - ) +void PLY::get_ascii_item(const char* word, int type, ValueType& val) { switch (type) { case Int8: @@ -1624,7 +1619,7 @@ void PLY::get_ascii_item( val.i32 = atoi(word); break; case Uint32: - val.u32 = strtoul(word, (char **)NULL, 10); + val.u32 = strtoul(word, (char**)NULL, 10); break; case Float32: val.f = (float)atof(word); @@ -1696,15 +1691,15 @@ words - list of words describing the element nwords - number of words in the list ******************************************************************************/ -void PLY::add_element(const char **words, int /*nwords*/) +void PLY::add_element(const char** words, int /*nwords*/) { - /* create the new element */ + // create the new element PlyElement *elem = new PlyElement; elem->name = words[1]; elem->num = atoi(words[2]); - /* add the new element to the object's list */ - this->elems.push_back(elem); + // add the new element to the object's list + elems.push_back(elem); } @@ -1718,20 +1713,20 @@ name - name of property type returns integer code for property, or 0 if not found ******************************************************************************/ -int PLY::get_prop_type(const char *type_name) +int PLY::get_prop_type(const char* type_name) { - /* try to match the type name */ + // try to match the type name for (int i = StartType + 1; i < EndType; ++i) if (equal_strings (type_name, type_names[i])) - return (i); + return i; - /* see if we can match an old type name */ + // see if we can match an old type name for (int i = StartType + 1; i < EndType; ++i) if (equal_strings (type_name, old_type_names[i])) - return (i); + return i; - /* if we get here, we didn't find the type */ - return (0); + // if we get here, we didn't find the type + return 0; } @@ -1743,35 +1738,33 @@ words - list of words describing the property nwords - number of words in the list ******************************************************************************/ -void PLY::add_property(const char **words, int /*nwords*/) +void PLY::add_property(const char** words, int /*nwords*/) { - /* create the new property */ + // create the new property PlyProperty *prop = new PlyProperty; - if (equal_strings(words[1], "list")) { /* list */ + if (equal_strings(words[1], "list")) { // list prop->count_external = get_prop_type (words[2]); prop->external_type = get_prop_type (words[3]); prop->name = words[4]; prop->is_list = LIST; - } - else if (equal_strings(words[1], "string")) { /* string */ + } else if (equal_strings(words[1], "string")) { // string prop->count_external = Int8; prop->external_type = Int8; prop->name = words[2]; prop->is_list = STRING; - } - else { /* scalar */ + } else { // scalar prop->external_type = get_prop_type (words[1]); prop->name = words[2]; prop->is_list = SCALAR; } - /* internal types are the same as external by default */ + // internal types are the same as external by default prop->internal_type = prop->external_type; prop->count_internal = prop->count_external; - /* add this property to the list of properties of the current element */ - PlyElement *elem = this->elems.back(); + // add this property to the list of properties of the current element + PlyElement *elem = elems.back(); elem->props.push_back(prop); } @@ -1783,9 +1776,9 @@ Add a comment to a PLY file descriptor. line - line containing comment ******************************************************************************/ -void PLY::add_comment(const char *line) +void PLY::add_comment(const char* line) { - /* skip over "comment" and leading spaces and tabs */ + // skip over "comment" and leading spaces and tabs int i = 7; while (line[i] == ' ' || line[i] == '\t') i++; @@ -1800,9 +1793,9 @@ Add a some object information to a PLY file descriptor. line - line containing text info ******************************************************************************/ -void PLY::add_obj_info(const char *line) +void PLY::add_obj_info(const char* line) { - /* skip over "obj_info" and leading spaces and tabs */ + // skip over "obj_info" and leading spaces and tabs int i = 8; while (line[i] == ' ' || line[i] == '\t') i++; @@ -1841,11 +1834,11 @@ returns the number of elements int PLY::get_element_list(std::vector& elem_names) const { - /* create the list of element names */ + // create the list of element names elem_names.resize(elems.size()); for (size_t i = 0; i < elems.size(); ++i) elem_names[i] = elems[i]->name; - /* return the number of elements and the list of element names */ + // return the number of elements and the list of element names return (int)elems.size(); } @@ -1857,9 +1850,9 @@ Append a comment to a PLY file. comment - the comment to append ******************************************************************************/ -void PLY::append_comment(const char *comment) +void PLY::append_comment(const char* comment) { - /* add comment to list */ + // add comment to list comments.push_back(comment); } @@ -1886,10 +1879,10 @@ Append object information (arbitrary text) to a PLY file. obj_info - the object info to append ******************************************************************************/ -void PLY::append_obj_info(const char *obj_info) +void PLY::append_obj_info(const char* _obj_info) { - /* add info to list */ - this->obj_info.push_back(obj_info); + // add info to list + obj_info.push_back(_obj_info); } @@ -1922,16 +1915,16 @@ returns pointer to the name of this next element LPCSTR PLY::setup_element_read(int index, int* elem_count) { if ((size_t)index > elems.size()) { - DEBUG("warning: No element with index %d", index); - return (0); + DEBUG("warning: No element with index %d", index); + return 0; } PlyElement* elem = elems[index]; - /* set this to be the current element */ + // set this to be the current element which_elem = elem; - /* return the number of such elements in the file and the element's name */ + // return the number of such elements in the file and the element's name *elem_count = elem->num; return elem->name.c_str(); } @@ -1948,12 +1941,12 @@ prop - property to add to those that will be returned void PLY::setup_property(const PlyProperty& prop) { - PlyElement *elem = this->which_elem; + PlyElement *elem = which_elem; - /* deposit the property information into the element's description */ + // deposit the property information into the element's description int index = find_property(elem, prop.name.c_str()); if (index == -1) { - DEBUG("warning: Can't find property '%s' in element '%s'", prop.name.c_str(), elem->name.c_str()); + DEBUG("warning: Can't find property '%s' in element '%s'", prop.name.c_str(), elem->name.c_str()); return; } PlyProperty *prop_ptr = elem->props[index]; @@ -1962,7 +1955,7 @@ void PLY::setup_property(const PlyProperty& prop) prop_ptr->count_internal = prop.count_internal; prop_ptr->count_offset = prop.count_offset; - /* specify that the user wants this property */ + // specify that the user wants this property elem->store_prop[index] = STORE_PROP; } @@ -1980,7 +1973,7 @@ returns pointer to structure containing description of other_props PLY::PlyOtherProp* PLY::get_other_properties(int offset) { - return get_other_properties(this->which_elem, offset); + return get_other_properties(which_elem, offset); } @@ -1993,20 +1986,17 @@ elem_name - name of element that information is being described nelems - number of elements of this type to be written ******************************************************************************/ -void PLY::describe_element( - char *elem_name, - int nelems - ) +void PLY::describe_element(const char* elem_name, int nelems) { - /* look for appropriate element */ + // look for appropriate element PlyElement *elem = find_element(elem_name); if (elem == NULL) abort_ply("error: describe_element: can't find element '%s'",elem_name); elem->num = nelems; - /* now this element is the current element */ - this->which_elem = elem; + // now this element is the current element + which_elem = elem; } @@ -2019,9 +2009,9 @@ prop - the new property void PLY::describe_property(const PlyProperty& prop) { - PlyElement *elem = this->which_elem; + PlyElement *elem = which_elem; - /* copy the new property */ + // copy the new property PlyProperty *elem_prop = new PlyProperty; copy_property(*elem_prop, prop); elem->props.push_back(elem_prop); @@ -2039,14 +2029,14 @@ void PLY::describe_other_properties( int offset ) { - /* look for appropriate element */ + // look for appropriate element PlyElement *elem = find_element(other->name.c_str()); if (elem == NULL) { DEBUG("warning: describe_other_properties: can't find element '%s'", other->name.c_str()); return; } - /* copy the other properties */ + // copy the other properties for (size_t i = 0; i < other->props.size(); ++i) { PlyProperty *prop = new PlyProperty; copy_property(*prop, *other->props[i]); @@ -2054,7 +2044,7 @@ void PLY::describe_other_properties( elem->store_prop.push_back(OTHER_PROP); } - /* save other info about other properties */ + // save other info about other properties elem->other_size = other->size; elem->other_offset = offset; } @@ -2068,16 +2058,16 @@ PLY file. These other elements were presumably read from another PLY file. other_elems - info about other elements that we want to store ******************************************************************************/ -void PLY::describe_other_elements(PlyOtherElems *other_elems) +void PLY::describe_other_elements(PlyOtherElems* _other_elems) { - /* ignore this call if there is no other element */ - if (other_elems == NULL) + // ignore this call if there is no other element + if (_other_elems == NULL) return; - /* save pointer to this information */ - this->other_elems = other_elems; + // save pointer to this information + other_elems = _other_elems; - /* describe the other properties of this element */ + // describe the other properties of this element for (size_t i = 0; i < other_elems->other_list.size(); ++i) { OtherElem *other = &(other_elems->other_list[i]); element_count(other->elem_name.c_str(), other->elem_count); @@ -2098,7 +2088,7 @@ elem_name - name of the element that we're making the rules for returns pointer to the default rules ******************************************************************************/ -PLY::PlyPropRules* PLY::init_rule(const char *elem_name) +PLY::PlyPropRules* PLY::init_rule(const char* elem_name) { PlyElement *elem = find_element(elem_name); if (elem == NULL) @@ -2109,17 +2099,17 @@ PLY::PlyPropRules* PLY::init_rule(const char *elem_name) rules->max_props = 0; rules->rule_list = NULL; - /* see if there are other rules we should use */ + // see if there are other rules we should use if (elem->props.empty()) - return (rules); + return rules; - /* default is to use averaging rule */ + // default is to use averaging rule rules->rule_list = new int[elem->props.size()]; for (size_t i = 0; i < elem->props.size(); ++i) rules->rule_list[i] = AVERAGE_RULE; - /* try to match the element, property and rule name */ - for (PlyRuleList *list = this->rule_list; list != NULL; list = list->next) { + // try to match the element, property and rule name + for (PlyRuleList *list = rule_list; list != NULL; list = list->next) { if (!equal_strings(list->element, elem->name.c_str())) continue; @@ -2130,7 +2120,7 @@ PLY::PlyPropRules* PLY::init_rule(const char *elem_name) found_prop = 1; - /* look for matching rule name */ + // look for matching rule name for (int j = 0; rule_name_list[j].code != -1; ++j) if (equal_strings(list->name, rule_name_list[j].name.c_str())) { rules->rule_list[i] = rule_name_list[j].code; @@ -2144,7 +2134,7 @@ PLY::PlyPropRules* PLY::init_rule(const char *elem_name) } } - return (rules); + return rules; } @@ -2157,18 +2147,18 @@ prop_name - name of the property whose rule we're modifying rule_type - type of rule (MAXIMUM_RULE, MINIMUM_RULE, MAJORITY_RULE, etc.) ******************************************************************************/ -void PLY::modify_rule(PlyPropRules *rules, const char *prop_name, int rule_type) +void PLY::modify_rule(PlyPropRules* rules, const char* prop_name, int rule_type) { PlyElement *elem = rules->elem; - /* find the property and modify its rule type */ + // find the property and modify its rule type for (size_t i = 0; i < elem->props.size(); ++i) if (equal_strings(elem->props[i]->name.c_str(), prop_name)) { rules->rule_list[i] = rule_type; return; } - /* we didn't find the property if we get here */ + // we didn't find the property if we get here abort_ply("error: modify_rule: Can't find property '%s'", prop_name); } @@ -2180,10 +2170,10 @@ Begin to create a set of properties from a set of propagation rules. rules - rules for the element ******************************************************************************/ -void PLY::start_props(PlyPropRules *rules) +void PLY::start_props(PlyPropRules* rules) { - /* save pointer to the rules in the PLY object */ - this->current_rules = rules; + // save pointer to the rules in the PLY object + current_rules = rules; } @@ -2196,11 +2186,11 @@ weight - weights for this set of properties other_props - the properties to use ******************************************************************************/ -void PLY::weight_props(float weight, void *other_props) +void PLY::weight_props(float weight, void* other_props) { - PlyPropRules *rules = this->current_rules; + PlyPropRules *rules = current_rules; - /* allocate space for properties and weights, if necessary */ + // allocate space for properties and weights, if necessary if (rules->max_props == 0) { rules->max_props = 6; } @@ -2210,7 +2200,7 @@ void PLY::weight_props(float weight, void *other_props) rules->props.reserve(rules->max_props); rules->weights.reserve(rules->max_props); - /* remember these new properties and their weights */ + // remember these new properties and their weights rules->props.push_back(other_props); rules->weights.push_back(weight); } @@ -2227,31 +2217,27 @@ returns a pointer to the new properties void* PLY::get_new_props() { - PlyPropRules *rules = this->current_rules; + PlyPropRules *rules = current_rules; PlyElement *elem = rules->elem; PlyProperty *prop; int offset; int type; ValueType val; - int random_pick; - /* return NULL if we've got no "other" properties */ + // return NULL if we've got no "other" properties if (elem->other_size == 0) - return (NULL); + return NULL; - /* create room for combined other properties */ + // create room for combined other properties char *new_data = new char[elem->other_size]; - /* make sure there is enough room to store values we're to combine */ + // make sure there is enough room to store values we're to combine vals.resize(rules->props.size()); - /* in case we need a random choice */ - random_pick = FLOOR2INT(SEACAVE::random() * rules->props.size()); - - /* calculate the combination for each "other" property of the element */ + // calculate the combination for each "other" property of the element for (size_t i = 0; i < elem->props.size(); ++i) { - /* don't bother with properties we've been asked to store explicitly */ + // don't bother with properties we've been asked to store explicitly if (elem->store_prop[i]) continue; @@ -2259,15 +2245,15 @@ void* PLY::get_new_props() offset = prop->offset; type = prop->external_type; - /* collect together all the values we're to combine */ + // collect together all the values we're to combine for (size_t j = 0; j < rules->props.size(); ++j) { - char* data = (char *)rules->props[j]; + char* data = (char*)rules->props[j]; void* ptr = (void *)(data + offset); get_stored_item((void*)ptr, type, val); vals[j] = ValueType2Type(val, type); } - /* calculate the combined value */ + // calculate the combined value switch (rules->rule_list[i]) { case AVERAGE_RULE: { double sum = 0; @@ -2294,7 +2280,7 @@ void* PLY::get_new_props() break; } case RANDOM_RULE: { - val.d = vals[random_pick]; + val.d = vals[FLOOR2INT(SEACAVE::random() * rules->props.size())]; break; } case SAME_RULE: { @@ -2308,11 +2294,11 @@ void* PLY::get_new_props() abort_ply("error: get_new_props: Bad rule = %d", rules->rule_list[i]); } - /* store the combined value */ - store_item((void*)(new_data + offset), type, val, Float64); + // store the combined value + store_item(new_data + offset, type, val, Float64); } - return ((void*)new_data); + return new_data; } @@ -2320,9 +2306,9 @@ void* PLY::get_new_props() Set the list of user-specified property combination rules. ******************************************************************************/ -void PLY::set_prop_rules(PlyRuleList *prop_rules) +void PLY::set_prop_rules(PlyRuleList* prop_rules) { - this->rule_list = prop_rules; + rule_list = prop_rules; } @@ -2339,26 +2325,25 @@ returns pointer to the new rule list ******************************************************************************/ PLY::PlyRuleList* PLY::append_prop_rule( - PlyRuleList *rule_list, - const char *name, - const char *property + PlyRuleList* rule_list, + const char* name, + const char* property ) { char *str2; char *ptr; - /* find . */ + // find . char *str = strdup(property); for (ptr = str; *ptr != '\0' && *ptr != '.'; ptr++) ; - /* split string at . */ + // split string at . if (*ptr == '.') { *ptr = '\0'; str2 = ptr + 1; - } - else { + } else { DEBUG("warning: Can't find property '%s' for rule '%s'", property, name); - return (rule_list); + return rule_list; } PlyRuleList *rule = new PlyRuleList; @@ -2367,18 +2352,18 @@ PLY::PlyRuleList* PLY::append_prop_rule( rule->property = str2; rule->next = NULL; - /* either start rule list or append to it */ + // either start rule list or append to it if (rule_list == NULL) rule_list = rule; - else { /* append new rule to current list */ + else { // append new rule to current list PlyRuleList *rule_ptr = rule_list; while (rule_ptr->next != NULL) rule_ptr = rule_ptr->next; rule_ptr->next = rule; } - /* return pointer to list */ - return (rule_list); + // return pointer to list + return rule_list; } @@ -2392,7 +2377,7 @@ name - name of rule we're trying to match returns 1 if we find a match, 0 if not ******************************************************************************/ -int PLY::matches_rule_name(const char *name) +int PLY::matches_rule_name(const char* name) { for (int i = 0; rule_name_list[i].code != -1; ++i) if (equal_strings(rule_name_list[i].name.c_str(), name)) diff --git a/libs/IO/PLY.h b/libs/IO/PLY.h index d6354edef..44a0f259e 100644 --- a/libs/IO/PLY.h +++ b/libs/IO/PLY.h @@ -1,22 +1,26 @@ -/* - -PLY polygon files parser. -(originally by Greg Turk, heavily modified by cDc@seacave) - -A PLY file contains a single polygonal _object_. - -An object is composed of lists of _elements_. Typical elements are -vertices, faces, edges and materials. - -Each type of element for a given object has one or more _properties_ -associated with the element type. For instance, a vertex element may -have as properties three floating-point values x,y,z and three unsigned -chars for red, green and blue. - -*/ - -#ifndef __PLY_H__ -#define __PLY_H__ +//////////////////////////////////////////////////////////////////// +// PLY.h +// +// Copyright 2023 cDc@seacave +// Distributed under the Boost Software License, Version 1.0 +// (See http://www.boost.org/LICENSE_1_0.txt) +// +// PLY polygon files parser. +// (originally by Greg Turk, heavily modified by cDc@seacave) +// +// A PLY file contains a single polygonal _object_. +// +// An object is composed of lists of _elements_. Typical elements are +// vertices, faces, edges and materials. +// +// Each type of element for a given object has one or more _properties_ +// associated with the element type. For instance, a vertex element may +// have as properties three floating-point values x,y,z and three unsigned +// chars for red, green and blue. + + +#ifndef __SEACAVE_PLY_H__ +#define __SEACAVE_PLY_H__ // I N C L U D E S ///////////////////////////////////////////////// @@ -24,8 +28,8 @@ chars for red, green and blue. // D E F I N E S /////////////////////////////////////////////////// -#define PLY_OKAY 0 /* ply routine worked okay */ -#define PLY_ERROR -1 /* error in ply routine */ +#define PLY_OKAY 0 // ply routine worked okay +#define PLY_ERROR -1 // error in ply routine // S T R U C T S /////////////////////////////////////////////////// @@ -35,9 +39,9 @@ class IO_API PLY public: // scalar data types supported by PLY format enum FileType { - ASCII = 1, // ascii PLY file - BINARY_BE = 2, // binary PLY file, big endian - BINARY_LE = 3, // binary PLY file, little endian + ASCII = 1, // ascii PLY file + BINARY_BE = 2, // binary PLY file, big endian + BINARY_LE = 3, // binary PLY file, little endian }; enum DataType { StartType = 0, @@ -67,67 +71,67 @@ class IO_API PLY // description of a property struct PlyProperty { - std::string name; /* property name */ - int external_type; /* file's data type */ - int internal_type; /* program's data type */ - int offset; /* offset bytes of prop in a struct */ - - int is_list; /* 0 = scalar, 1 = list, 2 = char string */ - int count_external; /* file's count type */ - int count_internal; /* program's count type */ - int count_offset; /* offset byte for list count */ + std::string name; // property name + int external_type; // file's data type + int internal_type; // program's data type + int offset; // offset bytes of prop in a struct + + int is_list; // 0 = scalar, 1 = list, 2 = char string + int count_external; // file's count type + int count_internal; // program's count type + int count_offset; // offset byte for list count }; // description of an element struct PlyElement { - std::string name; /* element name */ - int num; /* number of elements in this object */ - int size; /* size of element (bytes) or -1 if variable */ - std::vector props;/* list of properties in the file */ - std::vector store_prop; /* flags: property wanted by user? */ - int other_offset; /* offset to un-asked-for props, or -1 if none*/ - int other_size; /* size of other_props structure */ + std::string name; // element name + int num; // number of elements in this object + int size; // size of element (bytes) or -1 if variable + std::vector props;// list of properties in the file + std::vector store_prop; // flags: property wanted by user? + int other_offset; // offset to un-asked-for props, or -1 if none + int other_size; // size of other_props structure }; // describes other properties in an element struct PlyOtherProp { - std::string name; /* element name */ - int size; /* size of other_props */ - std::vector props;/* list of properties in other_props */ + std::string name; // element name + int size; // size of other_props + std::vector props;// list of properties in other_props }; // for storing other_props for an other element struct OtherData { - void *other_props; + void* other_props; }; // data for one "other" element struct OtherElem { - std::string elem_name; /* names of other elements */ - int elem_count; /* count of instances of each element */ - OtherData **other_data; /* actual property data for the elements */ - PlyOtherProp *other_props; /* description of the property data */ + std::string elem_name; // names of other elements + int elem_count; // count of instances of each element + OtherData** other_data; // actual property data for the elements + PlyOtherProp* other_props; // description of the property data }; // "other" elements, not interpreted by user struct PlyOtherElems { - std::vector other_list;/* list of data for other elements */ + std::vector other_list;// list of data for other elements }; // rules for combining "other" properties struct PlyPropRules { - PlyElement *elem; /* element whose rules we are making */ - int *rule_list; /* types of rules (AVERAGE_PLY, MAJORITY_PLY, etc.) */ - uint32_t max_props; /* maximum number of properties we have room for now */ - std::vector props;/* list of properties we're combining */ - std::vector weights;/* list of weights of the properties */ + PlyElement* elem; // element whose rules we are making + int* rule_list; // types of rules (AVERAGE_PLY, MAJORITY_PLY, etc.) + uint32_t max_props; // maximum number of properties we have room for now + std::vector props; // list of properties we're combining + std::vector weights; // list of weights of the properties }; struct PlyRuleList { - LPCSTR name; /* name of the rule */ - char *element; /* name of element that rule applies to */ - char *property; /* name of property that rule applies to */ - struct PlyRuleList *next; /* pointer for linked list of rules */ + LPCSTR name; // name of the rule + char* element; // name of element that rule applies to + char* property; // name of property that rule applies to + struct PlyRuleList* next; // pointer for linked list of rules }; // property propagation rules @@ -156,107 +160,107 @@ class IO_API PLY bool read(LPCSTR); bool read(SEACAVE::ISTREAM*); - bool write(LPCSTR, int, LPCSTR*, int, size_t = 0); - bool write(SEACAVE::OSTREAM*, int, LPCSTR*, int, size_t = 0); - void flush(); + bool write(LPCSTR, int, LPCSTR*, int, size_t memBufferSize=0); + bool write(SEACAVE::OSTREAM*, int, LPCSTR*, int, size_t memBufferSize=0); void release(); void set_legacy_type_names(); - void get_info(float *, int *); + void get_info(float*, int*); - void append_comment(const char *); - void append_obj_info(const char *); + void append_comment(const char*); + void append_obj_info(const char*); void copy_comments(const PLY&); void copy_obj_info(const PLY&); std::vector& get_comments(); std::vector& get_obj_info(); - void describe_property(const char *, const PlyProperty&); - void describe_property(const char *, int nprops, const PlyProperty*); - void get_property(const char *, PlyProperty *); + void describe_property(const char*, const PlyProperty&); + void describe_property(const char*, int nprops, const PlyProperty*); + void get_property(const char*, PlyProperty*); void get_element(void*); - PlyOtherElems *get_other_element(); + PlyOtherElems* get_other_element(); int get_element_list(std::vector&) const; void setup_property(const PlyProperty&); LPCSTR setup_element_read(int, int*); - PlyOtherProp *get_other_properties(int); + PlyOtherProp* get_other_properties(int); - void element_count(const char *, int); + int get_elements_count() const { return (int)elems.size(); } int get_current_element_count() const { return which_elem->num; } - void describe_element(char *, int); + void element_count(const char*, int); + void describe_element(const char*, int); void describe_property(const PlyProperty&); - void describe_other_properties(PlyOtherProp *, int); - void describe_other_elements( PlyOtherElems *); - void get_element_setup(const char *, int, PlyProperty *); - int get_element_description(const char *, std::vector&) const; - void element_layout(const char *, int, int, PlyProperty *); + void describe_other_properties(PlyOtherProp*, int); + void describe_other_elements( PlyOtherElems*); + void get_element_setup(const char*, int, PlyProperty*); + int get_element_description(const char*, std::vector&) const; + void element_layout(const char*, int, int, PlyProperty*); bool header_complete(); - void put_element_setup(const char *); + void put_element_setup(const char*); void put_element(const void*); void put_other_elements(); - PlyPropRules *init_rule(const char *); - void modify_rule(PlyPropRules *, const char *, int); - void start_props(PlyPropRules *); - void weight_props(float, void *); - void *get_new_props(); - void set_prop_rules(PlyRuleList *); - PlyRuleList *append_prop_rule(PlyRuleList *, const char *, const char *); + PlyPropRules* init_rule(const char*); + void modify_rule(PlyPropRules*, const char*, int); + void start_props(PlyPropRules*); + void weight_props(float, void*); + void* get_new_props(); + void set_prop_rules(PlyRuleList*); + PlyRuleList* append_prop_rule(PlyRuleList*, const char*, const char*); - /* find an element in a ply's list */ - PlyElement* find_element(const char *) const; - /* find a property in an element's list */ - int find_property(PlyElement *, const char *) const; + // find an element in a ply's list + PlyElement* find_element(const char*) const; + // find a property in an element's list + int find_property(PlyElement*, const char*) const; static inline bool equal_strings(const char* s1, const char* s2) { return _tcscmp(s1, s2) == 0; } protected: - /* write to a file the word describing a PLY file data type */ - void write_scalar_type(SEACAVE::OSTREAM*, int); + // write to a file the word describing a PLY file data type + void write_scalar_type(int); - /* read a line from a file and break it up into separate words */ + // read a line from a file and break it up into separate words typedef SEACAVE::TokenInputStream STRISTREAM; - char **get_words(STRISTREAM&, int *, char **); + char** get_words(STRISTREAM&, int*, char**); - /* write an item to a file */ + // write an item to a file void write_binary_item(const ValueType&, int, int); void write_ascii_item(const ValueType&, int, int); - /* return the value of a stored item */ - void get_stored_item(void*, int, ValueType&); + // return the value of a stored item + static void get_stored_item(const void*, int, ValueType&); - /* get binary or ascii item and store it according to ptr and type */ - void get_binary_item(SEACAVE::ISTREAM*, int, ValueType&); - void get_ascii_item(const char*, int, ValueType&); + // get binary or ascii item and store it according to ptr and type + void get_binary_item(int, ValueType&); + static void get_ascii_item(const char*, int, ValueType&); - /* store a value into where a pointer and a type specify */ - void store_item(void*, int, const ValueType&, int); + // store a value into where a pointer and a type specify + static void store_item(void*, int, const ValueType&, int); - /* add information to a PLY file descriptor */ - void add_element(const char **, int); - void add_property(const char **, int); - void add_comment(const char *); - void add_obj_info(const char *); + // add information to a PLY file descriptor + void add_element(const char**, int); + void add_property(const char**, int); + void add_comment(const char*); + void add_obj_info(const char*); - /* get a bunch of elements from a file */ + // get a bunch of elements from a file void ascii_get_element(uint8_t*); void binary_get_element(uint8_t*); - void setup_other_props(PlyElement *); - PlyOtherProp *get_other_properties(PlyElement *, int); - PlyOtherProp *get_other_properties(const char *, int); + void setup_other_props(PlyElement*); + PlyOtherProp* get_other_properties(PlyElement*, int); + PlyOtherProp* get_other_properties(const char*, int); - int matches_rule_name(const char *); - int get_prop_type(const char *); + int matches_rule_name(const char*); + int get_prop_type(const char*); - /* copy a property */ + // copy a property static void copy_property(PlyProperty&, const PlyProperty&); - /* return the value as T stored in val as type */ + // return the value as T stored in val as type template static inline T ValueType2Type(const ValueType& val, int type) { switch (type) { @@ -281,32 +285,31 @@ class IO_API PLY return T(0); } -public: +protected: // description of PLY file - std::string filename; /* file name */ - SEACAVE::MemFile* mfp; /* mem file pointer */ - SEACAVE::OSTREAM* f; /* output file pointer */ + std::string filename; // file name + int file_type; // ascii or binary + float version; // version number of file + std::vector elems; // list of elements + std::vector comments;// list of comments + std::vector obj_info;// list of object info items + PlyElement* which_elem; // element we're currently reading or writing + PlyOtherElems* other_elems; // "other" elements from a PLY file + PlyPropRules* current_rules; // current propagation rules + PlyRuleList* rule_list; // rule list from user + std::vector vals; // rule list from user + union { - SEACAVE::ISTREAM* istream; /* input file pointer */ - SEACAVE::OSTREAM* ostream; /* output file pointer */ + SEACAVE::ISTREAM* istream; // input file pointer + SEACAVE::OSTREAM* ostream; // output file pointer }; - int file_type; /* ascii or binary */ - float version; /* version number of file */ - std::vector elems;/* list of elements */ - std::vector comments;/* list of comments */ - std::vector obj_info;/* list of object info items */ - PlyElement *which_elem; /* element we're currently reading or writing */ - PlyOtherElems *other_elems; /* "other" elements from a PLY file */ - PlyPropRules *current_rules; /* current propagation rules */ - PlyRuleList *rule_list; /* rule list from user */ - std::vector vals; /* rule list from user */ - const char* const* write_type_names; /* names of scalar types to be used for writing (new types by default) */ + SEACAVE::MemFile* mfp; // mem-file pointer (optional) + const char* const* write_type_names; // names of scalar types to be used for writing (new types by default) -protected: - static const char* const type_names[9]; // names of scalar types - static const char* const old_type_names[9]; // old names of types for backward compatibility + static const char* const type_names[9]; // names of scalar types + static const char* const old_type_names[9]; // old names of types for backward compatibility static const int ply_type_size[9]; static const RuleName rule_name_list[7]; }; -#endif // __PLY_H__ +#endif // __SEACAVE_PLY_H__ diff --git a/libs/MVS/Camera.h b/libs/MVS/Camera.h index 0d27daf03..8c49c35a1 100644 --- a/libs/MVS/Camera.h +++ b/libs/MVS/Camera.h @@ -131,6 +131,16 @@ class MVS_API CameraIntern return invK; } + // scale image pixel coordinates with the given scale such that it accounts for + // the convention that the center of a pixel is defined at integer coordinates + template + static inline TPoint2 ScaleImagePixel(const TPoint2& x, TYPE s) { + return TPoint2( + (x.x+TYPE(0.5))*s-TYPE(0.5), + (x.y+TYPE(0.5))*s-TYPE(0.5) + ); + } + // return scaled K (assuming standard K format) template static inline TMatrix ScaleK(const TMatrix& K, TYPE s) { @@ -201,6 +211,27 @@ class MVS_API CameraIntern width, height ); } + // return the OpenGL projection matrix corresponding to K: + // - flip: if true, flip the y axis to match OpenGL image convention + template + static inline TMatrix ProjectionMatrixOpenGL(const TMatrix& K, const cv::Size& size, TYPE nearZ, TYPE farZ, bool flip = true) { + // based on https://strawlab.org/2011/11/05/augmented-reality-with-OpenGL + const TYPE fx(K(0,0)), fy(K(1,1)); + const TYPE cx(K(0,2)+0.5f), cy(K(1,2)+0.5f); + const TYPE skew(K(0,1)); + const TYPE ihw(TYPE(2)/size.width), ihh(TYPE(2)/size.height); + const TYPE iy(flip ? TYPE(-1) : TYPE(1)); + const TYPE ilen(TYPE(1)/(farZ-nearZ)); + return TMatrix( + fx*ihw, skew*ihw, cx*ihw-TYPE(1), 0, + 0, iy*fy*ihh, iy*(cy*ihh-TYPE(1)), 0, + 0, 0, (farZ+nearZ)*ilen, -TYPE(2)*farZ*nearZ*ilen, + 0, 0, 1, 0); + } + inline Matrix4x4 GetProjectionMatrixOpenGL(const cv::Size& size, REAL nearZ, REAL farZ, bool flipY = true) const { + return ProjectionMatrixOpenGL(K, size, nearZ, farZ, flipY); + } + // normalize inhomogeneous 2D point by the given camera intrinsics K // K is assumed to be the [3,3] triangular matrix with: fx, fy, s, cx, cy and scale 1 template diff --git a/libs/MVS/Common.cpp b/libs/MVS/Common.cpp index ac3a71c19..2af2c8278 100644 --- a/libs/MVS/Common.cpp +++ b/libs/MVS/Common.cpp @@ -34,3 +34,36 @@ // Common.obj will contain the pre-compiled type information #include "Common.h" +#include "Mesh.h" + +using namespace MVS; + +void MVS::Initialize(LPCTSTR appname, unsigned nMaxThreads, int nProcessPriority) { + // initialize thread options + Process::setCurrentProcessPriority((Process::Priority)nProcessPriority); + #ifdef _USE_OPENMP + if (nMaxThreads != 0) + omp_set_num_threads(nMaxThreads); + #endif + + #ifdef _USE_BREAKPAD + // initialize crash memory dumper + MiniDumper::Create(appname, WORKING_FOLDER); + #endif + + // initialize random number generator + Util::Init(); +} + +void MVS::Finalize() { + #if TD_VERBOSE != TD_VERBOSE_OFF + // print memory statistics + Util::LogMemoryInfo(); + #endif + + #ifdef _USE_CUDA + // release static CUDA kernels before CUDA context is destroyed + Mesh::kernelComputeFaceNormal.Release(); + #endif +} +/*----------------------------------------------------------------*/ diff --git a/libs/MVS/Common.h b/libs/MVS/Common.h index c3125a765..3d4c8ee4e 100644 --- a/libs/MVS/Common.h +++ b/libs/MVS/Common.h @@ -58,8 +58,15 @@ using namespace SEACAVE; +#define _USE_OPENCV +#define _DISABLE_NO_ID +#include "Interface.h" + namespace MVS { +// Initialize / close the library; should be called at the beginning and end of the program +void Initialize(LPCTSTR appname, unsigned nMaxThreads=0, int nProcessPriority=0); +void Finalize(); /*----------------------------------------------------------------*/ } // namespace MVS diff --git a/libs/MVS/DepthMap.cpp b/libs/MVS/DepthMap.cpp index 142a5e41b..d2178b622 100644 --- a/libs/MVS/DepthMap.cpp +++ b/libs/MVS/DepthMap.cpp @@ -32,8 +32,6 @@ #include "Common.h" #include "DepthMap.h" #include "Mesh.h" -#define _USE_OPENCV -#include "Interface.h" #include "../Common/AutoEstimator.h" // CGAL: depth-map initialization #include diff --git a/libs/MVS/Image.h b/libs/MVS/Image.h index 642767618..6e8c46952 100644 --- a/libs/MVS/Image.h +++ b/libs/MVS/Image.h @@ -46,28 +46,8 @@ namespace MVS { typedef uint32_t IIndex; -typedef cList IIndexArr; - -struct MVS_API ViewInfo { - IIndex ID; // image local-ID (the index in the scene images list) - uint32_t points; // number of 3D points shared with the reference image - float scale; // image scale relative to the reference image - float angle; // image angle relative to the reference image (radians) - float area; // common image area relative to the reference image (ratio) - - #ifdef _USE_BOOST - // implement BOOST serialization - template - void serialize(Archive& ar, const unsigned int /*version*/) { - ar & ID; - ar & points; - ar & scale; - ar & angle; - ar & area; - } - #endif -}; -typedef MVS_API TIndexScore ViewScore; +typedef SEACAVE::cList IIndexArr; +typedef _INTERFACE_NAMESPACE::Interface::Image::ViewScore ViewScore; typedef MVS_API CLISTDEF0IDX(ViewScore, IIndex) ViewScoreArr; /*----------------------------------------------------------------*/ @@ -89,7 +69,7 @@ class MVS_API Image float avgDepth; // average depth of the points seen by this camera public: - inline Image() : poseID(NO_ID), width(0), height(0) {} + inline Image() : poseID(NO_ID), width(0), height(0), avgDepth(0) {} inline bool IsValid() const { return poseID != NO_ID; } inline bool HasResolution() const { return width > 0 && height > 0; } diff --git a/libs/MVS/Interface.h b/libs/MVS/Interface.h index 6cbad1eb4..920e93b21 100644 --- a/libs/MVS/Interface.h +++ b/libs/MVS/Interface.h @@ -13,7 +13,7 @@ // D E F I N E S /////////////////////////////////////////////////// #define MVSI_PROJECT_ID "MVSI" // identifies the project stream -#define MVSI_PROJECT_VER ((uint32_t)6) // identifies the version of a project stream +#define MVSI_PROJECT_VER ((uint32_t)7) // identifies the version of a project stream // set a default namespace name if none given #ifndef _INTERFACE_NAMESPACE @@ -26,6 +26,11 @@ #define _USE_CUSTOM_CV #endif +// set to disable custom NO_ID declaration +#ifndef _DISABLE_NO_ID +#define _INTERFACE_NO_ID +#endif + // S T R U C T S /////////////////////////////////////////////////// @@ -162,7 +167,9 @@ class Matx namespace _INTERFACE_NAMESPACE { // invalid index +#ifdef _INTERFACE_NO_ID constexpr uint32_t NO_ID {std::numeric_limits::max()}; +#endif // custom serialization namespace ARCHIVE { @@ -514,14 +521,40 @@ struct Interface // structure describing an image struct Image { + // structure describing how an other image relates to this image in terms of overlap, + // i.e. how many 3D points are shared between the two images, base-line and common area, + // useful for ex. when selecting the best images to densly match with + struct ViewScore { + uint32_t ID; // image local-ID, the index in this scene images list + uint32_t points; // number of 3D points shared with the reference image + float scale; // image scale relative to the reference image + float angle; // image angle relative to the reference image (radians) + float area; // common image area relative to the reference image (ratio) + float score; // aggregated image score relative to the reference image (larger is better) + + template + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & ID; + ar & points; + ar & scale; + ar & angle; + ar & area; + ar & score; + } + }; + std::string name; // image file name std::string maskName; // segmentation file name (optional) uint32_t platformID; // ID of the associated platform uint32_t cameraID; // ID of the associated camera on the associated platform uint32_t poseID; // ID of the pose of the associated platform - uint32_t ID; // ID of this image in the global space (optional) + uint32_t ID; // image global-ID, ex. the ID given outside the current scene, like the index in the full list of image files (optional) + float minDepth; // minimum depth of the points seen by this image (optional) + float avgDepth; // average depth of the points seen by this image (optional) + float maxDepth; // maximum depth of the points seen by this image (optional) + std::vector viewScores; // list of view scores for this image (optional) - Image() : platformID(NO_ID), cameraID(NO_ID), poseID(NO_ID), ID(NO_ID) {} + Image() : platformID(NO_ID), cameraID(NO_ID), poseID(NO_ID), ID(NO_ID), minDepth(0), avgDepth(0), maxDepth(0) {} bool IsValid() const { return poseID != NO_ID; } @@ -537,6 +570,12 @@ struct Interface if (version > 2) { ar & ID; } + if (version > 6) { + ar & minDepth; + ar & avgDepth; + ar & maxDepth; + ar & viewScores; + } } }; typedef std::vector ImageArr; diff --git a/libs/MVS/Mesh.cpp b/libs/MVS/Mesh.cpp index e708f6767..589ad880a 100644 --- a/libs/MVS/Mesh.cpp +++ b/libs/MVS/Mesh.cpp @@ -92,7 +92,7 @@ using namespace MVS; #define USE_MESH_BF 0 // brute-force #define USE_MESH_OCTREE 1 // octree (misses some triangles) #define USE_MESH_BVH 2 // BVH (misses some triangles) -#define USE_MESH_INT USE_MESH_BF +#define USE_MESH_INT USE_MESH_BVH #if USE_MESH_INT == USE_MESH_BVH #include @@ -117,7 +117,7 @@ void Mesh::ReleaseExtra() faceNormals.Release(); faceFaces.Release(); faceTexcoords.Release(); - textureDiffuse.release(); + texturesDiffuse.Release(); } // ReleaseExtra void Mesh::EmptyExtra() { @@ -128,7 +128,7 @@ void Mesh::EmptyExtra() faceNormals.Empty(); faceFaces.Empty(); faceTexcoords.Empty(); - textureDiffuse.release(); + texturesDiffuse.Empty(); } // EmptyExtra void Mesh::Swap(Mesh& rhs) { @@ -141,7 +141,8 @@ void Mesh::Swap(Mesh& rhs) faceNormals.Swap(rhs.faceNormals); faceFaces.Swap(rhs.faceFaces); faceTexcoords.Swap(rhs.faceTexcoords); - std::swap(textureDiffuse, rhs.textureDiffuse); + faceTexindices.Swap(rhs.faceTexindices); + std::swap(texturesDiffuse, rhs.texturesDiffuse); } // Swap // combine this mesh with the given mesh, without removing duplicate vertices void Mesh::Join(const Mesh& mesh) @@ -220,8 +221,8 @@ Mesh::Vertex Mesh::GetCenter() const // extract array of vertices incident to each vertex void Mesh::ListIncidenteVertices() { - vertexVertices.Empty(); - vertexVertices.Resize(vertices.GetSize()); + vertexVertices.clear(); + vertexVertices.resize(vertices.size()); FOREACH(i, faces) { const Face& face = faces[i]; for (int v=0; v<3; ++v) { @@ -229,22 +230,22 @@ void Mesh::ListIncidenteVertices() for (int i=1; i<3; ++i) { const VIndex idxVert(face[(v+i)%3]); if (verts.Find(idxVert) == VertexIdxArr::NO_INDEX) - verts.Insert(idxVert); + verts.emplace_back(idxVert); } } } } -// extract array of triangles incident to each vertex +// extract the (ordered) array of triangles incident to each vertex void Mesh::ListIncidenteFaces() { - vertexFaces.Empty(); + vertexFaces.clear(); vertexFaces.resize(vertices.size()); FOREACH(i, faces) { const Face& face = faces[i]; for (int v=0; v<3; ++v) { ASSERT(vertexFaces[face[v]].Find(i) == FaceIdxArr::NO_INDEX); - vertexFaces[face[v]].Insert(i); + vertexFaces[face[v]].emplace_back(i); } } } @@ -293,8 +294,8 @@ void Mesh::ListIncidenteFaceFaces() // (make sure you called ListIncidenteFaces() before) void Mesh::ListBoundaryVertices() { - vertexBoundary.Empty(); - vertexBoundary.Resize(vertices.GetSize()); + vertexBoundary.clear(); + vertexBoundary.resize(vertices.size()); vertexBoundary.Memset(0); VertCountMap mapVerts; mapVerts.reserve(12*2); FOREACH(idxV, vertices) { @@ -326,17 +327,17 @@ void Mesh::ListBoundaryVertices() // compute normal for all faces void Mesh::ComputeNormalFaces() { - faceNormals.Resize(faces.GetSize()); + faceNormals.resize(faces.size()); #ifndef _USE_CUDA FOREACH(idxFace, faces) faceNormals[idxFace] = normalized(FaceNormal(faces[idxFace])); #else if (kernelComputeFaceNormal.IsValid()) { - reportCudaError(kernelComputeFaceNormal((int)faces.GetSize(), + reportCudaError(kernelComputeFaceNormal((int)faces.size(), vertices, faces, CUDA::KernelRT::OutputParam(faceNormals.GetDataSize()), - faces.GetSize() + faces.size() )); reportCudaError(kernelComputeFaceNormal.GetResult(0, faceNormals @@ -377,8 +378,8 @@ void Mesh::ComputeNormalVertices() // G. Thurmer, C. A. Wuthrich "Computing vertex normals from polygonal facets", Journal of Graphics Tools, 1998 void Mesh::ComputeNormalVertices() { - ASSERT(!faceNormals.IsEmpty()); - vertexNormals.Resize(vertices.GetSize()); + ASSERT(!faceNormals.empty()); + vertexNormals.resize(vertices.size()); vertexNormals.Memset(0); FOREACH(idxFace, faces) { const Face& face = faces[idxFace]; @@ -393,8 +394,8 @@ void Mesh::ComputeNormalVertices() vertexNormals[face[1]] += t*ACOS(-ComputeAngleN(e0.ptr(), e1.ptr())); vertexNormals[face[2]] += t*ACOS(-ComputeAngleN(e1.ptr(), e2.ptr())); } - FOREACHPTR(pVertexNormal, vertexNormals) - normalize(*pVertexNormal); + for (Normal& vertexNormal: vertexNormals) + normalize(vertexNormal); } #endif @@ -438,10 +439,10 @@ void Mesh::GetEdgeFaces(VIndex v0, VIndex v1, FaceIdxArr& afaces) const { const FaceIdxArr& faces0 = vertexFaces[v0]; const FaceIdxArr& faces1 = vertexFaces[v1]; - std::unordered_set setFaces1(faces1.Begin(), faces1.End()); - FOREACH(i, faces0) { - if (setFaces1.find(faces0[i]) != setFaces1.end()) - afaces.Insert(faces0[i]); + std::unordered_set setFaces1(faces1.begin(), faces1.end()); + for (FIndex idxFace: faces0) { + if (setFaces1.find(idxFace) != setFaces1.end()) + afaces.Insert(idxFace); } } @@ -451,20 +452,20 @@ void Mesh::GetFaceFaces(FIndex f, FaceIdxArr& afaces) const const FaceIdxArr& faces0 = vertexFaces[face[0]]; const FaceIdxArr& faces1 = vertexFaces[face[1]]; const FaceIdxArr& faces2 = vertexFaces[face[2]]; - std::unordered_set setFaces(faces1.Begin(), faces1.End()); - FOREACHPTR(pIdxFace, faces0) { - if (f != *pIdxFace && setFaces.find(*pIdxFace) != setFaces.end()) - afaces.InsertSortUnique(*pIdxFace); + std::unordered_set setFaces(faces1.begin(), faces1.end()); + for (FIndex idxFace: faces0) { + if (f != idxFace && setFaces.find(idxFace) != setFaces.end()) + afaces.InsertSortUnique(idxFace); } - FOREACHPTR(pIdxFace, faces2) { - if (f != *pIdxFace && setFaces.find(*pIdxFace) != setFaces.end()) - afaces.InsertSortUnique(*pIdxFace); + for (FIndex idxFace: faces2) { + if (f != idxFace && setFaces.find(idxFace) != setFaces.end()) + afaces.InsertSortUnique(idxFace); } setFaces.clear(); - setFaces.insert(faces2.Begin(), faces2.End()); - FOREACHPTR(pIdxFace, faces0) { - if (f != *pIdxFace && setFaces.find(*pIdxFace) != setFaces.end()) - afaces.InsertSortUnique(*pIdxFace); + setFaces.insert(faces2.begin(), faces2.end()); + for (FIndex idxFace: faces0) { + if (f != idxFace && setFaces.find(idxFace) != setFaces.end()) + afaces.InsertSortUnique(idxFace); } } @@ -482,605 +483,191 @@ void Mesh::GetEdgeVertices(FIndex f0, FIndex f1, uint32_t* vs0, uint32_t* vs1) c } } +// get the edge orientation in the given face: +// return false for backward, true for forward +bool Mesh::GetEdgeOrientation(FIndex idxFace, VIndex iV0, VIndex iV1) const +{ + const Face& face = faces[idxFace]; + const VIndex i0 = FindVertex(face, iV0); + ASSERT(i0 != NO_ID); + ASSERT(face[(i0+1)%3] == iV1 || face[(i0+2)%3] == iV1); + return face[(i0+1)%3] == iV1; +} + +// find the adjacent face for the given face edge; +// return NO_ID if no adjacent faces exist OR +// more than one adjacent face exist OR +// the edge have opposite orientations in each face +Mesh::FIndex Mesh::GetEdgeAdjacentFace(FIndex idxFace, VIndex iV0, VIndex iV1) const +{ + // iterate over all faces containing the first vertex + ASSERT(vertexFaces.size() == vertices.size()); + const bool edgeOrientation = GetEdgeOrientation(idxFace, iV0, iV1); + FIndex idxFaceAdj = NO_ID; + for (FIndex iF: vertexFaces[iV0]) { + // if this adjacent face is not the analyzed face + if (iF != idxFace) { + // iterate over all face vertices + const Face& face = faces[iF]; + for (int i = 0; i < 3; ++i) { + // if the face vertex is the second vertex + if (face[i] == iV1) { + // check if there are more than two adjacent faces (manifold constraint) + if (idxFaceAdj != NO_ID) + return NO_ID; + // check if edge vertices ordering is opposite in the two faces (manifold constraint) + if (GetEdgeOrientation(iF, iV0, iV1) == edgeOrientation) + return NO_ID; + idxFaceAdj = iF; + } + } + } + } + return idxFaceAdj; +} + void Mesh::GetAdjVertices(VIndex v, VertexIdxArr& indices) const { - ASSERT(vertexFaces.GetSize() == vertices.GetSize()); + ASSERT(vertexFaces.size() == vertices.size()); const FaceIdxArr& idxFaces = vertexFaces[v]; std::unordered_set setIndices; - FOREACHPTR(pIdxFace, idxFaces) { - const Face& face = faces[*pIdxFace]; + for (FIndex idxFace: idxFaces) { + const Face& face = faces[idxFace]; for (int i=0; i<3; ++i) { const VIndex vAdj(face[i]); if (vAdj != v && setIndices.insert(vAdj).second) - indices.Insert(vAdj); + indices.emplace_back(vAdj); } } } void Mesh::GetAdjVertexFaces(VIndex idxVCenter, VIndex idxVAdj, FaceIdxArr& indices) const { - ASSERT(vertexFaces.GetSize() == vertices.GetSize()); + ASSERT(vertexFaces.size() == vertices.size()); const FaceIdxArr& idxFaces = vertexFaces[idxVCenter]; - FOREACHPTR(pIdxFace, idxFaces) { - const Face& face = faces[*pIdxFace]; + for (FIndex idxFace: idxFaces) { + const Face& face = faces[idxFace]; ASSERT(FindVertex(face, idxVCenter) != NO_ID); if (FindVertex(face, idxVAdj) != NO_ID) - indices.Insert(*pIdxFace); + indices.emplace_back(idxFace); } } /*----------------------------------------------------------------*/ - -#if 0 -#define DEFINE_FACE_VERTS(n) \ - const Face& f##n = faces[componentFaces[n]]; \ - const uint32_t idx##n(Mesh::FindVertex(f##n, (VIndex)v)); \ - const VIndex v##n##1(f##n[(idx##n+1)%3]); \ - const VIndex v##n##2(f##n[(idx##n+2)%3]) -#define IS_LOOP_FACE4(a, b, c, d) \ - (v##a##2 == v##b##1 && v##b##2 == v##c##1 && v##c##2 == v##d##1 && v##d##2 == v##a##1) -#define IS_LOOP_FACE3(a, b, c) \ - (v##a##2 == v##b##1 && v##b##2 == v##c##1 && v##c##2 == v##a##1) - -namespace FIX_NONMANIFOLD { -typedef Mesh::VIndex VIndex; -typedef Mesh::FIndex FIndex; -typedef uint32_t Index; -struct Node; -struct Edge { - Node* pPrev; // a node that points to this node - Node* pNext; // the next node - FIndex fIdx; // index of the face that generated this edge (the link from this node to the next) - inline Edge() : pPrev(NULL), pNext(NULL), fIdx(NO_ID) {} - inline bool IsEmpty() const { return (fIdx == NO_ID); } -}; -struct Node { - VIndex vIdx; - Edge edge; - uint32_t idComponent; - inline Node(VIndex _vIdx) : vIdx(_vIdx), idComponent(NO_ID) {} -}; -struct Graph { - typedef std::unordered_map VertexMap; - typedef SEACAVE::cList Nodes; - - VertexMap index2idx; - Nodes nodes; - UnsignedArr components; - - inline void Clear() { - nodes.Empty(); - components.Empty(); - index2idx.clear(); - } - inline Index NumNodes() const { return (Index)nodes.GetSize(); } - void AddEdge(VIndex vIdx0, VIndex vIdx1, FIndex fIdx) { - const auto vert0(index2idx.insert(std::make_pair(vIdx0, NumNodes()))); - Node& n0 = (vert0.second ? nodes.AddConstruct(vIdx0) : nodes[vert0.first->second]); - const auto vert1(index2idx.insert(std::make_pair(vIdx1, NumNodes()))); - Node& n1 = (vert1.second ? nodes.AddConstruct(vIdx1) : nodes[vert1.first->second]); - n0.edge.pNext = &n1; - n0.edge.fIdx = fIdx; - n1.edge.pPrev = &n0; - } - VIndex ComputeComponents() { - ASSERT(components.IsEmpty()); - VIndex vIdxMultiComponent(NO_ID); - unsigned nCount(0); - do { - // find first node not visited yet - Node* pNode; - FOREACHPTR(pN, nodes) { - if (pN->idComponent == NO_ID) { - pNode = pN; - break; - } - } - const uint32_t id((uint32_t)components.GetSize()); - unsigned& size = components.AddConstruct(0); - Node* const pStartNode(pNode); - do { - ++size; - pNode->idComponent = id; - if (pNode->edge.pNext == NULL) - break; - ASSERT(pNode->edge.pNext->edge.pPrev != NULL); - if (pNode->edge.pNext->edge.pPrev != pNode) - vIdxMultiComponent = pNode->edge.pNext->vIdx; - } while ((pNode=pNode->edge.pNext) != pStartNode && pNode->idComponent == NO_ID); - nCount += size; - if (pNode != NULL && pNode->idComponent < id) { - const uint32_t prev_id(pNode->idComponent); - components.RemoveLast(); - pNode = pStartNode; - do { - pNode->idComponent = prev_id; - } while ((pNode=pNode->edge.pNext)->idComponent != prev_id); - } - } while (nCount < nodes.GetSize()); - return vIdxMultiComponent; - } -}; -} // namespace FIX_NONMANIFOLD - -// find all non-manifold vertices and for each, duplicate the vertex, -// assigning the new vertex to the smallest connected set of faces -// return true if problems were found -bool Mesh::FixNonManifold() -{ - TD_TIMER_STARTD(); - using namespace FIX_NONMANIFOLD; - ASSERT(!vertices.IsEmpty() && !faces.IsEmpty()); - if (vertexFaces.GetSize() != vertices.GetSize()) - ListIncidenteFaces(); - Graph graph; - IndexArr componentFaces; - IndexArr componentVertices; - std::unordered_set removeFaces; - unsigned nNonManifoldVertices(0), nPyramid3(0), nPyramid4(0); - FOREACH(v, vertices) { - const FaceIdxArr& vFaces = vertexFaces[v]; - FOREACHPTR(pFIdx, vFaces) { - const Face& f(faces[*pFIdx]); - const uint32_t i(FindVertex(f, (VIndex)v)); - graph.AddEdge(f[(i+1)%3], f[(i+2)%3], (uint32_t)*pFIdx); - } - // find all connected sub-graphs - const VIndex vIdxMultiComponent(graph.ComputeComponents()); - if (graph.components.GetSize() <= 1) { - graph.Clear(); - continue; - } - // there are at least two connected components (usually exactly two); - // duplicate the vertex and assign the duplicate to the smallest component - ASSERT(graph.components.GetSize() > 1); - size_t nLongestCompIdx(0); - FOREACH(c, graph.components) { - if (graph.components[nLongestCompIdx] < graph.components[c]) - nLongestCompIdx = c; - } - FOREACH(c, graph.components) { - if (c == nLongestCompIdx) - continue; - ASSERT(componentVertices.IsEmpty() && componentFaces.IsEmpty()); - FOREACHPTR(pNode, graph.nodes) { - if (pNode->idComponent != c) - continue; - ASSERT(!pNode->edge.IsEmpty()); - componentVertices.Insert(pNode->vIdx); - componentFaces.Insert(pNode->edge.fIdx); - } - if (componentFaces.GetSize() == 3 && componentVertices.GetSize() == 3 && graph.components.GetSize() == 2 && vFaces.GetSize() > 6) { - // this is the case of a legitimate vertex on the surface and a pyramid rising from the neighboring surface - // having the apex this vertex and the base formed by 3 vertices; - // check that 3 faces form a loop - DEFINE_FACE_VERTS(0); - DEFINE_FACE_VERTS(1); - DEFINE_FACE_VERTS(2); - ASSERT(IS_LOOP_FACE3(0,1,2)); - // to find the right vertex order for the new face, - // set first two vertices in the order appearing in any of the three existing faces, - // and the third as the remaining one - faces.AddConstruct( - v01, - v02, - (v02 == v11 ? v12 : v22) - ); - // remove component faces and create a new face from the three component vertices - ASSERT(componentVertices.GetSize() == graph.components[c]); - FOREACHPTR(pFIdx, componentFaces) - removeFaces.insert(*pFIdx); - ++nPyramid3; - #if 1 - } else if (componentFaces.GetSize() == 4 && componentVertices.GetSize() == 4 && graph.components.GetSize() == 2 && vFaces.GetSize() > 8) { - // this is the case of a legitimate vertex on the surface and a pyramid rising from the neighboring surface - // having the apex this vertex and the base formed by 4 vertices; - // check that 3 faces form a loop - DEFINE_FACE_VERTS(0); - DEFINE_FACE_VERTS(1); - DEFINE_FACE_VERTS(2); - DEFINE_FACE_VERTS(3); - // to find the right vertex order for the new faces, - // use the fact that the faces are already in link order, - // so set first new face as the linked vertices of the first two faces - // and second new face as the linked vertices of the last two faces - ASSERT(IS_LOOP_FACE4(0,1,2,3)); - faces.AddConstruct(v01, v02, v12); - faces.AddConstruct(v21, v22, v32); - // remove component faces and create two new faces from the four component vertices - ASSERT(componentVertices.GetSize() == graph.components[c]); - FOREACHPTR(pFIdx, componentFaces) - removeFaces.insert(*pFIdx); - ++nPyramid4; - #endif - } else { - // simply duplicate the vertex and assign it to the component faces - const VIndex newIndex((VIndex)vertices.GetSize()); - Vertex& pos(vertices.AddEmpty()); - pos = vertices[v]; - FOREACHPTR(pFIdx, componentFaces) - GetVertex(faces[*pFIdx], (VIndex)v) = newIndex; - } - componentVertices.Empty(); - componentFaces.Empty(); - } - graph.Clear(); - ++nNonManifoldVertices; - } - if (!removeFaces.empty()) { - // remove old faces; - // delete them in reverse order since the remove operation is simply replacing the removed item with the last item - std::vector orderedRemoveFaces; - orderedRemoveFaces.reserve(removeFaces.size()); - for (FIndex fIdx: removeFaces) - orderedRemoveFaces.push_back(fIdx); - std::sort(orderedRemoveFaces.begin(), orderedRemoveFaces.end()); - std::vector::const_iterator it(orderedRemoveFaces.cend()); - do { - faces.RemoveAt(*(--it)); - } while (it != orderedRemoveFaces.cbegin()); - } - DEBUG("Fixed %u non-manifold vertices and %u faces removed: %u pyramid3 and %u pyramid4 (%s)", nNonManifoldVertices, removeFaces.size(), nPyramid3, nPyramid4, TD_TIMER_GET_FMT().c_str()); - return (nNonManifoldVertices > 0); -} // FixNonManifold -#undef IS_LINK_FACE3 -#undef IS_LOOP_FACE3 -#undef IS_LOOP_FACE4 -#undef DEFINE_FACE_VERTS -#else -#define DEFINE_FACE_VERTS(n) \ - const Face& f##n = faces[*itFace++]; \ - const uint32_t idx##n(Mesh::FindVertex(f##n, (VIndex)v)); \ - const VIndex v##n##1(f##n[(idx##n+1)%3]); \ - const VIndex v##n##2(f##n[(idx##n+2)%3]) -#define IS_LOOP_FACE3(a, b, c) \ - (v##a##2 == v##b##1 && v##b##2 == v##c##1 && v##c##2 == v##a##1) -#define IS_LINK_FACE3(a, b, c) \ - (v##a##2 == v##b##1 && v##b##2 == v##c##1) -#define DEFINE_FACES4(a, b, c, d, go2) \ - if (IS_LINK_FACE3(a,b,c)) { \ - if (!IS_LINK_FACE3(c,d,a)) \ - goto go2; \ - faces.AddConstruct(v##a##1, v##a##2, v##b##2); \ - faces.AddConstruct(v##c##1, v##c##2, v##d##2); \ - } -#define DEFINE_REMOVE3(go2) \ - /* check that 3 faces form a loop */ \ - itFace = componentFaces.cbegin(); \ - DEFINE_FACE_VERTS(0); \ - DEFINE_FACE_VERTS(1); \ - DEFINE_FACE_VERTS(2); \ - if (!IS_LOOP_FACE3(0,1,2) && !IS_LOOP_FACE3(0,2,1)) \ - goto go2; \ - /* to find the right vertex order for the new face, */ \ - /* set first two vertices in the order appearing in any of the three existing faces, */ \ - /* and the third as the remaining one */ \ - faces.AddConstruct( \ - v01, \ - v02, \ - (v02 == v11 ? v12 : v22) \ - ); \ - /* remove component faces and create a new face from the three component vertices */ \ - for (auto fIdx: componentFaces) \ - removeFaces.insert(fIdx); \ - ++nPyramid3 -#define DEFINE_REMOVE4(go2) \ - /* check that 3 faces form a loop */ \ - itFace = componentFaces.cbegin(); \ - DEFINE_FACE_VERTS(0); \ - DEFINE_FACE_VERTS(1); \ - DEFINE_FACE_VERTS(2); \ - DEFINE_FACE_VERTS(3); \ - /* to find the right vertex order for the new faces, */ \ - /* find the link order of the face */ \ - DEFINE_FACES4(0,1,2,3, go2) else \ - DEFINE_FACES4(0,1,3,2, go2) else \ - DEFINE_FACES4(0,2,1,3, go2) else \ - DEFINE_FACES4(0,2,3,1, go2) else \ - DEFINE_FACES4(0,3,1,2, go2) else \ - DEFINE_FACES4(0,3,2,1, go2) else \ - goto go2; \ - /* remove component faces and create two new faces from the four component vertices */ \ - for (auto fIdx: componentFaces) \ - removeFaces.insert(fIdx); \ - ++nPyramid4 -#define DEFINE_REMOVE_FACES \ - if (!removeFaces.empty()) { \ - /* remove old faces; */ \ - /* delete them in reverse order since the remove operation is simply replacing the removed item with the last item */ \ - std::vector orderedRemoveFaces; \ - orderedRemoveFaces.reserve(removeFaces.size()); \ - nRemoveFaces += (unsigned)removeFaces.size(); \ - for (FIndex fIdx: removeFaces) \ - orderedRemoveFaces.push_back(fIdx); \ - removeFaces.clear(); \ - std::sort(orderedRemoveFaces.begin(), orderedRemoveFaces.end()); \ - std::vector::const_iterator it(orderedRemoveFaces.cend()); \ - do { \ - faces.RemoveAt(*(--it)); \ - } while (it != orderedRemoveFaces.cbegin()); \ - } - -struct VertexInfo { - typedef Mesh::VIndex VIndex; - typedef Mesh::FIndex FIndex; - typedef boost::property VertexProperty; - typedef boost::property EdgeProperty; - typedef boost::adjacency_list Graph; - typedef boost::graph_traits::vertex_descriptor Vertex; - typedef boost::graph_traits::edge_descriptor Edge; - typedef boost::property_map::type VertexIndex1Map; - typedef boost::property_map::type EdgeIndexMap; - typedef boost::graph_traits::vertex_iterator VertexIter; - typedef boost::graph_traits::out_edge_iterator EdgeIter; - typedef std::unordered_map Components; - typedef boost::associative_property_map ComponentMap; - typedef std::unordered_map VertexMap; - typedef std::unordered_set VertexSet; - struct FilterVertex { - FilterVertex() {} - FilterVertex(const VertexSet* _filterVerts) : filterVerts(_filterVerts) {} - template - bool operator()(const Vertex& v) const { - return (filterVerts->find(v) == filterVerts->cend()); - } - const VertexSet* filterVerts; - }; - struct FilterEdge { - FilterEdge() {} - FilterEdge(const Graph* _graph, const VertexSet* _filterVerts) : graph(_graph), filterVerts(_filterVerts) {} - template - bool operator()(const Edge& e) const { - return (filterVerts->find(boost::source(e,*graph)) == filterVerts->cend() && - filterVerts->find(boost::target(e,*graph)) == filterVerts->cend()); - } - const Graph* graph; - const VertexSet* filterVerts; - }; - - VertexMap index2idx; // useful/valid only during graph creation - Graph graph; - VertexIndex1Map vertexIndex1; - EdgeIndexMap edgeIndex; - Components components; - VertexSet filterVerts; - - inline VertexInfo() { - vertexIndex1 = boost::get(boost::vertex_index1, graph); - edgeIndex = boost::get(boost::edge_index, graph); - } - Vertex AddVertex(VIndex v) { - auto vert(index2idx.insert(std::make_pair(v, Vertex()))); - if (vert.second) { - vert.first->second = boost::add_vertex(graph); - vertexIndex1[vert.first->second] = v; - } - return vert.first->second; - } - void AddEdge(VIndex v0, VIndex v1, FIndex f) { - boost::add_edge(AddVertex(v0), AddVertex(v1), f, graph); - } - size_t ComputeComponents() { - components.clear(); - ComponentMap componentMap(components); - return boost::connected_components(graph, componentMap); - } - size_t ComputeFilteredComponents() { - ASSERT(!filterVerts.empty()); - FilterEdge filterEdge(&graph, &filterVerts); - FilterVertex filterVertex(&filterVerts); - boost::filtered_graph filterGraph(graph, filterEdge, filterVertex); - components.clear(); - ComponentMap componentMap(components); - const size_t nComponents(boost::connected_components(filterGraph, componentMap)); - filterVerts.clear(); - return nComponents; - } - void Clear() { - graph.clear(); - index2idx.clear(); - } -}; - -// find all non-manifold edges/vertices and for each, duplicate the vertex, -// assigning the new vertex to the smallest connected set of faces; -// return true if problems were found -bool Mesh::FixNonManifold() +// fix non-manifold vertices and edges; +// return the number of non-manifold issues found +unsigned Mesh::FixNonManifold(float magDisplacementDuplicateVertices, VertexIdxArr* duplicatedVertices) { - TD_TIMER_STARTD(); - ASSERT(!vertices.IsEmpty() && !faces.IsEmpty()); - if (vertexFaces.GetSize() != vertices.GetSize()) + ASSERT(!vertices.empty() && !faces.empty()); + if (vertexFaces.size() != vertices.size()) ListIncidenteFaces(); - VertexInfo vertexInfo; - IntArr sizes; - unsigned nNonManifoldVertices(0), nNonManifoldEdges(0), nRemoveFaces(0), nPyramid3(0), nPyramid4(0); - std::unordered_set seenFaces; - std::unordered_set removeFaces; - std::unordered_set componentFaces; - std::unordered_set::const_iterator itFace; - VertexInfo::EdgeIter ei, eie; - // fix non-manifold edges - ASSERT(seenFaces.empty()); - FOREACH(v, vertices) { - const FaceIdxArr& vFaces = vertexFaces[v]; - if (vFaces.GetSize() < 3) - continue; - FOREACHPTR(pFIdx, vFaces) { - const Face& f(faces[*pFIdx]); - const uint32_t i(FindVertex(f, v)); - vertexInfo.AddEdge(f[(i+1)%3], f[(i+2)%3], *pFIdx); - } - for (const auto& idx2id: vertexInfo.index2idx) { - boost::tie(ei, eie) = boost::out_edges(idx2id.second, vertexInfo.graph); - if (std::distance(ei, eie) >= 4) { - ASSERT(vertexInfo.filterVerts.empty()); - // do not proceed, if any of the faces was removed - FOREACHPTR(pFIdx, vFaces) { - if (seenFaces.find(*pFIdx) != seenFaces.cend()) - goto ABORT_EDGE; - } - { - // current vertex and this vertex form the non-manifold edge - if (vertexInfo.ComputeComponents() > 1) { - // filter-out all vertices not belonging to this component - const size_t mainComp(vertexInfo.components[idx2id.second]); - for (const auto& idx2id: vertexInfo.index2idx) { - if (vertexInfo.components[idx2id.second] != mainComp) - vertexInfo.filterVerts.insert(idx2id.second); - } + // iterate over all vertices and separates the components + // incident to the same vertex by duplicating the vertex + unsigned numNonManifoldIssues(0); + CLISTDEF0IDX(int, FIndex) components(faces.size()); + FOREACH(idxVert, vertices) { + // reset component indices to which each face connected to this vertex + const FaceIdxArr& vertFaces = vertexFaces[idxVert]; + for (FIndex iF: vertFaces) + components[iF] = -1; + // find the components connected to this vertex + FaceIdxArr queueFaces; + queueFaces.reserve(vertFaces.size()); + FIndex idxFaceNext(0); + int component(0); + for ( ; ; ++component) { + // find one face not yet belonging to a component + while (idxFaceNext < vertFaces.size()) { + const FIndex iF(vertFaces[idxFaceNext++]); + if (components[iF] == -1) { + // add component as seed to the list + queueFaces.push_back(iF); + // mark the current face with a new component + components[iF] = component; + // process component + goto ProcessComponent; } - // filter-out this vertex to find the two components to be split - vertexInfo.filterVerts.insert(idx2id.second); - const size_t nComponents(vertexInfo.ComputeFilteredComponents()); - if (nComponents < 2) - break; // something is wrong, the vertex configuration is not as expected - // find all vertices in the smallest component - sizes.Resize(nComponents); - sizes.Memset(0); - for (const auto& comp: vertexInfo.components) - ++sizes[comp.second]; - size_t nLongestCompIdx(0); - for (size_t s=1; s 6*/) { - // this is the case of a legitimate vertex on the surface and a pyramid rising from the neighboring surface - // having the apex the current vertex and the base formed by 3 vertices - one being this vertex; - DEFINE_REMOVE3(GENERAL_EDGE); - #if 1 - } else if (componentFaces.size() == 4/* && vertexInfo.components.size() > 8*/) { - // this is the case of a legitimate vertex on the surface and a pyramid rising from the neighboring surface - // having the apex the current vertex and the base formed by 4 vertices - one being this vertex; - DEFINE_REMOVE4(GENERAL_EDGE); - #endif - } else { - GENERAL_EDGE: - // simply duplicate the vertex and assign it to the component faces - const Mesh::VIndex newIndex((Mesh::VIndex)vertices.GetSize()); - for (auto fIdx: componentFaces) - GetVertex(faces[fIdx], (VIndex)v) = newIndex; - vertices.Insert(pos / nComponentVertices); + // if there is exactly one face adjacent to this edge + // tag it with the current component and add it to the queue + const FIndex idxFaceAdj(GetEdgeAdjacentFace(idxFaceCurrent, idxVert, idxVertAdj)); + if (idxFaceAdj != NO_ID && components[idxFaceAdj] == -1) { + components[idxFaceAdj] = component; + queueFaces.push_back(idxFaceAdj); } - for (auto fIdx: componentFaces) - seenFaces.insert(fIdx); - componentFaces.clear(); } - ++nNonManifoldEdges; - } - ABORT_EDGE: - break; - } + } while (!queueFaces.empty()); } - vertexInfo.Clear(); - } - seenFaces.clear(); - DEFINE_REMOVE_FACES; - // fix non-manifold vertices - if (nNonManifoldEdges) - ListIncidenteFaces(); - ASSERT(seenFaces.empty()); - FOREACH(v, vertices) { - const FaceIdxArr& vFaces = vertexFaces[v]; - if (vFaces.GetSize() < 2) + // if there is only one component, continue with the next vertex + if (component <= 1) continue; - FOREACHPTR(pFIdx, vFaces) { - const Face& f(faces[*pFIdx]); - const uint32_t i(FindVertex(f, v)); - vertexInfo.AddEdge(f[(i+1)%3], f[(i+2)%3], *pFIdx); - } - // find all connected sub-graphs - const size_t nComponents(vertexInfo.ComputeComponents()); - if (nComponents == 1) - goto ABORT_VERTEX; - // do not proceed, if any of the faces was removed - FOREACHPTR(pFIdx, vFaces) { - if (seenFaces.find(*pFIdx) != seenFaces.cend()) - goto ABORT_VERTEX; - } - { - // there are at least two connected components (usually exactly two); - // duplicate the vertex and assign the duplicate to the smallest component - ASSERT(nComponents > 1); - sizes.Resize(nComponents); - sizes.Memset(0); - for (const auto& comp: vertexInfo.components) - ++sizes[comp.second]; - size_t nLongestCompIdx(0); - for (size_t s=1; semplace_back(idxVert); + // update the face indices of the current component + FaceIdxArr& vertFacesNew = vertexFaces.emplace_back(); + FaceIdxArr& vertFaces = vertexFaces[idxVert]; + RFOREACH(ivf, vertFaces) { + const FIndex idxFace = vertFaces[ivf]; + if (components[idxFace] != c) + continue; + // link face to the new vertex and remove it from the original vertex + Face& face = faces[idxFace]; + for (int i = 0; i < 3; ++i) { + if (face[i] == idxVert) { + face[i] = idxVertNew; + vertFacesNew.InsertAt(0, idxFace); + break; + } } + vertFaces.RemoveAtMove(ivf); } - if (componentFaces.size() == 3 && sizes[s] == 3 && nComponents == 2/* && vFaces.GetSize() > 6*/) { - // this is the case of a legitimate vertex on the surface and a pyramid rising from the neighboring surface - // having the apex this vertex and the base formed by 3 vertices; - DEFINE_REMOVE3(GENERAL_VERTEX); - #if 1 - } else if (componentFaces.size() == 4 && sizes[s] == 4 && nComponents == 2/* && vFaces.GetSize() > 8*/) { - // this is the case of a legitimate vertex on the surface and a pyramid rising from the neighboring surface - // having the apex this vertex and the base formed by 4 vertices; - DEFINE_REMOVE4(GENERAL_VERTEX); - #endif - } else { - GENERAL_VERTEX: - // simply duplicate the vertex and assign it to the component faces - const VIndex newIndex((VIndex)vertices.GetSize()); - Vertex& pos(vertices.AddEmpty()); - pos = vertices[v]; - for (auto fIdx: componentFaces) - GetVertex(faces[fIdx], (VIndex)v) = newIndex; - } - for (auto fIdx: componentFaces) - seenFaces.insert(fIdx); - componentFaces.clear(); + ++numNonManifoldIssues; } - ++nNonManifoldVertices; + // adjust vertex positions + if (magDisplacementDuplicateVertices > 0) { + // list changed vertices + VertexIdxArr verts(component); + verts[0] = idxVert; + for (int c = 1; c < component; ++c) + verts[c] = vertices.size()-(component-c); + // adjust the position of the vertices in the direction + // to the center of the first ring of faces + FOREACH(i, verts) { + const VIndex idxVert(verts[i]); + VertexIdxArr adjVerts; + GetAdjVertices(idxVert, adjVerts); + TAccumulator accum; + for (VIndex iV: adjVerts) + accum.Add(vertices[iV], 1.f); + const Vertex bv(accum.Normalized()); + Vertex& v(vertices[idxVert]); + const Vertex dir(bv-v); + v += dir * magDisplacementDuplicateVertices; + } } - ABORT_VERTEX:; - vertexInfo.Clear(); } - seenFaces.clear(); - if (nNonManifoldVertices) - vertexFaces.Empty(); - DEFINE_REMOVE_FACES; - DEBUG_ULTIMATE("Fixed %u/%u non-manifold edges/vertices and %u faces removed: %u pyramid3 and %u pyramid4 (%s)", nNonManifoldEdges, nNonManifoldVertices, nRemoveFaces, nPyramid3, nPyramid4, TD_TIMER_GET_FMT().c_str()); - return (nNonManifoldEdges > 0 || nNonManifoldVertices > 0); -} // FixNonManifold -#undef DEFINE_REMOVE_FACES -#undef DEFINE_REMOVE3 -#undef DEFINE_REMOVE4 -#undef DEFINE_FACES4 -#undef IS_LINK_FACE3 -#undef IS_LOOP_FACE3 -#undef DEFINE_FACE_VERTS -#endif + vertexFaces.Release(); + return numNonManifoldIssues; +} /*----------------------------------------------------------------*/ namespace CLEAN { @@ -1127,13 +714,13 @@ class TriEdgeCollapse : public vcg::tri::TriEdgeCollapseQuadric::AddVertices(mesh, vertices.GetSize()); + CLEAN::Mesh::VertexIterator vi = vcg::tri::Allocator::AddVertices(mesh, vertices.size()); FOREACHPTR(pVert, vertices) { const Vertex& p(*pVert); CLEAN::Vertex::CoordType& P((*vi).P()); @@ -1149,7 +736,7 @@ void Mesh::Clean(float fDecimate, float fSpurious, bool bRemoveSpikes, unsigned idx = &*vi; ++vi; } - CLEAN::Mesh::FaceIterator fi = vcg::tri::Allocator::AddFaces(mesh, faces.GetSize()); + CLEAN::Mesh::FaceIterator fi = vcg::tri::Allocator::AddFaces(mesh, faces.size()); FOREACHPTR(pFace, faces) { const Face& f(*pFace); ASSERT((*fi).VN() == 3); @@ -1254,10 +841,10 @@ void Mesh::Clean(float fDecimate, float fSpurious, bool bRemoveSpikes, unsigned edgeLens.Insert((P1-P0).Norm()); } #if 0 - const auto ret(ComputeX84Threshold(edgeLens.Begin(), edgeLens.GetSize(), 3.f*fSpurious)); + const auto ret(ComputeX84Threshold(edgeLens.Begin(), edgeLens.size(), 3.f*fSpurious)); const float thLongEdge(ret.first+ret.second); #else - const float thLongEdge(edgeLens.GetNth(edgeLens.GetSize()*95/100)*fSpurious); + const float thLongEdge(edgeLens.GetNth(edgeLens.size()*95/100)*fSpurious); #endif // remove faces with too long edges const size_t numLongFaces(vcg::tri::UpdateSelection::FaceOutOfRangeEdge(mesh, 0, thLongEdge)); @@ -1384,7 +971,7 @@ void Mesh::Clean(float fDecimate, float fSpurious, bool bRemoveSpikes, unsigned // import VCG mesh { - ASSERT(vertices.IsEmpty() && faces.IsEmpty()); + ASSERT(vertices.empty() && faces.empty()); vertices.Reserve(mesh.VN()); vcg::SimpleTempData indices(mesh.vert); VIndex idx(0); @@ -1409,7 +996,7 @@ void Mesh::Clean(float fDecimate, float fSpurious, bool bRemoveSpikes, unsigned f[2] = indices[fp->cV(2)]; } } - DEBUG("Cleaned mesh: %u vertices, %u faces (%s)", vertices.GetSize(), faces.GetSize(), TD_TIMER_GET_FMT().c_str()); + DEBUG("Cleaned mesh: %u vertices, %u faces (%s)", vertices.size(), faces.size(), TD_TIMER_GET_FMT().c_str()); } // Clean /*----------------------------------------------------------------*/ @@ -1424,91 +1011,184 @@ static const Mesh::TexCoord halfPixel(0.5f, 0.5f); // translate, normalize and flip Y axis of the texture coordinates void Mesh::FaceTexcoordsNormalize(TexCoordArr& newFaceTexcoords, bool flipY) const { - ASSERT(!faceTexcoords.empty() && !textureDiffuse.empty()); - const TexCoord invNorm(1.f/(float)textureDiffuse.cols, 1.f/(float)textureDiffuse.rows); + ASSERT(HasTextureCoordinates()); newFaceTexcoords.resize(faceTexcoords.size()); - if (flipY) { - FOREACH(i, faceTexcoords) { - const TexCoord& texcoord = faceTexcoords[i]; - newFaceTexcoords[i] = TexCoord( - (texcoord.x+halfPixel.x)*invNorm.x, - 1.f-(texcoord.y+halfPixel.y)*invNorm.y - ); - } + if (texturesDiffuse.empty()) { + if (flipY) { + FOREACH(i, faceTexcoords) { + const TexCoord& texcoord = faceTexcoords[i]; + newFaceTexcoords[i] = TexCoord(texcoord.x, 1.f-texcoord.y); + } + } else + newFaceTexcoords = faceTexcoords; } else { - FOREACH(i, faceTexcoords) - newFaceTexcoords[i] = (faceTexcoords[i]+halfPixel)*invNorm; + TexCoordArr invNorms(texturesDiffuse.size()); + FOREACH(i, texturesDiffuse) { + ASSERT(!texturesDiffuse[i].empty()); + invNorms[i] = TexCoord(1.f/(float)texturesDiffuse[i].cols, 1.f/(float)texturesDiffuse[i].rows); + } + if (flipY) { + FOREACH(i, faceTexcoords) { + const TexCoord& texcoord = faceTexcoords[i]; + const TexCoord& invNorm = invNorms[GetFaceTextureIndex(i/3)]; + newFaceTexcoords[i] = TexCoord( + (texcoord.x+halfPixel.x)*invNorm.x, + 1.f-(texcoord.y+halfPixel.y)*invNorm.y + ); + } + } else { + FOREACH(i, faceTexcoords) { + const TexCoord& invNorm = invNorms[GetFaceTextureIndex(i/3)]; + newFaceTexcoords[i] = (faceTexcoords[i]+halfPixel)*invNorm; + } + } } } // FaceTexcoordsNormalize // flip Y axis, unnormalize and translate back texture coordinates void Mesh::FaceTexcoordsUnnormalize(TexCoordArr& newFaceTexcoords, bool flipY) const { - ASSERT(!faceTexcoords.empty() && !textureDiffuse.empty()); - const TexCoord scale((float)textureDiffuse.cols, (float)textureDiffuse.rows); + ASSERT(HasTextureCoordinates()); newFaceTexcoords.resize(faceTexcoords.size()); - if (flipY) { - FOREACH(i, faceTexcoords) { - const TexCoord& texcoord = faceTexcoords[i]; - newFaceTexcoords[i] = TexCoord( - texcoord.x*scale.x-halfPixel.x, - (1.f-texcoord.y)*scale.y-halfPixel.y - ); - } + if (texturesDiffuse.empty()) { + if (flipY) { + FOREACH(i, faceTexcoords) { + const TexCoord& texcoord = faceTexcoords[i]; + newFaceTexcoords[i] = TexCoord(texcoord.x, 1.f-texcoord.y); + } + } else + newFaceTexcoords = faceTexcoords; } else { - FOREACH(i, faceTexcoords) - newFaceTexcoords[i] = faceTexcoords[i]*scale - halfPixel; + TexCoordArr scales(texturesDiffuse.size()); + FOREACH(i, texturesDiffuse) { + ASSERT(!texturesDiffuse[i].empty()); + scales[i] = TexCoord((float)texturesDiffuse[i].cols, (float)texturesDiffuse[i].rows); + } + if (flipY) { + FOREACH(i, faceTexcoords) { + const TexCoord& texcoord = faceTexcoords[i]; + const TexCoord& scale = scales[GetFaceTextureIndex(i/3)]; + newFaceTexcoords[i] = TexCoord( + texcoord.x*scale.x-halfPixel.x, + (1.f-texcoord.y)*scale.y-halfPixel.y + ); + } + } else { + FOREACH(i, faceTexcoords) { + const TexCoord& scale = scales[GetFaceTextureIndex(i/3)]; + newFaceTexcoords[i] = faceTexcoords[i]*scale - halfPixel; + } + } } } // FaceTexcoordsUnnormalize /*----------------------------------------------------------------*/ // define a PLY file format composed only of vertices and triangles +namespace MeshInternal { namespace BasicPLY { - // list of property information for a vertex - static const PLY::PlyProperty vert_props[] = { - {"x", PLY::Float32, PLY::Float32, offsetof(Mesh::Vertex,x), 0, 0, 0, 0}, - {"y", PLY::Float32, PLY::Float32, offsetof(Mesh::Vertex,y), 0, 0, 0, 0}, - {"z", PLY::Float32, PLY::Float32, offsetof(Mesh::Vertex,z), 0, 0, 0, 0} + // list of the kinds of elements in the PLY + static const char* elem_names[] = { + "vertex", + "face" }; - struct VertexNormal { + // list of property information for a vertex + struct Vertex { Mesh::Vertex v; Mesh::Normal n; + static void InitLoadProps(PLY& ply, int elem_count, + Mesh::VertexArr& vertices, Mesh::NormalArr& vertexNormals) + { + PLY::PlyElement* elm = ply.find_element(elem_names[0]); + const size_t nMaxProps(SizeOfArray(props)); + for (size_t p=0; p::max()); - faces.Reserve((FIndex)group.faces.size()); - for (const ObjModel::Face& f: group.faces) { - ASSERT(f.vertices[0] != NO_ID); - faces.emplace_back(f.vertices[0], f.vertices[1], f.vertices[2]); - if (f.texcoords[0] != NO_ID) { - for (int i=0; i<3; ++i) - faceTexcoords.emplace_back(model.get_texcoords()[f.texcoords[i]]); - } - if (f.normals[0] != NO_ID) { - Normal& n = faceNormals.emplace_back(Normal::ZERO); - for (int i=0; i<3; ++i) - n += normalized(model.get_normals()[f.normals[i]]); - normalize(n); + FOREACH(groupIdx, model.get_groups()) { + const auto& group = model.get_groups()[groupIdx]; + ASSERT(group.faces.size() < std::numeric_limits::max()); + faces.reserve((FIndex)group.faces.size()); + for (const ObjModel::Face& f: group.faces) { + ASSERT(f.vertices[0] != NO_ID); + faces.emplace_back(f.vertices[0], f.vertices[1], f.vertices[2]); + if (f.texcoords[0] != NO_ID) { + for (int i=0; i<3; ++i) + faceTexcoords.emplace_back(model.get_texcoords()[f.texcoords[i]]); + faceTexindices.emplace_back((TexIndex)groupIdx); + } + if (f.normals[0] != NO_ID) { + Normal& n = faceNormals.emplace_back(Normal::ZERO); + for (int i=0; i<3; ++i) + n += normalized(model.get_normals()[f.normals[i]]); + normalize(n); + } } + // store texture + ObjModel::MaterialLib::Material* pMaterial(model.GetMaterial(group.material_name)); + if (pMaterial && pMaterial->LoadDiffuseMap()) + texturesDiffuse.emplace_back(pMaterial->diffuse_map); } - // store texture - ObjModel::MaterialLib::Material* pMaterial(model.GetMaterial(group.material_name)); - if (pMaterial && pMaterial->LoadDiffuseMap()) - cv::swap(textureDiffuse, pMaterial->diffuse_map); - // flip Y axis, unnormalize and translate back texture coordinates if (!faceTexcoords.empty()) { + if (texturesDiffuse.size() <= 1) + faceTexindices.Release(); TexCoordArr unnormFaceTexcoords; FaceTexcoordsUnnormalize(unnormFaceTexcoords, true); faceTexcoords.Swap(unnormFaceTexcoords); @@ -1688,7 +1371,7 @@ bool Mesh::LoadOBJ(const String& fileName) // import the mesh as a GLTF file bool Mesh::LoadGLTF(const String& fileName, bool bBinary) { - ASSERT(!fileName.IsEmpty()); + ASSERT(!fileName.empty()); Release(); // load model @@ -1706,7 +1389,7 @@ bool Mesh::LoadGLTF(const String& fileName, bool bBinary) if (!warn.empty()) DEBUG("warning: %s", warn.c_str()); } - + // parse model for (const tinygltf::Mesh& gltfMesh : gltfModel.meshes) { for (const tinygltf::Primitive& gltfPrimitive : gltfMesh.primitives) { @@ -1776,95 +1459,83 @@ bool Mesh::Save(const String& fileName, const cList& comments, bool bBin ret = SavePLY(ext != _T(".ply") ? String(fileName+_T(".ply")) : fileName, comments, bBinary); if (!ret) return false; - DEBUG_EXTRA("Mesh saved: %u vertices, %u faces (%s)", vertices.GetSize(), faces.GetSize(), TD_TIMER_GET_FMT().c_str()); + DEBUG_EXTRA("Mesh saved: %u vertices, %u faces (%s)", vertices.size(), faces.size(), TD_TIMER_GET_FMT().c_str()); return true; } // export the mesh as a PLY file -bool Mesh::SavePLY(const String& fileName, const cList& comments, bool bBinary) const +bool Mesh::SavePLY(const String& fileName, const cList& comments, bool bBinary, bool bTexLossless) const { ASSERT(!fileName.empty()); Util::ensureFolder(fileName); // create PLY object - const size_t bufferSize(vertices.size()*(4*3/*pos*/+2/*eol*/) + faces.size()*(1*1/*len*/+4*3/*idx*/+2/*eol*/) + 2048/*extra size*/); + using namespace MeshInternal; PLY ply; - if (!ply.write(fileName, 2, BasicPLY::elem_names, bBinary?PLY::BINARY_LE:PLY::ASCII, bufferSize)) { + if (!ply.write(fileName, 2, BasicPLY::elem_names, bBinary?PLY::BINARY_LE:PLY::ASCII)) { DEBUG_EXTRA("error: can not create the mesh file"); return false; } // export comments - FOREACHPTR(pStr, comments) - ply.append_comment(pStr->c_str()); + for (const String& comment: comments) + ply.append_comment(comment); // export texture file name as comment if needed - String textureFileName; - if (!faceTexcoords.empty() && !textureDiffuse.empty()) { - textureFileName = Util::getFileFullName(fileName)+_T(".png"); - ply.append_comment((_T("TextureFile ")+Util::getFileNameExt(textureFileName)).c_str()); + if (HasTexture()) { + FOREACH(texId, texturesDiffuse) { + const String textureFileName(Util::getFileFullName(fileName) + std::to_string(texId).c_str() + (bTexLossless?_T(".png"):_T(".jpg"))); + ply.append_comment((_T("TextureFile ")+Util::getFileNameExt(textureFileName)).c_str()); + texturesDiffuse[texId].Save(textureFileName); + } } - if (vertexNormals.empty()) { - // describe what properties go into the vertex elements - ply.describe_property(BasicPLY::elem_names[0], 3, BasicPLY::vert_props); + // describe what properties go into vertex and face elements + ASSERT(vertexNormals.empty() || vertexNormals.size() == vertices.size()); + BasicPLY::Vertex::InitSaveProps(ply, (int)vertices.size(), !vertexNormals.empty()); + BasicPLY::Face::InitSaveProps(ply, (int)faces.size(), !faces.empty(), !faceTexcoords.empty(), !faceTexindices.empty()); + if (!ply.header_complete()) + return false; - // export the array of vertices + // export the array of vertices + BasicPLY::Vertex::Select(ply); + if (vertexNormals.empty()) { FOREACHPTR(pVert, vertices) ply.put_element(pVert); } else { - ASSERT(vertices.size() == vertexNormals.size()); - - // describe what properties go into the vertex elements - ply.describe_property(BasicPLY::elem_names[0], 6, BasicPLY::vert_normal_props); - - // export the array of vertices - BasicPLY::VertexNormal vn; + BasicPLY::Vertex v; FOREACH(i, vertices) { - vn.v = vertices[i]; - vn.n = vertexNormals[i]; - ply.put_element(&vn); + v.v = vertices[i]; + v.n = vertexNormals[i]; + ply.put_element(&v); } } - if (ply.get_current_element_count() == 0) - return false; + ASSERT(ply.get_current_element_count() == (int)vertices.size()); + // export the array of faces + BasicPLY::Face::Select(ply); + BasicPLY::Face face = {{3},{6}}; if (faceTexcoords.empty()) { - // describe what properties go into the vertex elements - ply.describe_property(BasicPLY::elem_names[1], 1, BasicPLY::face_props); - - // export the array of faces - BasicPLY::Face face = {3}; FOREACHPTR(pFace, faces) { - face.pFace = pFace; + face.face.pFace = const_cast(pFace); ply.put_element(&face); } } else { - ASSERT(faceTexcoords.size() == faces.size()*3); - // translate, normalize and flip Y axis of the texture coordinates TexCoordArr normFaceTexcoords; FaceTexcoordsNormalize(normFaceTexcoords, true); - // describe what properties go into the vertex elements - ply.describe_property(BasicPLY::elem_names[1], 2, BasicPLY::face_tex_props); - // export the array of faces - BasicPLY::FaceTex face = {{3},{6}}; FOREACH(f, faces) { face.face.pFace = faces.data()+f; face.tex.pTex = normFaceTexcoords.data()+f*3; + if (!faceTexindices.empty()) + face.texId = faceTexindices[f]; ply.put_element(&face); } - - // export the texture - if (!textureDiffuse.empty()) - textureDiffuse.Save(textureFileName); } - if (ply.get_current_element_count() == 0) - return false; + ASSERT(ply.get_current_element_count() == (int)faces.size()); - // write to file - return ply.header_complete(); + return true; } // export the mesh as a OBJ file bool Mesh::SaveOBJ(const String& fileName) const @@ -1898,27 +1569,31 @@ bool Mesh::SaveOBJ(const String& fileName) const } // store faces - ObjModel::Group& group = model.AddGroup(_T("material_0")); - group.faces.reserve(faces.size()); - FOREACH(idxFace, faces) { - const Face& face = faces[idxFace]; - ObjModel::Face f; - memset(&f, 0xFF, sizeof(ObjModel::Face)); - for (int i=0; i<3; ++i) { - f.vertices[i] = face[i]; - if (!faceTexcoords.empty()) - f.texcoords[i] = idxFace*3+i; - if (!vertexNormals.empty()) - f.normals[i] = face[i]; + FOREACH(idxTexture, texturesDiffuse) { + ObjModel::Group& group = model.AddGroup(_T("material_" + std::to_string(idxTexture))); + group.faces.reserve(faces.size()); + FOREACH(idxFace, faces) { + const auto texIdx = faceTexindices[idxFace]; + if (texIdx != idxTexture) + continue; + + const Face& face = faces[idxFace]; + ObjModel::Face f; + memset(&f, 0xFF, sizeof(ObjModel::Face)); + for (int i=0; i<3; ++i) { + f.vertices[i] = face[i]; + if (!faceTexcoords.empty()) + f.texcoords[i] = idxFace*3+i; + if (!vertexNormals.empty()) + f.normals[i] = face[i]; + } + group.faces.push_back(f); } - group.faces.push_back(f); + ObjModel::MaterialLib::Material* pMaterial(model.GetMaterial(group.material_name)); + ASSERT(pMaterial != NULL); + pMaterial->diffuse_map = texturesDiffuse[idxTexture]; } - // store texture - ObjModel::MaterialLib::Material* pMaterial(model.GetMaterial(group.material_name)); - ASSERT(pMaterial != NULL); - pMaterial->diffuse_map = textureDiffuse; - return model.Save(fileName); } // export the mesh as a GLTF file @@ -1930,136 +1605,148 @@ void ExtendBufferGLTF(const T* src, size_t size, tinygltf::Buffer& dst, size_t& dst.data.resize(byte_offset + byte_length); memcpy(&dst.data[byte_offset], &src[0], byte_length); } + bool Mesh::SaveGLTF(const String& fileName, bool bBinary) const { - ASSERT(!fileName.IsEmpty()); + ASSERT(!fileName.empty()); Util::ensureFolder(fileName); - // store a copy of the mesh if it has texture, in order to convert - // the texture coordinates from per face to per vertex - Mesh meshCompressed; - if (HasTexture()) - ConvertTexturePerVertex(meshCompressed); - const Mesh& mesh(HasTexture() ? meshCompressed : *this); + std::vector meshes; + if (texturesDiffuse.size() > 1) { + meshes = SplitMeshPerTextureBlob(); + for (Mesh& mesh: meshes) { + Mesh convertedMesh; + mesh.ConvertTexturePerVertex(convertedMesh); + mesh.Swap(convertedMesh); + } + } else { + Mesh convertedMesh; + ConvertTexturePerVertex(convertedMesh); + meshes.emplace_back(std::move(convertedMesh)); + } // create GLTF model tinygltf::Model gltfModel; tinygltf::Scene gltfScene; tinygltf::Mesh gltfMesh; - tinygltf::Primitive gltfPrimitive; tinygltf::Buffer gltfBuffer; gltfScene.name = "scene"; gltfMesh.name = "mesh"; - // setup vertices - { - STATIC_ASSERT(3 * sizeof(Vertex::Type) == sizeof(Vertex)); // VertexArr should be continuous - const Box box(GetAABB()); - gltfPrimitive.attributes["POSITION"] = (int)gltfModel.accessors.size(); - tinygltf::Accessor vertexPositionAccessor; - vertexPositionAccessor.name = "vertexPositionAccessor"; - vertexPositionAccessor.bufferView = (int)gltfModel.bufferViews.size(); - vertexPositionAccessor.type = TINYGLTF_TYPE_VEC3; - vertexPositionAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; - vertexPositionAccessor.count = mesh.vertices.size(); - vertexPositionAccessor.minValues = {box.ptMin.x(), box.ptMin.y(), box.ptMin.z()}; - vertexPositionAccessor.maxValues = {box.ptMax.x(), box.ptMax.y(), box.ptMax.z()}; - gltfModel.accessors.emplace_back(std::move(vertexPositionAccessor)); - // setup vertices buffer - tinygltf::BufferView vertexPositionBufferView; - vertexPositionBufferView.name = "vertexPositionBufferView"; - vertexPositionBufferView.buffer = (int)gltfModel.buffers.size(); - ExtendBufferGLTF(mesh.vertices.data(), mesh.vertices.size(), gltfBuffer, - vertexPositionBufferView.byteOffset, vertexPositionBufferView.byteLength); - gltfModel.bufferViews.emplace_back(std::move(vertexPositionBufferView)); - } + for (size_t meshId = 0; meshId < meshes.size(); meshId++) { + const Mesh& mesh = meshes[meshId]; + ASSERT(mesh.HasTextureCoordinatesPerVertex()); + tinygltf::Primitive gltfPrimitive; + // setup vertices + { + STATIC_ASSERT(3 * sizeof(Vertex::Type) == sizeof(Vertex)); // VertexArr should be continuous + const Box box(GetAABB()); + gltfPrimitive.attributes["POSITION"] = (int)gltfModel.accessors.size(); + tinygltf::Accessor vertexPositionAccessor; + vertexPositionAccessor.name = "vertexPositionAccessor"; + vertexPositionAccessor.bufferView = (int)gltfModel.bufferViews.size(); + vertexPositionAccessor.type = TINYGLTF_TYPE_VEC3; + vertexPositionAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + vertexPositionAccessor.count = mesh.vertices.size(); + vertexPositionAccessor.minValues = {box.ptMin.x(), box.ptMin.y(), box.ptMin.z()}; + vertexPositionAccessor.maxValues = {box.ptMax.x(), box.ptMax.y(), box.ptMax.z()}; + gltfModel.accessors.emplace_back(std::move(vertexPositionAccessor)); + // setup vertices buffer + tinygltf::BufferView vertexPositionBufferView; + vertexPositionBufferView.name = "vertexPositionBufferView"; + vertexPositionBufferView.buffer = (int)gltfModel.buffers.size(); + ExtendBufferGLTF(mesh.vertices.data(), mesh.vertices.size(), gltfBuffer, + vertexPositionBufferView.byteOffset, vertexPositionBufferView.byteLength); + gltfModel.bufferViews.emplace_back(std::move(vertexPositionBufferView)); + } - // setup faces - { - STATIC_ASSERT(3 * sizeof(Face::Type) == sizeof(Face)); // FaceArr should be continuous - gltfPrimitive.indices = (int)gltfModel.accessors.size(); - tinygltf::Accessor triangleAccessor; - triangleAccessor.name = "triangleAccessor"; - triangleAccessor.bufferView = (int)gltfModel.bufferViews.size(); - triangleAccessor.type = TINYGLTF_TYPE_SCALAR; - triangleAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT; - triangleAccessor.count = mesh.faces.size() * 3; - gltfModel.accessors.emplace_back(std::move(triangleAccessor)); - // setup triangles buffer - tinygltf::BufferView triangleBufferView; - triangleBufferView.name = "triangleBufferView"; - triangleBufferView.buffer = (int)gltfModel.buffers.size(); - ExtendBufferGLTF(mesh.faces.data(), mesh.faces.size(), gltfBuffer, - triangleBufferView.byteOffset, triangleBufferView.byteLength); - gltfModel.bufferViews.emplace_back(std::move(triangleBufferView)); - gltfPrimitive.mode = TINYGLTF_MODE_TRIANGLES; - } + // setup faces + { + STATIC_ASSERT(3 * sizeof(Face::Type) == sizeof(Face)); // FaceArr should be continuous + gltfPrimitive.indices = (int)gltfModel.accessors.size(); + tinygltf::Accessor triangleAccessor; + triangleAccessor.name = "triangleAccessor"; + triangleAccessor.bufferView = (int)gltfModel.bufferViews.size(); + triangleAccessor.type = TINYGLTF_TYPE_SCALAR; + triangleAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT; + triangleAccessor.count = mesh.faces.size() * 3; + gltfModel.accessors.emplace_back(std::move(triangleAccessor)); + // setup triangles buffer + tinygltf::BufferView triangleBufferView; + triangleBufferView.name = "triangleBufferView"; + triangleBufferView.buffer = (int)gltfModel.buffers.size(); + ExtendBufferGLTF(mesh.faces.data(), mesh.faces.size(), gltfBuffer, + triangleBufferView.byteOffset, triangleBufferView.byteLength); + gltfModel.bufferViews.emplace_back(std::move(triangleBufferView)); + gltfPrimitive.mode = TINYGLTF_MODE_TRIANGLES; + } - // setup material - gltfPrimitive.material = (int)gltfModel.materials.size(); - tinygltf::Material gltfMaterial; - gltfMaterial.name = "material"; - gltfMaterial.doubleSided = true; - if (mesh.HasTexture()) { - // setup texture - gltfMaterial.emissiveFactor = std::vector{0,0,0}; - gltfMaterial.pbrMetallicRoughness.baseColorTexture.index = (int)gltfModel.textures.size(); - gltfMaterial.pbrMetallicRoughness.baseColorTexture.texCoord = 0; - gltfMaterial.pbrMetallicRoughness.baseColorFactor = std::vector{1,1,1,1}; - gltfMaterial.pbrMetallicRoughness.metallicFactor = 0; - gltfMaterial.pbrMetallicRoughness.roughnessFactor = 1; - gltfMaterial.extensions = {{"KHR_materials_unlit", {}}}; - gltfModel.extensionsUsed = {"KHR_materials_unlit"}; - // setup texture coordinates accessor - gltfPrimitive.attributes["TEXCOORD_0"] = (int)gltfModel.accessors.size(); - tinygltf::Accessor vertexTexcoordAccessor; - vertexTexcoordAccessor.name = "vertexTexcoordAccessor"; - vertexTexcoordAccessor.bufferView = (int)gltfModel.bufferViews.size(); - vertexTexcoordAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; - vertexTexcoordAccessor.count = mesh.faceTexcoords.size(); - vertexTexcoordAccessor.type = TINYGLTF_TYPE_VEC2; - gltfModel.accessors.emplace_back(std::move(vertexTexcoordAccessor)); - // setup texture coordinates - STATIC_ASSERT(2 * sizeof(TexCoord::Type) == sizeof(TexCoord)); // TexCoordArr should be continuous - ASSERT(mesh.vertices.size() == mesh.faceTexcoords.size()); - tinygltf::BufferView vertexTexcoordBufferView; - vertexTexcoordBufferView.name = "vertexTexcoordBufferView"; - vertexTexcoordBufferView.buffer = (int)gltfModel.buffers.size(); - TexCoordArr normFaceTexcoords; - mesh.FaceTexcoordsNormalize(normFaceTexcoords, false); - ExtendBufferGLTF(normFaceTexcoords.data(), normFaceTexcoords.size(), gltfBuffer, - vertexTexcoordBufferView.byteOffset, vertexTexcoordBufferView.byteLength); - gltfModel.bufferViews.emplace_back(std::move(vertexTexcoordBufferView)); - // setup texture - tinygltf::Texture texture; - texture.name = "texture"; - texture.source = (int)gltfModel.images.size(); - texture.sampler = (int)gltfModel.samplers.size(); - gltfModel.textures.emplace_back(std::move(texture)); - // setup texture image - tinygltf::Image image; - image.name = Util::getFileFullName(fileName); - image.width = mesh.textureDiffuse.cols; - image.height = mesh.textureDiffuse.rows; - image.component = 3; - image.bits = 8; - image.pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; - image.mimeType = "image/png"; - image.image.resize(mesh.textureDiffuse.size().area() * 3); - mesh.textureDiffuse.copyTo(cv::Mat(mesh.textureDiffuse.size(), CV_8UC3, image.image.data())); - gltfModel.images.emplace_back(std::move(image)); - // setup texture sampler - tinygltf::Sampler sampler; - sampler.name = "sampler"; - sampler.minFilter = TINYGLTF_TEXTURE_FILTER_LINEAR; - sampler.magFilter = TINYGLTF_TEXTURE_FILTER_LINEAR; - sampler.wrapS = TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE; - sampler.wrapT = TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE; - gltfModel.samplers.emplace_back(std::move(sampler)); + // setup material + gltfPrimitive.material = (int)gltfModel.materials.size(); + tinygltf::Material gltfMaterial; + gltfMaterial.name = "material"; + gltfMaterial.doubleSided = true; + if (mesh.HasTexture()) { + // setup texture + gltfMaterial.emissiveFactor = std::vector{0,0,0}; + gltfMaterial.pbrMetallicRoughness.baseColorTexture.index = (int)gltfModel.textures.size(); + gltfMaterial.pbrMetallicRoughness.baseColorTexture.texCoord = 0; + gltfMaterial.pbrMetallicRoughness.baseColorFactor = std::vector{1,1,1,1}; + gltfMaterial.pbrMetallicRoughness.metallicFactor = 0; + gltfMaterial.pbrMetallicRoughness.roughnessFactor = 1; + gltfMaterial.extensions = {{"KHR_materials_unlit", {}}}; + gltfModel.extensionsUsed = {"KHR_materials_unlit"}; + // setup texture coordinates accessor + gltfPrimitive.attributes["TEXCOORD_0"] = (int)gltfModel.accessors.size(); + tinygltf::Accessor vertexTexcoordAccessor; + vertexTexcoordAccessor.name = "vertexTexcoordAccessor"; + vertexTexcoordAccessor.bufferView = (int)gltfModel.bufferViews.size(); + vertexTexcoordAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + vertexTexcoordAccessor.count = mesh.faceTexcoords.size(); + vertexTexcoordAccessor.type = TINYGLTF_TYPE_VEC2; + gltfModel.accessors.emplace_back(std::move(vertexTexcoordAccessor)); + // setup texture coordinates + STATIC_ASSERT(2 * sizeof(TexCoord::Type) == sizeof(TexCoord)); // TexCoordArr should be continuous + ASSERT(mesh.vertices.size() == mesh.faceTexcoords.size()); + tinygltf::BufferView vertexTexcoordBufferView; + vertexTexcoordBufferView.name = "vertexTexcoordBufferView"; + vertexTexcoordBufferView.buffer = (int)gltfModel.buffers.size(); + TexCoordArr normFaceTexcoords; + mesh.FaceTexcoordsNormalize(normFaceTexcoords, false); + ExtendBufferGLTF(normFaceTexcoords.data(), normFaceTexcoords.size(), gltfBuffer, + vertexTexcoordBufferView.byteOffset, vertexTexcoordBufferView.byteLength); + gltfModel.bufferViews.emplace_back(std::move(vertexTexcoordBufferView)); + // setup texture + tinygltf::Texture texture; + texture.name = "texture"; + texture.source = (int)gltfModel.images.size(); + texture.sampler = (int)gltfModel.samplers.size(); + gltfModel.textures.emplace_back(std::move(texture)); + // setup texture image + tinygltf::Image image; + image.name = Util::getFileFullName(fileName) + "_" + std::to_string(meshId).c_str(); + image.width = mesh.texturesDiffuse[0].cols; + image.height = mesh.texturesDiffuse[0].rows; + image.component = 3; + image.bits = 8; + image.pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + image.mimeType = "image/png"; + image.image.resize(mesh.texturesDiffuse[0].size().area() * 3); + mesh.texturesDiffuse[0].copyTo(cv::Mat(mesh.texturesDiffuse[0].size(), CV_8UC3, image.image.data())); + gltfModel.images.emplace_back(std::move(image)); + // setup texture sampler + tinygltf::Sampler sampler; + sampler.name = "sampler"; + sampler.minFilter = TINYGLTF_TEXTURE_FILTER_LINEAR; + sampler.magFilter = TINYGLTF_TEXTURE_FILTER_LINEAR; + sampler.wrapS = TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE; + sampler.wrapT = TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE; + gltfModel.samplers.emplace_back(std::move(sampler)); + } + gltfModel.materials.emplace_back(std::move(gltfMaterial)); + gltfModel.buffers.emplace_back(std::move(gltfBuffer)); + gltfMesh.primitives.emplace_back(std::move(gltfPrimitive)); } - gltfModel.materials.emplace_back(std::move(gltfMaterial)); - gltfModel.buffers.emplace_back(std::move(gltfBuffer)); - gltfMesh.primitives.emplace_back(std::move(gltfPrimitive)); // setup scene node gltfScene.nodes.emplace_back((int)gltfModel.nodes.size()); @@ -2107,28 +1794,28 @@ bool Mesh::Save(const FacesChunkArr& chunks, const String& fileName, const cList bool Mesh::Save(const VertexArr& vertices, const String& fileName, bool bBinary) { - ASSERT(!fileName.IsEmpty()); + ASSERT(!fileName.empty()); Util::ensureFolder(fileName); // create PLY object - const size_t bufferSize(vertices.GetSize()*(4*3/*pos*/+2/*eol*/) + 2048/*extra size*/); + using namespace MeshInternal; PLY ply; - if (!ply.write(fileName, 1, BasicPLY::elem_names, bBinary?PLY::BINARY_LE:PLY::ASCII, bufferSize)) { + if (!ply.write(fileName, 1, BasicPLY::elem_names, bBinary?PLY::BINARY_LE:PLY::ASCII)) { DEBUG_EXTRA("error: can not create the mesh file"); return false; } - // describe what properties go into the vertex elements - ply.describe_property(BasicPLY::elem_names[0], 3, BasicPLY::vert_props); + // describe what properties go into vertex and face elements + BasicPLY::Vertex::InitSaveProps(ply, (int)vertices.size(), false); + if (!ply.header_complete()) + return false; // export the array of vertices FOREACHPTR(pVert, vertices) ply.put_element(pVert); - if (ply.get_current_element_count() == 0) - return false; + ASSERT(ply.get_current_element_count() == (int)vertices.size()); - // write to file - return ply.header_complete(); + return true; } /*----------------------------------------------------------------*/ @@ -3011,16 +2698,16 @@ static void EnsureEdgeSize(Polyhedron& p, double epsilonMin, double epsilonMax, ComputeStatsEdge(p, edge); // process big edges - ASSERT(bigEdges.IsEmpty()); + ASSERT(bigEdges.empty()); for (Halfedge_iterator h = p.edges_begin(); h != p.edges_end(); ++h, ++h) { ASSERT(++Halfedge_iterator(h) == h->opposite()); const double edgeSize(edge_size(h)); if (edgeSize > epsilonMax) - bigEdges.AddConstruct(h, (float)edgeSize); + bigEdges.emplace_back(h, (float)edgeSize); } - DEBUG_LEVEL(3, "Big edges: %u", bigEdges.GetSize()); + DEBUG_LEVEL(3, "Big edges: %u", bigEdges.size()); bigEdges.Sort(); // process big edges first - for (EdgeScoreArr::IDX i=0; i void operator() (HDS& hds) { typedef typename HDS::Vertex::Point Point; CGAL::Polyhedron_incremental_builder_3 B(hds, false); - B.begin_surface(vertices.GetSize(), faces.GetSize()); + B.begin_surface(vertices.size(), faces.size()); // add the vertices FOREACH(i, vertices) { const Mesh::Vertex& v = vertices[i]; @@ -3353,7 +3040,7 @@ void Mesh::EnsureEdgeSize(float epsilonMin, float epsilonMax, float collapseRati // is bigger than the given number of pixels void Mesh::Subdivide(const AreaArr& maxAreas, uint32_t maxArea) { - ASSERT(vertexFaces.GetSize() == vertices.GetSize()); + ASSERT(vertexFaces.size() == vertices.size()); // each face that needs to split, remember for each edge the new vertex index // (each new vertex index corresponds to the edge opposed to the existing vertex index) @@ -3378,10 +3065,10 @@ void Mesh::Subdivide(const AreaArr& maxAreas, uint32_t maxArea) typedef Mesh::FacetCountMap FacetCountMap; // for each image, compute the projection area of visible faces - FacetSplitMap mapSplits; mapSplits.reserve(faces.GetSize()); + FacetSplitMap mapSplits; mapSplits.reserve(faces.size()); FacetCountMap mapFaces; mapFaces.reserve(12*3); - vertices.Reserve(vertices.GetSize()*2); - faces.Reserve(faces.GetSize()*3); + vertices.Reserve(vertices.size()*2); + faces.Reserve(faces.size()*3); const uint32_t maxAreaTh(2*maxArea); FOREACH(f, maxAreas) { const AreaArr::Type area(maxAreas[f]); @@ -3401,8 +3088,8 @@ void Mesh::Subdivide(const AreaArr& maxAreas, uint32_t maxArea) } // create a new vertex at the middle of the current edge // (current edge is the opposite edge to the current vertex index) - split.idxVert[i] = newface[i] = vertices.GetSize(); - vertices.AddConstruct((vertices[face[(i+1)%3]]+vertices[face[(i+2)%3]])*0.5f); + split.idxVert[i] = newface[i] = vertices.size(); + vertices.emplace_back((vertices[face[(i+1)%3]]+vertices[face[(i+2)%3]])*0.5f); } // create the last three faces, defined by one old and two new vertices for (int i=0; i<3; ++i) { @@ -3524,7 +3211,7 @@ void Mesh::Subdivide(const AreaArr& maxAreas, uint32_t maxArea) } // remove all faces that split - ASSERT(faces.GetSize()-(faces.GetCapacity()/3)/*initial size*/ > mapSplits.size()); + ASSERT(faces.size()-(faces.capacity()/3)/*initial size*/ > mapSplits.size()); for (const auto& s: mapSplits) faces.RemoveAt(s.first); } @@ -3534,32 +3221,32 @@ void Mesh::Subdivide(const AreaArr& maxAreas, uint32_t maxArea) //#define DECIMATE_JOINHOLES // not finished void Mesh::Decimate(VertexIdxArr& verticesRemove) { - ASSERT(vertices.GetSize() == vertexFaces.GetSize()); - FaceIdxArr facesRemove(0, verticesRemove.GetSize()*8); + ASSERT(vertices.size() == vertexFaces.size()); + FaceIdxArr facesRemove(0, verticesRemove.size()*8); #ifdef DECIMATE_JOINHOLES cList holes; #endif FOREACHPTR(pIdxV, verticesRemove) { const VIndex idxV(*pIdxV); - ASSERT(idxV < vertices.GetSize()); + ASSERT(idxV < vertices.size()); // create the list of consecutive vertices around selected vertex VertexIdxArr verts; { FaceIdxArr& vf(vertexFaces[idxV]); - if (vf.IsEmpty()) + if (vf.empty()) continue; - const FIndex n(vf.GetSize()); + const FIndex n(vf.size()); facesRemove.Join(vf); - ASSERT(verts.IsEmpty()); + ASSERT(verts.empty()); { // add vertices of the first face - const Face& f = faces[vf.First()]; + const Face& f = faces[vf.front()]; const uint32_t i(FindVertex(f, idxV)); verts.Insert(f[(i+1)%3]); verts.Insert(f[(i+2)%3]); vf.RemoveAt(0); } - while (verts.GetSize() < n) { + while (verts.size() < n) { // find the face that contains our vertex and the last added vertex const VIndex idxVL(verts.Last()); FOREACH(idxF, vf) { @@ -3571,7 +3258,7 @@ void Mesh::Decimate(VertexIdxArr& verticesRemove) // add the missing vertex at the end ASSERT(f[(i+2)%3] == idxV); const FIndex idxVN(f[(i+1)%3]); - ASSERT(verts.First() != idxVN); + ASSERT(verts.front() != idxVN); verts.Insert(idxVN); vf.RemoveAt(idxF); goto NEXT_FACE_FORWARD; @@ -3586,9 +3273,9 @@ void Mesh::Decimate(VertexIdxArr& verticesRemove) break; NEXT_FACE_FORWARD:; } - while (!vf.IsEmpty()) { + while (!vf.empty()) { // find the face that contains our vertex and the first added vertex - const VIndex idxVF(verts.First()); + const VIndex idxVF(verts.front()); FOREACH(idxF, vf) { const Face& f = faces[vf[idxF]]; ASSERT(FindVertex(f, idxV) != NO_ID); @@ -3598,7 +3285,7 @@ void Mesh::Decimate(VertexIdxArr& verticesRemove) // add the missing vertex at the beginning ASSERT(f[(i+1)%3] == idxV); const FIndex idxVP(f[(i+2)%3]); - ASSERT(verts.Last() != idxVP || vf.GetSize() == 1); + ASSERT(verts.Last() != idxVP || vf.size() == 1); if (verts.Last() != idxVP) verts.InsertAt(0, idxVP); vf.RemoveAt(idxF); @@ -3628,23 +3315,23 @@ void Mesh::Decimate(VertexIdxArr& verticesRemove) // extend the hole with the new loop vertices VertexIdxArr& hole(*pHole); hole.RemoveAtMove(idxVH); - const VIndex idxS((idxVH+hole.GetSize()-1)%hole.GetSize()); + const VIndex idxS((idxVH+hole.size()-1)%hole.size()); const VIndex idxL(verts.Find(hole[idxS])); ASSERT(idxL != VertexIdxArr::NO_INDEX); - ASSERT(verts[(idxL+verts.GetSize()-1)%verts.GetSize()] == hole[(idxS+1)%hole.GetSize()]); - const VIndex n(verts.GetSize()-2); + ASSERT(verts[(idxL+verts.size()-1)%verts.size()] == hole[(idxS+1)%hole.size()]); + const VIndex n(verts.size()-2); for (VIndex v=1; v<=n; ++v) - hole.InsertAt(idxS+v, verts[(idxL+v)%verts.GetSize()]); + hole.InsertAt(idxS+v, verts[(idxL+v)%verts.size()]); goto NEXT_VERTEX; } // or create a new hole - if (verts.GetSize() < 3) + if (verts.size() < 3) continue; verts.Swap(holes.AddEmpty()); #else // close the holes defined by the complete loop of consecutive vertices // (the loop can be opened, cause some of the vertices can be on the border) - if (verts.GetSize() > 2) + if (verts.size() > 2) CloseHoleQuality(verts); #endif NEXT_VERTEX:; @@ -3652,7 +3339,7 @@ void Mesh::Decimate(VertexIdxArr& verticesRemove) #ifndef _RELEASE // check all removed vertices are completely disconnected from the mesh FOREACHPTR(pIdxV, verticesRemove) - ASSERT(vertexFaces[*pIdxV].IsEmpty()); + ASSERT(vertexFaces[*pIdxV].empty()); #endif // remove deleted faces @@ -3665,18 +3352,16 @@ void Mesh::Decimate(VertexIdxArr& verticesRemove) // close the holes defined by the complete loop of consecutive vertices // (the loop can be opened, cause some of the vertices can be on the border) FOREACHPTR(pHole, holes) { - ASSERT(pHole->GetSize() > 2); + ASSERT(pHole->size() > 2); CloseHoleQuality(*pHole); } #endif #ifndef _RELEASE // check all faces see valid vertices - FOREACH(idxF, faces) { - const Face& face = faces[idxF]; + for (const Face& face: faces) for (int v=0; v<3; ++v) - ASSERT(face[v] < vertices.GetSize()); - } + ASSERT(face[v] < vertices.size()); #endif } /*----------------------------------------------------------------*/ @@ -3685,28 +3370,28 @@ void Mesh::Decimate(VertexIdxArr& verticesRemove) // split it recursively in two halves till the splits becomes a face void Mesh::CloseHole(VertexIdxArr& split0) { - ASSERT(split0.GetSize() >= 3); - if (split0.GetSize() == 3) { - const FIndex idxF(faces.GetSize()); - faces.AddConstruct(split0[0], split0[1], split0[2]); + ASSERT(split0.size() >= 3); + if (split0.size() == 3) { + const FIndex idxF(faces.size()); + faces.emplace_back(split0[0], split0[1], split0[2]); for (int v=0; v<3; ++v) { #ifndef _RELEASE FaceIdxArr indices; GetAdjVertexFaces(split0[v], split0[(v+1)%3], indices); - ASSERT(indices.GetSize() < 2); + ASSERT(indices.size() < 2); indices.Empty(); GetAdjVertexFaces(split0[v], split0[(v+2)%3], indices); - ASSERT(indices.GetSize() < 2); + ASSERT(indices.size() < 2); #endif vertexFaces[split0[v]].Insert(idxF); } return; } - const VIndex i(split0.GetSize() >> 1); - const VIndex j(split0.GetSize()-i); + const VIndex i(split0.size() >> 1); + const VIndex j(split0.size()-i); VertexIdxArr split1(0, j+1); - split1.Join(split0.Begin()+i, j); - split1.Insert(split0.First()); + split1.Join(split0.data()+i, j); + split1.emplace_back(split0.front()); split0.RemoveLast(j-1); CloseHole(split0); CloseHole(split1); @@ -3738,27 +3423,27 @@ void Mesh::CloseHoleQuality(VertexIdxArr& verts) // which are possible not to exist if the edges are on the border FaceIdxArr indices; mesh.GetAdjVertexFaces(face[2], face[0], indices); - if (indices.GetSize() > 1) { + if (indices.size() > 1) { aspectRatio = -1; return; } indices.Empty(); mesh.GetAdjVertexFaces(face[0], face[1], indices); - if (indices.GetSize() > 1) { + if (indices.size() > 1) { aspectRatio = -1; return; } - const FIndex i0(indices.GetSize()); + const FIndex i0(indices.size()); mesh.GetAdjVertexFaces(face[1], face[2], indices); - if (indices.GetSize()-i0 > 1) { + if (indices.size()-i0 > 1) { aspectRatio = -1; return; } - if (indices.IsEmpty()) + if (indices.empty()) dihedral = FD2R(33.f); else { const Normal n0(mesh.FaceNormal(mesh.faces[indices[0]])); - if (indices.GetSize() == 1) + if (indices.size() == 1) dihedral = ACOS(ComputeAngle(n.ptr(), n0.ptr())); else { const Normal n1(mesh.FaceNormal(mesh.faces[indices[1]])); @@ -3784,10 +3469,10 @@ void Mesh::CloseHoleQuality(VertexIdxArr& verts) }; // create the initial list of new possible face along the edge of the hole - ASSERT(verts.GetSize() > 2); - cList candidateFaces(0, verts.GetSize()); + ASSERT(verts.size() > 2); + cList candidateFaces(0, verts.size()); FOREACH(v, verts) { - if (candidateFaces.AddConstruct(verts[v], verts[(v+1)%verts.GetSize()], verts[(v+2)%verts.GetSize()], *this).aspectRatio < 0) + if (candidateFaces.emplace_back(verts[v], verts[(v+1)%verts.size()], verts[(v+2)%verts.size()], *this).aspectRatio < 0) candidateFaces.RemoveLast(); } candidateFaces.Sort(); @@ -3795,25 +3480,25 @@ void Mesh::CloseHoleQuality(VertexIdxArr& verts) // add new faces until there are only two vertices left while(true) { // add the best candidate face - ASSERT(!candidateFaces.IsEmpty()); + ASSERT(!candidateFaces.empty()); const Face& candidateFace = candidateFaces.Last(); ASSERT(verts.Find(candidateFace[0]) != VertexIdxArr::NO_INDEX); ASSERT(verts.Find(candidateFace[1]) != VertexIdxArr::NO_INDEX); ASSERT(verts.Find(candidateFace[2]) != VertexIdxArr::NO_INDEX); - const FIndex idxF(faces.GetSize()); + const FIndex idxF(faces.size()); faces.Insert(candidateFace); for (int v=0; v<3; ++v) { #ifndef _RELEASE FaceIdxArr indices; GetAdjVertexFaces(candidateFace[v], candidateFace[(v+1)%3], indices); - ASSERT(indices.GetSize() < 2); + ASSERT(indices.size() < 2); indices.Empty(); GetAdjVertexFaces(candidateFace[v], candidateFace[(v+2)%3], indices); - ASSERT(indices.GetSize() < 2); + ASSERT(indices.size() < 2); #endif vertexFaces[candidateFace[v]].Insert(idxF); } - if (verts.GetSize() <= 3) + if (verts.size() <= 3) break; const VIndex idxV(verts.Find(candidateFace[1])); // remove all candidate face containing this vertex @@ -3829,11 +3514,11 @@ void Mesh::CloseHoleQuality(VertexIdxArr& verts) } } // insert the two new candidate faces - const VIndex idxB(idxV+verts.GetSize()); - const VIndex idxVB2(verts[(idxB-2)%verts.GetSize()]); - const VIndex idxVB1(verts[(idxB-1)%verts.GetSize()]); - const VIndex idxVF1(verts[(idxV+1)%verts.GetSize()]); - const VIndex idxVF2(verts[(idxV+2)%verts.GetSize()]); + const VIndex idxB(idxV+verts.size()); + const VIndex idxVB2(verts[(idxB-2)%verts.size()]); + const VIndex idxVB1(verts[(idxB-1)%verts.size()]); + const VIndex idxVF1(verts[(idxV+1)%verts.size()]); + const VIndex idxVF2(verts[(idxV+2)%verts.size()]); { const CandidateFace newCandidateFace(idxVB2, idxVB1, idxVF1, *this); if (newCandidateFace.aspectRatio >= 0) @@ -3952,11 +3637,11 @@ void Mesh::RemoveVertices(VertexIdxArr& vertexRemove, bool bUpdateLists) FOREACHPTR(pIdxF, vf) GetVertex(faces[*pIdxF], idxVM) = idxV; } - if (!vertexFaces.IsEmpty()) { + if (!vertexFaces.empty()) { facesRemove.Join(vertexFaces[idxV]); vertexFaces.RemoveAt(idxV); } - if (!vertexVertices.IsEmpty()) + if (!vertexVertices.empty()) vertexVertices.RemoveAt(idxV); vertices.RemoveAt(idxV); idxLast = idxV; @@ -3987,8 +3672,10 @@ void Mesh::ConvertTexturePerVertex(Mesh& mesh) const ASSERT(HasTexture()); mesh.vertices = vertices; mesh.faces.resize(faces.size()); - mesh.faceTexcoords.reserve(vertices.size()*3/2); mesh.faceTexcoords.resize(vertices.size()); + if (!faceTexindices.empty()) + mesh.faceTexindices.resize(vertices.size()); + VertexIdxArr mapVertices(vertices.size(), vertices.size()*3/2); mapVertices.Memset(0xff); FOREACH(idxF, faces) { @@ -3997,6 +3684,7 @@ void Mesh::ConvertTexturePerVertex(Mesh& mesh) const // with the same position, but different texture coordinates const Face& face = faces[idxF]; Face& newface = mesh.faces[idxF]; + const TexIndex ti = !faceTexindices.empty() ? faceTexindices[idxF] : 0; for (int i=0; i<3; ++i) { const TexCoord& tc = faceTexcoords[idxF*3+i]; VIndex idxV(face[i]); @@ -4005,6 +3693,8 @@ void Mesh::ConvertTexturePerVertex(Mesh& mesh) const if (idxVT == NO_ID) { // vertex seen for the first time, so just copy it mesh.faceTexcoords[newface[i] = idxVT = idxV] = tc; + if (!faceTexindices.empty()) + mesh.faceTexindices[newface[i] = idxVT = idxV] = ti; break; } // vertex already seen in an other face, check the texture coordinates @@ -4018,6 +3708,8 @@ void Mesh::ConvertTexturePerVertex(Mesh& mesh) const mapVertices.emplace_back(newface[i] = idxVT = mesh.vertices.size()); mesh.vertices.emplace_back(vertices[face[i]]); mesh.faceTexcoords.emplace_back(tc); + if (!faceTexindices.empty()) + mesh.faceTexindices.emplace_back(ti); break; } // continue with the next linked vertex which share the position, @@ -4026,7 +3718,7 @@ void Mesh::ConvertTexturePerVertex(Mesh& mesh) const } } } - mesh.textureDiffuse = textureDiffuse; + mesh.texturesDiffuse = texturesDiffuse; } // ConvertTexturePerVertex /*----------------------------------------------------------------*/ @@ -4105,7 +3797,7 @@ void Mesh::SamplePoints(REAL samplingDensity, PointCloud& pointcloud) const { // compute the total area to deduce the number of points const REAL area(ComputeArea()); - const unsigned theoreticNumberOfPoints((unsigned)CEIL2INT(area * samplingDensity)); + const unsigned theoreticNumberOfPoints(CEIL2INT(area * samplingDensity)); return SamplePoints(samplingDensity, theoreticNumberOfPoints, pointcloud); } void Mesh::SamplePoints(REAL samplingDensity, unsigned mumPointsTheoretic, PointCloud& pointcloud) const @@ -4168,8 +3860,9 @@ void Mesh::SamplePoints(REAL samplingDensity, unsigned mumPointsTheoretic, Point const TexCoord& TO = faceTexcoords[idxTexCoord+0]; const TexCoord& TA = faceTexcoords[idxTexCoord+1]; const TexCoord& TB = faceTexcoords[idxTexCoord+2]; + const TexIndex& TI = faceTexindices[idxFace]; const TexCoord xt(TO + static_cast(x)*(TA - TO) + static_cast(y)*(TB - TO)); - pointcloud.colors.emplace_back(textureDiffuse.sampleSafe(xt)); + pointcloud.colors.emplace_back(texturesDiffuse[TI].sampleSafe(xt)); } } } @@ -4192,7 +3885,7 @@ void Mesh::Project(const Camera& camera, DepthMap& depthMap) const } void Mesh::Project(const Camera& camera, DepthMap& depthMap, Image8U3& image) const { - ASSERT(!faceTexcoords.empty() && !textureDiffuse.empty()); + ASSERT(!faceTexcoords.empty() && !texturesDiffuse.empty()); struct RasterMesh : TRasterMesh { typedef TRasterMesh Base; const Mesh& mesh; @@ -4215,7 +3908,8 @@ void Mesh::Project(const Camera& camera, DepthMap& depthMap, Image8U3& image) co xt = mesh.faceTexcoords[idxFaceTex+0] * pbary[0]; xt += mesh.faceTexcoords[idxFaceTex+1] * pbary[1]; xt += mesh.faceTexcoords[idxFaceTex+2] * pbary[2]; - image(pt) = mesh.textureDiffuse.sampleSafe(xt); + const auto texIdx = mesh.faceTexindices[idxFaceTex / 3]; + image(pt) = mesh.texturesDiffuse[texIdx].sampleSafe(xt); } } }; @@ -4298,7 +3992,7 @@ void Mesh::ProjectOrtho(const Camera& camera, DepthMap& depthMap) const } void Mesh::ProjectOrtho(const Camera& camera, DepthMap& depthMap, Image8U3& image) const { - ASSERT(!faceTexcoords.empty() && !textureDiffuse.empty()); + ASSERT(!faceTexcoords.empty() && !texturesDiffuse.empty()); struct RasterMesh : TRasterMesh { typedef TRasterMesh Base; const Mesh& mesh; @@ -4324,7 +4018,8 @@ void Mesh::ProjectOrtho(const Camera& camera, DepthMap& depthMap, Image8U3& imag xt = mesh.faceTexcoords[idxFaceTex+0] * bary[0]; xt += mesh.faceTexcoords[idxFaceTex+1] * bary[1]; xt += mesh.faceTexcoords[idxFaceTex+2] * bary[2]; - image(pt) = mesh.textureDiffuse.sampleSafe(xt); + auto texIdx = mesh.faceTexindices[idxFaceTex / 3]; + image(pt) = mesh.texturesDiffuse[texIdx].sampleSafe(xt); } } }; @@ -4341,9 +4036,9 @@ void Mesh::ProjectOrtho(const Camera& camera, DepthMap& depthMap, Image8U3& imag // assuming the mesh is properly oriented, ortho-project it to a camera looking from top to down void Mesh::ProjectOrthoTopDown(unsigned resolution, Image8U3& image, Image8U& mask, Point3& center) const { - ASSERT(!IsEmpty() && !textureDiffuse.empty()); + ASSERT(!IsEmpty() && !texturesDiffuse.empty()); // initialize camera - const AABB3f box(vertices.Begin(), vertices.GetSize()); + const AABB3f box(vertices.data(), vertices.size()); const Point3 size(Vertex(box.GetSize())*1.01f/*border*/); center = Vertex(box.GetCenter()); Camera camera; @@ -4440,16 +4135,44 @@ Mesh Mesh::SubMesh(const FaceIdxArr& chunk) const Mesh mesh; mesh.vertices = vertices; mesh.faces.reserve(chunk.size()); - for (FIndex idxFace: chunk) + if (!faceTexcoords.empty()) + mesh.faceTexcoords.reserve(chunk.size()*3); + for (FIndex idxFace: chunk) { mesh.faces.emplace_back(faces[idxFace]); + if (!faceTexcoords.empty()) { + const TexCoord* tri = faceTexcoords.data()+idxFace*3; + for (int i = 0; i < 3; ++i) + mesh.faceTexcoords.emplace_back(tri[i]); + } + } mesh.ListIncidenteFaces(); mesh.RemoveUnreferencedVertices(); - // fix non-manifold vertices and edges mesh.FixNonManifold(); return mesh; } // SubMesh /*----------------------------------------------------------------*/ +// extract one sub-mesh for each texture, i.e. for each value of faceTexindices; +std::vector Mesh::SplitMeshPerTextureBlob() const { + + ASSERT(HasTexture()); + if (texturesDiffuse.size() == 1) + return {*this}; + ASSERT(faceTexindices.size() == faces.size()); + std::vector submeshes; + submeshes.reserve(texturesDiffuse.size()); + FOREACH(texId, texturesDiffuse) { + FaceIdxArr chunk; + FOREACH(idxFace, faceTexindices) { + if (faceTexindices[idxFace] == texId) + chunk.push_back(idxFace); + } + Mesh submesh = SubMesh(chunk); + submesh.texturesDiffuse.emplace_back(texturesDiffuse[texId]); + submeshes.emplace_back(std::move(submesh)); + } + return submeshes; +} // transfer the texture of this mesh to the new mesh; @@ -4465,15 +4188,22 @@ inline Eigen::AlignedBox3f bounding_box(const FaceBox& faceBox) { #endif bool Mesh::TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices, unsigned borderSize, unsigned textureSize) { - ASSERT(HasTexture() && mesh.HasTexture()); - if (mesh.textureDiffuse.empty()) - mesh.textureDiffuse.create(textureSize, textureSize); - Image8U mask(mesh.textureDiffuse.size(), uint8_t(255)); - const FIndex num_faces(faceSubsetIndices.empty() ? faces.size() : faceSubsetIndices.size()); + ASSERT(HasTexture() && mesh.HasTextureCoordinates()); + if (mesh.texturesDiffuse.empty()) { + // create the texture at specified resolution and + // scale the UV-coordinates to the new resolution (assuming normalized coordinates) + mesh.texturesDiffuse.emplace_back(textureSize, textureSize).memset(0); + for (TexCoord& tex: mesh.faceTexcoords) { + ASSERT(tex.x <= 1 && tex.y <= 1); + tex *= (Mesh::Type)textureSize; + } + } + Image8U mask(mesh.texturesDiffuse.back().size(), uint8_t(255)); + const FIndex num_faces(faceSubsetIndices.empty() ? mesh.faces.size() : faceSubsetIndices.size()); if (vertices == mesh.vertices && faces == mesh.faces) { // the two meshes are identical, only the texture coordinates are different; // directly transfer the texture onto the new coordinates - #ifndef MESH_USE_OPENMP + #ifdef MESH_USE_OPENMP #pragma omp parallel for schedule(dynamic) for (int_t i=0; i<(int_t)num_faces; ++i) { const FIndex idx((FIndex)i); @@ -4486,19 +4216,20 @@ bool Mesh::TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices, unsi Mesh& meshTrg; Image8U& mask; const TexCoord* tri; - inline cv::Size Size() const { return meshTrg.textureDiffuse.size(); } + const TexIndex texId; + inline cv::Size Size() const { return meshTrg.texturesDiffuse[0].size(); } inline void operator()(const ImageRef& pt, const Point3f& bary) { - ASSERT(meshTrg.textureDiffuse.isInside(pt)); + ASSERT(meshTrg.texturesDiffuse[texId].isInside(pt)); const TexCoord x(tri[0]*bary.x + tri[1]*bary.y + tri[2]*bary.z); - const Pixel8U color(meshRef.textureDiffuse.sample(x)); - meshTrg.textureDiffuse(pt) = color; + const Pixel8U color(meshRef.texturesDiffuse[texId].sample(x)); + meshTrg.texturesDiffuse[texId](pt) = color; mask(pt) = 0; } - } data{*this, mesh, mask, faceTexcoords.data()+idxFace*3}; + } data{*this, mesh, mask, faceTexcoords.data()+idxFace*3, mesh.faceTexindices[idxFace]}; // render triangle and for each pixel interpolate the color // from the triangle corners using barycentric coordinates const TexCoord* tri = mesh.faceTexcoords.data()+idxFace*3; - Image8U::RasterizeTriangleBary(tri[0], tri[1], tri[2], data); + Image8U::RasterizeTriangleBary(tri[0], tri[1], tri[2], data); } } else { // the two meshes are different, transfer the texture by finding the closest point @@ -4536,10 +4267,10 @@ bool Mesh::TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices, unsi inline void IntersectsRayFace(FIndex idxFace) { const Face& face = mesh.faces[idxFace]; Type dist; - if (ray.Intersects(Triangle3f(mesh.vertices[face[0]], mesh.vertices[face[1]], mesh.vertices[face[2]]), &dist)) { - ASSERT(dist >= 0); - if (pick.dist > dist) { - pick.dist = dist; + if (ray.Intersects(Triangle3f( + mesh.vertices[face.x], mesh.vertices[face.y], mesh.vertices[face.z]), &dist)) { + if (pick.dist > ABS(dist)) { + pick.dist = ABS(dist); pick.idx = idxFace; } } @@ -4555,12 +4286,10 @@ bool Mesh::TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices, unsi #endif }; #if USE_MESH_INT == USE_MESH_BF || USE_MESH_INT == USE_MESH_BVH - const float diagonal(GetAABB().GetSize().norm()); #elif USE_MESH_INT == USE_MESH_OCTREE const Octree octree(vertices, [](Octree::IDX_TYPE size, Octree::Type /*radius*/) { return size > 8; }); - const float diagonal(octree.GetAabb().GetSize().norm()); struct OctreeIntersectRayMesh : IntersectRayMesh { OctreeIntersectRayMesh(const Octree& octree, const Mesh& _mesh, const Ray3f& _ray) : IntersectRayMesh(_mesh, _ray) { @@ -4601,13 +4330,17 @@ bool Mesh::TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices, unsi Mesh& meshTrg; Image8U& mask; const Face& face; - float diagonal; - inline cv::Size Size() const { return meshTrg.textureDiffuse.size(); } + const TexIndex texId; + inline cv::Size Size() const { return meshTrg.texturesDiffuse.back().size(); } inline void operator()(const ImageRef& pt, const Point3f& bary) { - ASSERT(meshTrg.textureDiffuse.isInside(pt)); - const Vertex X(meshTrg.vertices[face[0]]*bary.x + meshTrg.vertices[face[1]]*bary.y + meshTrg.vertices[face[2]]*bary.z); - const Normal N(normalized(meshTrg.vertexNormals[face[0]]*bary.x + meshTrg.vertexNormals[face[1]]*bary.y + meshTrg.vertexNormals[face[2]]*bary.z)); - const Ray3f ray(Vertex(X+N*diagonal), Normal(-N)); + ASSERT(meshTrg.texturesDiffuse[texId].isInside(pt)); + const Vertex X(meshTrg.vertices[face.x]*bary.x + + meshTrg.vertices[face.y]*bary.y + + meshTrg.vertices[face.z]*bary.z); + const Normal N(normalized(meshTrg.vertexNormals[face.x]*bary.x + + meshTrg.vertexNormals[face.y]*bary.y + + meshTrg.vertexNormals[face.z]*bary.z)); + const Ray3f ray(X, N); #if USE_MESH_INT == USE_MESH_BF const IntersectRayMesh intRay(meshRef, ray); #elif USE_MESH_INT == USE_MESH_BVH @@ -4623,27 +4356,27 @@ bool Mesh::TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices, unsi const Vertex baryRef(CorrectBarycentricCoordinates(BarycentricCoordinatesUV(meshRef.vertices[refFace[0]], meshRef.vertices[refFace[1]], meshRef.vertices[refFace[2]], refX))); const TexCoord* tri = meshRef.faceTexcoords.data()+refIdxFace*3; const TexCoord x(tri[0]*baryRef.x + tri[1]*baryRef.y + tri[2]*baryRef.z); - const Pixel8U color(meshRef.textureDiffuse.sample(x)); - meshTrg.textureDiffuse(pt) = color; + const Pixel8U color(meshRef.texturesDiffuse[texId].sample(x)); + meshTrg.texturesDiffuse.back()(pt) = color; mask(pt) = 0; } } #if USE_MESH_INT == USE_MESH_BF - } data{*this, mesh, mask, mesh.faces[idxFace], diagonal}; + } data{*this, mesh, mask, mesh.faces[idxFace], mesh.GetFaceTextureIndex(idxFace)}; #elif USE_MESH_INT == USE_MESH_BVH - } data{tree, *this, mesh, mask, mesh.faces[idxFace], diagonal}; + } data{tree, *this, mesh, mask, mesh.faces[idxFace], mesh.GetFaceTextureIndex(idxFace)}; #else - } data{octree, *this, mesh, mask, mesh.faces[idxFace], diagonal}; + } data{octree, *this, mesh, mask, mesh.faces[idxFace], mesh.GetFaceTextureIndex(idxFace)}; #endif // render triangle and for each pixel interpolate the color // from the triangle corners using barycentric coordinates const TexCoord* tri = mesh.faceTexcoords.data()+idxFace*3; - Image8U::RasterizeTriangleBary(tri[0], tri[1], tri[2], data); + Image8U::RasterizeTriangleBary(tri[0], tri[1], tri[2], data); } } // fill border if (borderSize > 0) { - ASSERT(mask.size().area() == mesh.textureDiffuse.size().area()); + ASSERT(mask.size().area() == mesh.texturesDiffuse[0].size().area()); const int border(static_cast(borderSize)); CLISTDEF0(int) idx_valid_pixels; idx_valid_pixels.push_back(-1); @@ -4654,13 +4387,13 @@ bool Mesh::TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices, unsi idx_valid_pixels.push_back(i); Image32F dists; cv::Mat_ labels; cv::distanceTransform(mask, dists, labels, cv::DIST_L1, 3, cv::DIST_LABEL_PIXEL); - ASSERT(mesh.textureDiffuse.isContinuous()); + ASSERT(mesh.texturesDiffuse[0].isContinuous()); for (int i=0; i(dists(i)); if (dist > 0 && dist <= border) { const int label(labels(i)); const int idx_closest_pixel(idx_valid_pixels[label]); - mesh.textureDiffuse(i) = mesh.textureDiffuse(idx_closest_pixel); + mesh.texturesDiffuse[0](i) = mesh.texturesDiffuse[0](idx_closest_pixel); } } } diff --git a/libs/MVS/Mesh.h b/libs/MVS/Mesh.h index 214e2f21c..648504994 100644 --- a/libs/MVS/Mesh.h +++ b/libs/MVS/Mesh.h @@ -57,22 +57,26 @@ class MVS_API Mesh typedef TPoint3 Face; typedef uint32_t FIndex; - typedef cList VertexArr; - typedef cList FaceArr; + typedef SEACAVE::cList VertexArr; + typedef SEACAVE::cList FaceArr; - typedef cList VertexIdxArr; - typedef cList FaceIdxArr; - typedef cList VertexVerticesArr; - typedef cList VertexFacesArr; + typedef SEACAVE::cList VertexIdxArr; + typedef SEACAVE::cList FaceIdxArr; + typedef SEACAVE::cList VertexVerticesArr; + typedef SEACAVE::cList VertexFacesArr; typedef TPoint3 Normal; - typedef cList NormalArr; + typedef SEACAVE::cList NormalArr; typedef TPoint2 TexCoord; - typedef cList TexCoordArr; + typedef SEACAVE::cList TexCoordArr; + + typedef uint8_t TexIndex; + typedef SEACAVE::cList TexIndexArr; + typedef SEACAVE::cList Image8U3Arr; typedef TPoint3 FaceFaces; - typedef cList FaceFacesArr; + typedef SEACAVE::cList FaceFacesArr; // used to find adjacent face struct FaceCount { @@ -114,7 +118,7 @@ class MVS_API Mesh FaceIdxArr faces; Box box; }; - typedef cList FacesChunkArr; + typedef SEACAVE::cList FacesChunkArr; public: VertexArr vertices; @@ -122,14 +126,15 @@ class MVS_API Mesh NormalArr vertexNormals; // for each vertex, the normal to the surface in that point (optional) VertexVerticesArr vertexVertices; // for each vertex, the list of adjacent vertices (optional) - VertexFacesArr vertexFaces; // for each vertex, the list of faces containing it (optional) + VertexFacesArr vertexFaces; // for each vertex, the ordered list of faces containing it (optional) BoolArr vertexBoundary; // for each vertex, stores if it is at the boundary or not (optional) NormalArr faceNormals; // for each face, the normal to it (optional) FaceFacesArr faceFaces; // for each face, the list of adjacent faces, NO_ID for border edges (optional) - TexCoordArr faceTexcoords; // for each face, the texture-coordinates corresponding to the contained vertices (optional) + TexCoordArr faceTexcoords; // for each face, the texture-coordinates corresponding to its vertices, 3x num faces OR for each vertex (optional) + TexIndexArr faceTexindices; // for each face, the corresponding index of the texture (optional) - Image8U3 textureDiffuse; // texture containing the diffuse color (optional) + Image8U3Arr texturesDiffuse; // textures containing the diffuse color (optional) #ifdef _USE_CUDA static CUDA::KernelRT kernelComputeFaceNormal; @@ -147,9 +152,11 @@ class MVS_API Mesh void EmptyExtra(); void Swap(Mesh&); void Join(const Mesh&); - inline bool IsEmpty() const { return vertices.empty(); } + bool IsEmpty() const { return vertices.empty(); } bool IsWatertight(); - inline bool HasTexture() const { ASSERT(faceTexcoords.empty() == textureDiffuse.empty()); return !faceTexcoords.empty(); } + bool HasTexture() const { return HasTextureCoordinates() && !texturesDiffuse.empty(); } + bool HasTextureCoordinates() const { ASSERT(faceTexcoords.empty() || faces.size()*3 == faceTexcoords.size() || vertices.size() == faceTexcoords.size()); return !faceTexcoords.empty(); } + bool HasTextureCoordinatesPerVertex() const { return !faceTexcoords.empty() && vertices.size() == faceTexcoords.size(); } Box GetAABB() const; Box GetAABB(const Box& bound) const; @@ -167,10 +174,12 @@ class MVS_API Mesh void GetEdgeFaces(VIndex, VIndex, FaceIdxArr&) const; void GetFaceFaces(FIndex, FaceIdxArr&) const; void GetEdgeVertices(FIndex, FIndex, uint32_t vs0[2], uint32_t vs1[2]) const; + bool GetEdgeOrientation(FIndex, VIndex, VIndex) const; + FIndex GetEdgeAdjacentFace(FIndex, VIndex, VIndex) const; void GetAdjVertices(VIndex, VertexIdxArr&) const; void GetAdjVertexFaces(VIndex, VIndex, FaceIdxArr&) const; - bool FixNonManifold(); + unsigned FixNonManifold(float magDisplacementDuplicateVertices = 0.01f, VertexIdxArr* duplicatedVertices = NULL); void Clean(float fDecimate=0.7f, float fSpurious=10.f, bool bRemoveSpikes=true, unsigned nCloseHoles=30, unsigned nSmoothMesh=2, float fEdgeLength=0, bool bLastClean=true); void EnsureEdgeSize(float minEdge=-0.5f, float maxEdge=-4.f, float collapseRatio=0.2, float degenerate_angle_deg=150, int mode=1, int max_iters=50); @@ -184,8 +193,10 @@ class MVS_API Mesh void RemoveFaces(FaceIdxArr& facesRemove, bool bUpdateLists=false); void RemoveVertices(VertexIdxArr& vertexRemove, bool bUpdateLists=false); VIndex RemoveUnreferencedVertices(bool bUpdateLists=false); + std::vector SplitMeshPerTextureBlob() const; void ConvertTexturePerVertex(Mesh&) const; + TexIndex GetFaceTextureIndex(FIndex idxF) const { return faceTexindices.empty() ? 0 : faceTexindices[idxF]; } void FaceTexcoordsNormalize(TexCoordArr& newFaceTexcoords, bool flipY=true) const; void FaceTexcoordsUnnormalize(TexCoordArr& newFaceTexcoords, bool flipY=true) const; @@ -223,7 +234,7 @@ class MVS_API Mesh bool Split(FacesChunkArr&, float maxArea); Mesh SubMesh(const FaceIdxArr& faces) const; - bool TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices={}, unsigned borderSize=3, unsigned textureSize=1024); + bool TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices={}, unsigned borderSize=3, unsigned textureSize=4096); // file IO bool Load(const String& fileName); @@ -240,7 +251,7 @@ class MVS_API Mesh bool LoadOBJ(const String& fileName); bool LoadGLTF(const String& fileName, bool bBinary=true); - bool SavePLY(const String& fileName, const cList& comments=cList(), bool bBinary=true) const; + bool SavePLY(const String& fileName, const cList& comments=cList(), bool bBinary=true, bool bTexLossless=true) const; bool SaveOBJ(const String& fileName) const; bool SaveGLTF(const String& fileName, bool bBinary=true) const; @@ -261,7 +272,8 @@ class MVS_API Mesh ar & vertexBoundary; ar & faceNormals; ar & faceTexcoords; - ar & textureDiffuse; + ar & faceTexindices; + ar & texturesDiffuse; } #endif }; diff --git a/libs/MVS/PatchMatchCUDA.cpp b/libs/MVS/PatchMatchCUDA.cpp index 1a6c95f99..814e73e9c 100644 --- a/libs/MVS/PatchMatchCUDA.cpp +++ b/libs/MVS/PatchMatchCUDA.cpp @@ -363,7 +363,7 @@ void PatchMatchCUDA::EstimateDepthMap(DepthData& depthData) const int index = r * depthData.depthMap.cols + c; const Point4& depthNormal = depthNormalEstimates[index]; const Depth depth = depthNormal.w(); - ASSERT(std::isfinite(depth)); + ASSERT(ISFINITE(depth)); depthData.depthMap(r, c) = depth; depthData.normalMap(r, c) = depthNormal.topLeftCorner<3, 1>(); if (scaleNumber == 0) { diff --git a/libs/MVS/PointCloud.cpp b/libs/MVS/PointCloud.cpp index 2920b8978..c62920961 100644 --- a/libs/MVS/PointCloud.cpp +++ b/libs/MVS/PointCloud.cpp @@ -226,46 +226,95 @@ Planef PointCloud::EstimateGroundPlane(const ImageArr& images, float planeThresh // define a PLY file format composed only of vertices +namespace PointCloudInternal { namespace BasicPLY { - typedef PointCloud::Point Point; - typedef PointCloud::Color Color; - typedef PointCloud::Normal Normal; - // list of property information for a vertex - struct PointColNormal { - Point p; - Color c; - Normal n; - float scale; - float confidence; - }; - static const PLY::PlyProperty vert_props[] = { - {"x", PLY::Float32, PLY::Float32, offsetof(PointColNormal,p.x), 0, 0, 0, 0}, - {"y", PLY::Float32, PLY::Float32, offsetof(PointColNormal,p.y), 0, 0, 0, 0}, - {"z", PLY::Float32, PLY::Float32, offsetof(PointColNormal,p.z), 0, 0, 0, 0}, - {"red", PLY::Uint8, PLY::Uint8, offsetof(PointColNormal,c.r), 0, 0, 0, 0}, - {"green", PLY::Uint8, PLY::Uint8, offsetof(PointColNormal,c.g), 0, 0, 0, 0}, - {"blue", PLY::Uint8, PLY::Uint8, offsetof(PointColNormal,c.b), 0, 0, 0, 0}, - {"nx", PLY::Float32, PLY::Float32, offsetof(PointColNormal,n.x), 0, 0, 0, 0}, - {"ny", PLY::Float32, PLY::Float32, offsetof(PointColNormal,n.y), 0, 0, 0, 0}, - {"nz", PLY::Float32, PLY::Float32, offsetof(PointColNormal,n.z), 0, 0, 0, 0}, - {"scale", PLY::Float32, PLY::Float32, offsetof(PointColNormal,scale), 0, 0, 0, 0}, - {"confidence", PLY::Float32, PLY::Float32, offsetof(PointColNormal,confidence), 0, 0, 0, 0} - }; // list of the kinds of elements in the PLY static const char* elem_names[] = { "vertex" }; + // list of property information for a vertex + struct Vertex { + PointCloud::Point p; + PointCloud::Color c; + PointCloud::Normal n; + struct Views { + uint8_t num; + uint32_t* pIndices; + float* pWeights; + } views; + float confidence; + float scale; + static void InitLoadProps(PLY& ply, int elem_count, + PointCloud::PointArr& points, PointCloud::ColorArr& colors, PointCloud::NormalArr& normals, PointCloud::PointViewArr& views, PointCloud::PointWeightArr& weights) + { + PLY::PlyElement* elm = ply.find_element(elem_names[0]); + const size_t nMaxProps(SizeOfArray(props)); + for (size_t p=0; p indices(rects.GetSize()); - std::iota(indices.Begin(), indices.End(), 0); - RectArr newRects(rects.GetSize()); - while (!rects.IsEmpty()) { + RectWIdxArr placedRects; + while (!unplacedRects.IsEmpty()) { int bestScore1 = std::numeric_limits::max(); int bestScore2 = std::numeric_limits::max(); IDX bestRectIndex = NO_IDX; @@ -108,9 +106,9 @@ bool MaxRectsBinPack::Insert(RectArr& rects, FreeRectChoiceHeuristic method) IDX privBestRectIndex = NO_IDX; Rect privBestNode; #pragma omp for nowait - for (int_t i=0; i<(int_t)rects.GetSize(); ++i) { + for (int_t i=0; i<(int_t)unplacedRects.size(); ++i) { int score1, score2; - Rect newNode(ScoreRect(rects[i].width, rects[i].height, method, score1, score2)); + Rect newNode(ScoreRect(unplacedRects[i].rect.width, unplacedRects[i].rect.height, method, score1, score2)); if (score1 < privBestScore1 || (score1 == privBestScore1 && score2 < privBestScore2)) { privBestScore1 = score1; privBestScore2 = score2; @@ -129,9 +127,9 @@ bool MaxRectsBinPack::Insert(RectArr& rects, FreeRectChoiceHeuristic method) } } #else - FOREACH(i, rects) { + FOREACH(i, unplacedRects) { int score1, score2; - Rect newNode(ScoreRect(rects[i].width, rects[i].height, method, score1, score2)); + Rect newNode(ScoreRect(unplacedRects[i].rect.width, unplacedRects[i].rect.height, method, score1, score2)); if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) { bestScore1 = score1; bestScore2 = score2; @@ -141,24 +139,18 @@ bool MaxRectsBinPack::Insert(RectArr& rects, FreeRectChoiceHeuristic method) } #endif - // if no place found... + // if no place found, return the placed rectangles list if (bestRectIndex == NO_IDX) { - // restore the original rectangles - FOREACH(j, rects) - newRects[indices[j]] = rects[j]; - rects.Swap(newRects); - return false; + break; } // store rectangle PlaceRect(bestNode); - newRects[indices[bestRectIndex]] = bestNode; - rects.RemoveAt(bestRectIndex); - indices.RemoveAt(bestRectIndex); + placedRects.Insert(RectWIdx{bestNode, unplacedRects[bestRectIndex].patchIdx}); + unplacedRects.RemoveAt(bestRectIndex); } - rects.Swap(newRects); - return true; + return placedRects; } void MaxRectsBinPack::PlaceRect(const Rect &node) @@ -524,6 +516,13 @@ int MaxRectsBinPack::ComputeTextureSize(const RectArr& rects, int mult) // ... as power of two return POWI(2, CEIL2INT(LOGN((float)sizeTex) / LOGN(2.f))); } + +int MaxRectsBinPack::ComputeTextureSize(const RectWIdxArr& rectsWIdx, int mult) { + RectArr rects(rectsWIdx.GetSize()); + FOREACH(i, rectsWIdx) + rects[i] = rectsWIdx[i].rect; + return ComputeTextureSize(rects, mult); +} /*----------------------------------------------------------------*/ @@ -563,7 +562,7 @@ void GuillotineBinPack::Init(int width, int height) freeRectangles.push_back(n); } -bool GuillotineBinPack::Insert(RectArr& rects, bool merge, +GuillotineBinPack::RectWIdxArr GuillotineBinPack::Insert(RectWIdxArr& unplacedRects, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod) { // Remember variables about the best packing choice we have made so far during the iteration process. @@ -571,19 +570,18 @@ bool GuillotineBinPack::Insert(RectArr& rects, bool merge, size_t bestRect = 0; bool bestFlipped = false; - // Pack rectangles one at a time until we have cleared the rects array of all rectangles. - // rects will get destroyed in the process. - cList indices(rects.GetSize()); - std::iota(indices.Begin(), indices.End(), 0); - RectArr newRects(rects.GetSize()); - while (!rects.IsEmpty()) { + // Pack rectangles one at a time until we have cleared the rects array of all rectangles or there is no space. + // unplacedRects will get destroyed in the process. + RectWIdxArr placedRects; + while (!unplacedRects.IsEmpty()) { // Stores the penalty score of the best rectangle placement - bigger=worse, smaller=better. int bestScore = std::numeric_limits::max(); for (size_t i = 0; i < freeRectangles.size(); ++i) { - for (size_t j = 0; j < rects.GetSize(); ++j) { + for (size_t j = 0; j < unplacedRects.GetSize(); ++j) { + Rect currentRect = unplacedRects[j].rect; // If this rectangle is a perfect match, we pick it instantly. - if (rects[j].width == freeRectangles[i].width && rects[j].height == freeRectangles[i].height) { + if (currentRect.width == freeRectangles[i].width && currentRect.height == freeRectangles[i].height) { bestFreeRect = i; bestRect = j; bestFlipped = false; @@ -592,7 +590,7 @@ bool GuillotineBinPack::Insert(RectArr& rects, bool merge, break; } // If flipping this rectangle is a perfect match, pick that then. - else if (rects[j].height == freeRectangles[i].width && rects[j].width == freeRectangles[i].height) { + else if (currentRect.height == freeRectangles[i].width && currentRect.width == freeRectangles[i].height) { bestFreeRect = i; bestRect = j; bestFlipped = true; @@ -601,8 +599,8 @@ bool GuillotineBinPack::Insert(RectArr& rects, bool merge, break; } // Try if we can fit the rectangle upright. - else if (rects[j].width <= freeRectangles[i].width && rects[j].height <= freeRectangles[i].height) { - int score = ScoreByHeuristic(rects[j].width, rects[j].height, freeRectangles[i], rectChoice); + else if (currentRect.width <= freeRectangles[i].width && currentRect.height <= freeRectangles[i].height) { + int score = ScoreByHeuristic(currentRect.width, currentRect.height, freeRectangles[i], rectChoice); if (score < bestScore) { bestFreeRect = i; bestRect = j; @@ -611,8 +609,8 @@ bool GuillotineBinPack::Insert(RectArr& rects, bool merge, } } // If not, then perhaps flipping sideways will make it fit? - else if (rects[j].height <= freeRectangles[i].width && rects[j].width <= freeRectangles[i].height) { - int score = ScoreByHeuristic(rects[j].height, rects[j].width, freeRectangles[i], rectChoice); + else if (currentRect.height <= freeRectangles[i].width && currentRect.width <= freeRectangles[i].height) { + int score = ScoreByHeuristic(currentRect.height, currentRect.width, freeRectangles[i], rectChoice); if (score < bestScore) { bestFreeRect = i; bestRect = j; @@ -625,19 +623,15 @@ bool GuillotineBinPack::Insert(RectArr& rects, bool merge, // If we didn't manage to find any rectangle to pack, abort. if (bestScore == std::numeric_limits::max()) { - // restore the original rectangles - FOREACH(j, rects) - newRects[indices[j]] = rects[j]; - rects.Swap(newRects); - return false; + break; } // Otherwise, we're good to go and do the actual packing. Rect newNode; newNode.x = freeRectangles[bestFreeRect].x; newNode.y = freeRectangles[bestFreeRect].y; - newNode.width = rects[bestRect].width; - newNode.height = rects[bestRect].height; + newNode.width = unplacedRects[bestRect].rect.width; + newNode.height = unplacedRects[bestRect].rect.height; if (bestFlipped) std::swap(newNode.width, newNode.height); @@ -647,9 +641,8 @@ bool GuillotineBinPack::Insert(RectArr& rects, bool merge, freeRectangles.erase(freeRectangles.begin() + bestFreeRect); // Remove the rectangle we just packed from the input list. - newRects[indices[bestRect]] = newNode; - rects.RemoveAt(bestRect); - indices.RemoveAt(bestRect); + placedRects.Insert(MaxRectsBinPack::RectWIdx{newNode, unplacedRects[bestRect].patchIdx}); + unplacedRects.RemoveAt(bestRect); // Perform a Rectangle Merge step if desired. if (merge) @@ -661,7 +654,7 @@ bool GuillotineBinPack::Insert(RectArr& rects, bool merge, // Check that we're really producing correct packings here. ASSERT(disjointRects.Add(newNode) == true); } - return true; + return placedRects; } GuillotineBinPack::Rect GuillotineBinPack::Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod) @@ -984,12 +977,10 @@ void SkylineBinPack::Init(int width, int height, bool useWasteMap_) } } -bool SkylineBinPack::Insert(RectArr& rects, LevelChoiceHeuristic method) +SkylineBinPack::RectWIdxArr SkylineBinPack::Insert(RectWIdxArr& unplacedRects, LevelChoiceHeuristic method) { - cList indices(rects.GetSize()); - std::iota(indices.Begin(), indices.End(), 0); - RectArr newRects(rects.GetSize()); - while (!rects.IsEmpty()) { + RectWIdxArr placedRects; + while (!unplacedRects.IsEmpty()) { int bestScore1 = std::numeric_limits::max(); int bestScore2 = std::numeric_limits::max(); int bestSkylineIndex = -1; @@ -1005,9 +996,9 @@ bool SkylineBinPack::Insert(RectArr& rects, LevelChoiceHeuristic method) IDX privBestRectIndex = NO_IDX; Rect privBestNode; #pragma omp for nowait - for (int_t i=0; i<(int_t)rects.GetSize(); ++i) { + for (int_t i=0; i<(int_t)unplacedRects.GetSize(); ++i) { int score1, score2, index; - Rect newNode(ScoreRect(rects[i].width, rects[i].height, method, score1, score2, index)); + Rect newNode(ScoreRect(unplacedRects[i].rect.width, unplacedRects[i].rect.height, method, score1, score2, index)); if (score1 < privBestScore1 || (score1 == privBestScore1 && score2 < privBestScore2)) { privBestScore1 = score1; privBestScore2 = score2; @@ -1028,9 +1019,9 @@ bool SkylineBinPack::Insert(RectArr& rects, LevelChoiceHeuristic method) } } #else - FOREACH(i, rects) { + FOREACH(i, unplacedRects) { int score1, score2, index; - Rect newNode(ScoreRect(rects[i].width, rects[i].height, method, score1, score2, index)); + Rect newNode(ScoreRect(unplacedRects[i].rect.width, unplacedRects[i].rect.height, method, score1, score2, index)); if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) { bestNode = newNode; bestScore1 = score1; @@ -1041,13 +1032,9 @@ bool SkylineBinPack::Insert(RectArr& rects, LevelChoiceHeuristic method) } #endif - // if no place found... + // if no place found, give up if (bestRectIndex == NO_IDX) { - // restore the original rectangles - FOREACH(j, rects) - newRects[indices[j]] = rects[j]; - rects.Swap(newRects); - return false; + break; } // Perform the actual packing. @@ -1056,14 +1043,12 @@ bool SkylineBinPack::Insert(RectArr& rects, LevelChoiceHeuristic method) disjointRects.Add(bestNode); #endif AddSkylineLevel(bestSkylineIndex, bestNode); - usedSurfaceArea += rects[bestRectIndex].area(); + usedSurfaceArea += unplacedRects[bestRectIndex].rect.area(); - newRects[indices[bestRectIndex]] = bestNode; - rects.RemoveAt(bestRectIndex); - indices.RemoveAt(bestRectIndex); + placedRects.Insert(MaxRectsBinPack::RectWIdx{bestNode, unplacedRects[bestRectIndex].patchIdx}); + unplacedRects.RemoveAt(bestRectIndex); } - rects.Swap(newRects); - return true; + return placedRects; } SkylineBinPack::Rect SkylineBinPack::Insert(int width, int height, LevelChoiceHeuristic method) diff --git a/libs/MVS/RectsBinPack.h b/libs/MVS/RectsBinPack.h index 390ec1a9f..c1ef9b048 100644 --- a/libs/MVS/RectsBinPack.h +++ b/libs/MVS/RectsBinPack.h @@ -57,8 +57,17 @@ namespace MVS { class MaxRectsBinPack { public: + // A simple rectangle typedef cv::Rect Rect; + // A rectangle that stores an index of origin + typedef struct { + Rect rect; + uint32_t patchIdx; + } RectWIdx; + /// A list of rectangles typedef CLISTDEF0(Rect) RectArr; + /// A list of rectangles along their original indices + typedef CLISTDEF0(RectWIdx) RectWIdxArr; /// Instantiates a bin of size (0,0). Call Init to create a new bin. MaxRectsBinPack(); @@ -84,7 +93,7 @@ class MaxRectsBinPack /// @param rects [IN/OUT] The list of rectangles to insert; the rectangles will be modified with the new coordinates in the process. /// @param method The rectangle placement rule to use when packing. /// returns true if all rectangles were inserted - bool Insert(RectArr& rects, FreeRectChoiceHeuristic method=RectBestShortSideFit); + RectWIdxArr Insert(RectWIdxArr& rects, FreeRectChoiceHeuristic method=RectBestShortSideFit); /// Inserts a single rectangle into the bin, possibly rotated. Rect Insert(int width, int height, FreeRectChoiceHeuristic method=RectBestShortSideFit); @@ -94,6 +103,7 @@ class MaxRectsBinPack /// Computes an approximate texture atlas size. static int ComputeTextureSize(const RectArr& rects, int mult=0); + static int ComputeTextureSize(const RectWIdxArr& rects, int mult=0); /// Returns true if a is contained/on the border in b. static inline bool IsContainedIn(const Rect& a, const Rect& b) { @@ -190,8 +200,12 @@ class DisjointRectCollection class GuillotineBinPack { public: + // A simple rectangle typedef cv::Rect Rect; + /// A list of rectangles typedef CLISTDEF0(Rect) RectArr; + /// A list of rectangles along their original indices + typedef CLISTDEF0(MaxRectsBinPack::RectWIdx) RectWIdxArr; /// The initial bin size will be (0,0). Call Init to set the bin size. GuillotineBinPack(); @@ -243,7 +257,7 @@ class GuillotineBinPack /// @param merge If true, performs Rectangle Merge operations during the packing process. /// @param rectChoice The free rectangle choice heuristic rule to use. /// @param splitMethod The free rectangle split heuristic rule to use. - bool Insert(RectArr& rects, bool merge, + RectWIdxArr Insert(RectWIdxArr& rects, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod); /// Computes the ratio of used/total surface area. 0.00 means no space is yet used, 1.00 means the whole bin is used. @@ -311,8 +325,12 @@ class GuillotineBinPack class SkylineBinPack { public: + // A simple rectangle typedef cv::Rect Rect; + /// A list of rectangles typedef CLISTDEF0(Rect) RectArr; + /// A list of rectangles along their original indices + typedef CLISTDEF0(MaxRectsBinPack::RectWIdx) RectWIdxArr; /// Instantiates a bin of size (0,0). Call Init to create a new bin. SkylineBinPack(); @@ -335,7 +353,7 @@ class SkylineBinPack /// Inserts the given list of rectangles in an offline/batch mode, possibly rotated. /// @param rects [in/out] The list of rectangles to insert. This vector will be update in the process. /// @param method The rectangle placement rule to use when packing. - bool Insert(RectArr& rects, LevelChoiceHeuristic method); + RectWIdxArr Insert(RectWIdxArr& rects, LevelChoiceHeuristic method); /// Inserts a single rectangle into the bin, possibly rotated. Rect Insert(int width, int height, LevelChoiceHeuristic method); diff --git a/libs/MVS/Scene.cpp b/libs/MVS/Scene.cpp index 165d46ebe..3cc319dc5 100644 --- a/libs/MVS/Scene.cpp +++ b/libs/MVS/Scene.cpp @@ -31,8 +31,6 @@ #include "Common.h" #include "Scene.h" -#define _USE_OPENCV -#include "Interface.h" #include "../Math/SimilarityTransform.h" using namespace MVS; @@ -156,6 +154,9 @@ bool Scene::LoadInterface(const String & fileName) return false; } imageData.UpdateCamera(platforms); + // init neighbors + imageData.neighbors.CopyOf(image.viewScores.data(), (uint32_t)image.viewScores.size()); + imageData.avgDepth = image.avgDepth; ++nCalibratedImages; nTotalPixels += imageData.width * imageData.height; DEBUG_ULTIMATE("Image loaded %3u: %s", ID, Util::getFileNameExt(imageData.name).c_str()); @@ -259,6 +260,8 @@ bool Scene::SaveInterface(const String & fileName, int version) const if (!platform.cameras[image.cameraID].HasResolution()) platform.SetFullK(image.cameraID, imageData.camera.K, imageData.width, imageData.height); } + image.viewScores = std::vector(imageData.neighbors.begin(), imageData.neighbors.end()); + image.avgDepth = imageData.avgDepth; } // export 3D points @@ -445,7 +448,7 @@ bool Scene::LoadViewNeighbors(const String& fileName) FOREACH(i, imageData.neighbors) { const IIndex nID(String::FromString(argv[i+1], NO_ID)); ASSERT(nID != NO_ID); - imageData.neighbors[i] = ViewScore{ViewInfo{nID, 0, 1.f, FD2R(15.f), 0.5f}, 3.f}; + imageData.neighbors[i] = ViewScore{nID, 0, 1.f, FD2R(15.f), 0.5f, 3.f}; } } @@ -467,7 +470,7 @@ bool Scene::SaveViewNeighbors(const String& fileName) const const Image& imageData = images[ID]; file.print("%u", ID); for (const ViewScore& neighbor: imageData.neighbors) - file.print(" %u", neighbor.idx.ID); + file.print(" %u", neighbor.ID); file.print("\n"); } @@ -500,7 +503,7 @@ bool Scene::Import(const String& fileName) DEBUG_EXTRA("error: invalid PLY file"); return false; } - for (int i = 0; i < (int)ply.elems.size(); ++i) { + for (int i = 0; i < ply.get_elements_count(); ++i) { int elem_count; LPCSTR elem_name = ply.setup_element_read(i, &elem_count); if (PLY::equal_strings("vertex", elem_name)) { @@ -520,7 +523,7 @@ bool Scene::Import(const String& fileName) } // Import /*----------------------------------------------------------------*/ -bool Scene::Load(const String& fileName, bool bImport) +Scene::SCENE_TYPE Scene::Load(const String& fileName, bool bImport) { TD_TIMER_STARTD(); Release(); @@ -529,25 +532,25 @@ bool Scene::Load(const String& fileName, bool bImport) // open the input stream std::ifstream fs(fileName, std::ios::in | std::ios::binary); if (!fs.is_open()) - return false; + return SCENE_NA; // load project header ID char szHeader[4]; fs.read(szHeader, 4); if (!fs || _tcsncmp(szHeader, PROJECT_ID, 4) != 0) { fs.close(); if (bImport && Import(fileName)) - return true; + return SCENE_IMPORT; if (LoadInterface(fileName)) - return true; + return SCENE_INTERFACE; VERBOSE("error: invalid project"); - return false; + return SCENE_NA; } // load project version uint32_t nVer; fs.read((char*)&nVer, sizeof(uint32_t)); if (!fs || nVer != PROJECT_VER) { VERBOSE("error: different project version"); - return false; + return SCENE_NA; } // load stream type uint32_t nType; @@ -557,7 +560,7 @@ bool Scene::Load(const String& fileName, bool bImport) fs.read((char*)&nReserved, sizeof(uint64_t)); // serialize in the current state if (!SerializeLoad(*this, fs, (ARCHIVE_TYPE)nType)) - return false; + return SCENE_NA; // init images nCalibratedImages = 0; size_t nTotalPixels(0); @@ -575,9 +578,13 @@ bool Scene::Load(const String& fileName, bool bImport) TD_TIMER_GET_FMT().c_str(), images.GetSize(), nCalibratedImages, (double)nTotalPixels/(1024.0*1024.0), (double)nTotalPixels/(1024.0*1024.0*nCalibratedImages), pointcloud.points.GetSize(), mesh.vertices.GetSize(), mesh.faces.GetSize()); - return true; + return SCENE_MVS; #else - return false; + if (bImport && Import(fileName)) + return SCENE_IMPORT; + if (LoadInterface(fileName)) + return SCENE_INTERFACE; + return SCENE_NA; #endif } // Load @@ -706,7 +713,7 @@ bool Scene::ExportMeshToDepthMaps(const String& baseName) IIndexArr IDs(0, image.neighbors.size()+1); IDs.push_back(idxImage); for (const ViewScore& neighbor: image.neighbors) - IDs.push_back(neighbor.idx.ID); + IDs.push_back(neighbor.ID); return ExportDepthDataRaw(fileName, image.name, IDs, image.GetSize(), image.camera.K, image.camera.R, image.camera.C, 0.001f, FLT_MAX, depthMap, normalMap, ConfidenceMap(), ViewsMap()); } ()) || (nType == 1 && !depthMap.Save(fileName)) || @@ -899,20 +906,22 @@ bool Scene::SelectNeighborViews(uint32_t ID, IndexArr& points, unsigned nMinView projs.Empty(); // store image score ViewScore& neighbor = neighbors.AddEmpty(); - neighbor.idx.ID = IDB; - neighbor.idx.points = score.points; - neighbor.idx.scale = score.avgScale/score.points; - neighbor.idx.angle = score.avgAngle/score.points; - neighbor.idx.area = area; + neighbor.ID = IDB; + neighbor.points = score.points; + neighbor.scale = score.avgScale/score.points; + neighbor.angle = score.avgAngle/score.points; + neighbor.area = area; neighbor.score = score.score*MAXF(area,0.01f); } - neighbors.Sort(); + neighbors.Sort([](const ViewScore& i, const ViewScore& j) { + return i.score > j.score; + }); #if TD_VERBOSE != TD_VERBOSE_OFF // print neighbor views if (VERBOSITY_LEVEL > 2) { String msg; FOREACH(n, neighbors) - msg += String::FormatString(" %3u(%upts,%.2fscl)", neighbors[n].idx.ID, neighbors[n].idx.points, neighbors[n].idx.scale); + msg += String::FormatString(" %3u(%upts,%.2fscl)", neighbors[n].ID, neighbors[n].points, neighbors[n].scale); VERBOSE("Reference image %3u sees %u views:%s (%u shared points)", ID, neighbors.size(), msg.c_str(), nPoints); } #endif @@ -926,9 +935,8 @@ bool Scene::SelectNeighborViews(uint32_t ID, IndexArr& points, unsigned nMinView void Scene::SelectNeighborViews(unsigned nMinViews, unsigned nMinPointViews, float fOptimAngle, unsigned nInsideROI) { - #ifdef DENSE_USE_OPENMP - #pragma omp parallel for shared(data, bAbort) - for (int_t ID=0; ID<(int_t)images.GetSize(); ++ID) { + #ifdef SCENE_USE_OPENMP + for (int_t ID=0; ID<(int_t)images.size(); ++ID) { const IIndex idxImage((IIndex)ID); #else FOREACH(idxImage, images) { @@ -949,9 +957,9 @@ bool Scene::FilterNeighborViews(ViewScoreArr& neighbors, float fMinArea, float f RFOREACH(n, neighbors) { const ViewScore& neighbor = neighbors[n]; if (neighbors.size() > nMinViews && - (neighbor.idx.area < fMinArea || - !ISINSIDE(neighbor.idx.scale, fMinScale, fMaxScale) || - !ISINSIDE(neighbor.idx.angle, fMinAngle, fMaxAngle))) + (neighbor.area < fMinArea || + !ISINSIDE(neighbor.scale, fMinScale, fMaxScale) || + !ISINSIDE(neighbor.angle, fMinAngle, fMaxAngle))) neighbors.RemoveAtMove(n); } if (neighbors.size() > nMaxViews) @@ -1389,13 +1397,13 @@ bool Scene::ExportChunks(const ImagesChunkArr& chunks, const String& path, ARCHI for (Image& image: subset.images) { RFOREACH(i, image.neighbors) { ViewScore& neighbor = image.neighbors[i]; - const auto itImage(mapImages.find(neighbor.idx.ID)); + const auto itImage(mapImages.find(neighbor.ID)); if (itImage == mapImages.end()) { image.neighbors.RemoveAtMove(i); continue; } ASSERT(itImage->second < subset.images.size()); - neighbor.idx.ID = itImage->second; + neighbor.ID = itImage->second; } } // extract point-cloud @@ -1742,7 +1750,7 @@ bool Scene::ComputeTowerCylinder(Point2f& centerPoint, float& fRadius, float& fR { // disregard tower mode for scenes with less than 20 cameras if (towerMode > 0 && images.size() < 20) { - DEBUG("error: too few images to be a tower: '%d'", images.size()); + DEBUG_ULTIMATE("error: too few images to be a tower: '%d'", images.size()); return false; } @@ -1763,7 +1771,7 @@ bool Scene::ComputeTowerCylinder(Point2f& centerPoint, float& fRadius, float& fR if (quality.y / quality.z > 0.6f || quality.x / quality.y < 0.8f) { // does not seem to be a line if (towerMode > 0) { - DEBUG("error: does not seem to be a tower: X(%.2f), Y(%.2f), Z(%.2f)", quality.x, quality.y, quality.z); + DEBUG_ULTIMATE("error: does not seem to be a tower: X(%.2f), Y(%.2f), Z(%.2f)", quality.x, quality.y, quality.z); return false; } } @@ -1790,7 +1798,7 @@ bool Scene::ComputeTowerCylinder(Point2f& centerPoint, float& fRadius, float& fR // calculate tower radius as median distance from tower center to cameras FloatArr cameraDistancesToMiddle(cameras2D.size()); FOREACH (camIdx, cameras2D) - cameraDistancesToMiddle[camIdx] = norm(cameras2D[camIdx] - centerPoint); + cameraDistancesToMiddle[camIdx] = (float)norm(cameras2D[camIdx] - centerPoint); const float fMedianDistance = cameraDistancesToMiddle.GetMedian(); fRadius = MAXF(0.2f, (fMedianDistance - 1.f) / 3.f); // get the average of top 85 to 95% of the highest distances to center @@ -1838,7 +1846,6 @@ size_t Scene::DrawCircle(PointCloud& pc, PointCloud::PointArr& outCircle, const pc.pointViews.emplace_back(views); pc.normals.emplace_back(n); pc.colors.emplace_back(Pixel8U::YELLOW); - pc.pointWeights.emplace_back(PointCloud::WeightArr{1.f}); } } return outCircle.size(); @@ -1873,7 +1880,7 @@ PointCloud Scene::BuildTowerMesh(const PointCloud& origPointCloud, const Point2f } else { cList sliceDistances(nTargetCircles); for (const Point3f& P : origPointCloud.points) { - const float d(norm(Point2f(P.x, P.y) - centerPoint)); + const float d((float)norm(Point2f(P.x, P.y) - centerPoint)); if (d <= fROIRadius) { const float fIdx((zMax - P.z) * nTargetDensity); int bIdx(FLOOR2INT(fIdx)); @@ -1899,8 +1906,8 @@ PointCloud Scene::BuildTowerMesh(const PointCloud& origPointCloud, const Point2f } else { if (pDistances.size() > 2) { pDistances.Sort(); - const size_t topIdx(MIN(pDistances.size() - 1, CEIL2INT(pDistances.size() * 0.95f))); - const size_t botIdx(MAX(1, FLOOR2INT(pDistances.size() * 0.5f))); + const size_t topIdx(MIN(pDistances.size() - 1, CEIL2INT(pDistances.size() * 0.95f))); + const size_t botIdx(MAX(1u, FLOOR2INT(pDistances.size() * 0.5f))); float avgTopDistance(0); for (size_t i = botIdx; i < topIdx; ++i) avgTopDistance += pDistances[i]; @@ -1913,23 +1920,19 @@ PointCloud Scene::BuildTowerMesh(const PointCloud& origPointCloud, const Point2f } // smoothen radii if (circleRadii.size() > 2) { - for (int ri = 1; ri < circleRadii.size() - 1; ++ri) { + for (size_t ri = 1; ri < circleRadii.size() - 1; ++ri) { const float aboveRad(circleRadii[ri - 1]); float& circleRadius = circleRadii[ri]; - const float beloweRad(circleRadii[ri + 1]); - const float AbvCrtDeltaPrc = ABS(aboveRad - circleRadius) / aboveRad; - const float BelCrtDeltaPrc = ABS(circleRadius - beloweRad) / circleRadius; + const float belowRad(circleRadii[ri + 1]); // set current radius as average of the most similar values in the closest 7 neighbors if (ri > 2 && ri < circleRadii.size() - 5) { FloatArr neighSeven(7); FOREACH(i, neighSeven) neighSeven[i] = circleRadii[ri - 2 + i]; - neighSeven.Sort(); const float medianRadius(neighSeven.GetMedian()); - circleRadius = ABS(medianRadius-aboveRad) < ABS(medianRadius-beloweRad) ? aboveRad : beloweRad; - } - else { - circleRadius = (aboveRad + beloweRad) / 2.f; + circleRadius = ABS(medianRadius-aboveRad) < ABS(medianRadius-belowRad) ? aboveRad : belowRad; + } else { + circleRadius = (aboveRad + belowRad) / 2.f; } } } @@ -2002,15 +2005,17 @@ PointCloud Scene::BuildTowerMesh(const PointCloud& origPointCloud, const Point2f mesh.faces.emplace_back(v0, v2, v1); } } - if (bInverted) { + if (bInverted) topPoints.swap(botPoints); - } } } - mesh.Save("towermesh_dbg.ply"); - towerPC.Save("cylinder.ply"); - } + mesh.Save("tower_mesh.ply"); + } else #endif + { + mesh.Release(); + } + towerPC.Save("tower.ply"); return towerPC; } @@ -2026,17 +2031,12 @@ void Scene::InitTowerScene(const int towerMode) Point2f centerPoint; if (!ComputeTowerCylinder(centerPoint, fRadius, fROIRadius, zMin, zMax, minCamZ, towerMode)) return; - DEBUG("Scene camera positions identified ROI as a tower, select neighbors as if ROI is a tower"); // add nTargetPoints points on each circle PointCloud towerPC(BuildTowerMesh(pointcloud, centerPoint, fRadius, fROIRadius, zMin, zMax, minCamZ, false)); + mesh.Release(); - switch (ABS(towerMode)) { - case 1: { // replace - pointcloud = std::move(towerPC); - break; - } - case 2: { // append + const auto AppendPointCloud = [this](const PointCloud& towerPC) { bool bHasNormal(pointcloud.normals.size() == pointcloud.GetSize()); bool bHasColor(pointcloud.colors.size() == pointcloud.GetSize()); bool bHasWeights(pointcloud.pointWeights.size() == pointcloud.GetSize()); @@ -2050,32 +2050,29 @@ void Scene::InitTowerScene(const int towerMode) if (bHasWeights) pointcloud.pointWeights.emplace_back(towerPC.pointWeights[idxPoint]); } + }; + + switch (ABS(towerMode)) { + case 1: // replace + pointcloud = std::move(towerPC); + VERBOSE("Scene identified as tower-like; replace existing point-cloud with detected tower point-cloud"); break; - } - case 3: { // select neighbors and remove added points + case 2: // append + AppendPointCloud(towerPC); + VERBOSE("Scene identified as tower-like; append to existing point-cloud the detected tower point-cloud"); + break; + case 3: // select neighbors pointcloud.Swap(towerPC); SelectNeighborViews(OPTDENSE::nMinViews, OPTDENSE::nMinViewsTrustPoint>1?OPTDENSE::nMinViewsTrustPoint:2, FD2R(OPTDENSE::fOptimAngle), OPTDENSE::nPointInsideROI); pointcloud.Swap(towerPC); + VERBOSE("Scene identified as tower-like; only select view neighbors from detected tower point-cloud"); break; - } - case 4: { // select neighbors + case 4: // select neighbors and append tower points pointcloud.Swap(towerPC); SelectNeighborViews(OPTDENSE::nMinViews, OPTDENSE::nMinViewsTrustPoint>1?OPTDENSE::nMinViewsTrustPoint:2, FD2R(OPTDENSE::fOptimAngle), OPTDENSE::nPointInsideROI); pointcloud.Swap(towerPC); - bool bHasNormal(pointcloud.normals.size() == pointcloud.GetSize()); - bool bHasColor(pointcloud.colors.size() == pointcloud.GetSize()); - bool bHasWeights(pointcloud.pointWeights.size() == pointcloud.GetSize()); - FOREACH(idxPoint, towerPC.points) { - pointcloud.points.emplace_back(towerPC.points[idxPoint]); - pointcloud.pointViews.emplace_back(towerPC.pointViews[idxPoint]); - if (bHasNormal) - pointcloud.normals.emplace_back(towerPC.normals[idxPoint]); - if (bHasColor) - pointcloud.colors.emplace_back(towerPC.colors[idxPoint]); - if (bHasWeights) - pointcloud.pointWeights.emplace_back(towerPC.pointWeights[idxPoint]); - } + AppendPointCloud(towerPC); + VERBOSE("Scene identified as tower-like; select view neighbors from detected tower point-cloud and next append it to existing point-cloud"); break; } - } } // InitTowerScene diff --git a/libs/MVS/Scene.h b/libs/MVS/Scene.h index 308367a9a..8eaa96369 100644 --- a/libs/MVS/Scene.h +++ b/libs/MVS/Scene.h @@ -80,7 +80,13 @@ class MVS_API Scene bool SaveViewNeighbors(const String& fileName) const; bool Import(const String& fileName); - bool Load(const String& fileName, bool bImport=false); + enum SCENE_TYPE { + SCENE_NA = 0, + SCENE_INTERFACE = 1, + SCENE_MVS = 2, + SCENE_IMPORT = 3, + }; + SCENE_TYPE Load(const String& fileName, bool bImport=false); bool Save(const String& fileName, ARCHIVE_TYPE type=ARCHIVE_DEFAULT) const; bool EstimateNeighborViewsPointCloud(unsigned maxResolution=16); @@ -146,7 +152,7 @@ class MVS_API Scene // Mesh texturing bool TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsigned minCommonCameras=0, float fOutlierThreshold=0.f, float fRatioDataSmoothness=0.3f, bool bGlobalSeamLeveling=true, bool bLocalSeamLeveling=true, unsigned nTextureSizeMultiple=0, unsigned nRectPackingHeuristic=3, Pixel8U colEmpty=Pixel8U(255,127,39), - float fSharpnessWeight=0.5f, int ignoreMaskLabel = -1, const IIndexArr& views=IIndexArr()); + float fSharpnessWeight=0.5f, int ignoreMaskLabel=-1, int maxTextureSize=0, const IIndexArr& views=IIndexArr()); #ifdef _USE_BOOST // implement BOOST serialization diff --git a/libs/MVS/SceneDensify.cpp b/libs/MVS/SceneDensify.cpp index 02c31ae3b..0c36a2c4a 100644 --- a/libs/MVS/SceneDensify.cpp +++ b/libs/MVS/SceneDensify.cpp @@ -158,13 +158,13 @@ bool DepthMapsData::SelectViews(IIndexArr& images, IIndexArr& imagesMap, IIndexA const IIndex idx(images[i]); ASSERT(imagesMap[idx] != NO_ID); const ViewScoreArr& neighbors(arrDepthData[idx].neighbors); - ASSERT(neighbors.GetSize() <= OPTDENSE::nMaxViews); + ASSERT(neighbors.size() <= OPTDENSE::nMaxViews); // register edges - FOREACHPTR(pNeighbor, neighbors) { - const IIndex idx2(pNeighbor->idx.ID); + for (const ViewScore& neighbor: neighbors) { + const IIndex idx2(neighbor.ID); ASSERT(imagesMap[idx2] != NO_ID); - edges[MakePairIdx(idx,idx2)] = pNeighbor->idx.area; - totScore += pNeighbor->score; + edges[MakePairIdx(idx,idx2)] = neighbor.area; + totScore += neighbor.score; ++numScores; } } @@ -178,7 +178,7 @@ bool DepthMapsData::SelectViews(IIndexArr& images, IIndexArr& imagesMap, IIndexA const float fEmptyPairwise = 8.f*OPTDENSE::fPairwiseMul; const float fSamePairwise = 24.f*OPTDENSE::fPairwiseMul; const IIndex _num_labels = OPTDENSE::nMaxViews+1; // N neighbors and an empty state - const IIndex _num_nodes = images.GetSize(); + const IIndex _num_nodes = images.size(); typedef MRFEnergy MRFEnergyType; CAutoPtr energy(new MRFEnergyType(TypeGeneral::GlobalSize())); CAutoPtrArr nodes(new MRFEnergyType::NodeId[_num_nodes]); @@ -189,8 +189,8 @@ bool DepthMapsData::SelectViews(IIndexArr& images, IIndexArr& imagesMap, IIndexA const ViewScoreArr& neighbors(arrDepthData[images[n]].neighbors); FOREACH(k, neighbors) arrUnary[k] = avgScore/neighbors[k].score; // use average score to normalize the values (not to depend so much on the number of features in the scene) - arrUnary[neighbors.GetSize()] = fEmptyUnaryMult*(neighbors.IsEmpty()?avgScore*0.01f:arrUnary[neighbors.GetSize()-1]); - nodes[n] = energy->AddNode(TypeGeneral::LocalSize(neighbors.GetSize()+1), TypeGeneral::NodeData(arrUnary.Begin())); + arrUnary[neighbors.size()] = fEmptyUnaryMult*(neighbors.empty()?avgScore*0.01f:arrUnary[neighbors.size()-1]); + nodes[n] = energy->AddNode(TypeGeneral::LocalSize(neighbors.size()+1), TypeGeneral::NodeData(arrUnary.data())); } // pairwise costs: as ratios between the area to be covered and the area actually covered EnergyCostArr arrPairwise(_num_labels*_num_labels); @@ -201,17 +201,17 @@ bool DepthMapsData::SelectViews(IIndexArr& images, IIndexArr& imagesMap, IIndexA const ViewScoreArr& neighborsJ(arrDepthData[pair.j].neighbors); arrPairwise.Empty(); FOREACHPTR(pNj, neighborsJ) { - const IIndex i(pNj->idx.ID); - const float areaJ(area/pNj->idx.area); + const IIndex i(pNj->ID); + const float areaJ(area/pNj->area); FOREACHPTR(pNi, neighborsI) { - const IIndex j(pNi->idx.ID); - const float areaI(area/pNi->idx.area); + const IIndex j(pNi->ID); + const float areaI(area/pNi->area); arrPairwise.Insert(pair.i == i && pair.j == j ? fSamePairwise : fPairwiseMul*(areaI+areaJ)); } arrPairwise.Insert(fEmptyPairwise+fPairwiseMul*areaJ); } for (const ViewScore& Ni: neighborsI) { - const float areaI(area/Ni.idx.area); + const float areaI(area/Ni.area); arrPairwise.Insert(fPairwiseMul*areaI+fEmptyPairwise); } arrPairwise.Insert(fEmptyPairwise*2); @@ -247,7 +247,7 @@ bool DepthMapsData::SelectViews(IIndexArr& images, IIndexArr& imagesMap, IIndexA idxNeighbor = NO_ID; // empty } else { idxNeighbor = label; - DEBUG_ULTIMATE("\treference image %3u paired with target image %3u (idx %2u)", images[n], neighbors[label].idx.ID, label); + DEBUG_ULTIMATE("\treference image %3u paired with target image %3u (idx %2u)", images[n], neighbors[label].ID, label); } } @@ -317,8 +317,8 @@ bool DepthMapsData::InitViews(DepthData& depthData, IIndex idxNeighbor, IIndex n // set target image as the given neighbor const ViewScore& neighbor = depthData.neighbors[idxNeighbor]; DepthData::ViewData& viewTrg = depthData.images.AddEmpty(); - viewTrg.pImageData = &scene.images[neighbor.idx.ID]; - viewTrg.scale = neighbor.idx.scale; + viewTrg.pImageData = &scene.images[neighbor.ID]; + viewTrg.scale = neighbor.scale; viewTrg.camera = viewTrg.pImageData->camera; if (loadImages) { viewTrg.pImageData->image.toGray(viewTrg.image, cv::COLOR_BGR2GRAY, true); @@ -328,7 +328,7 @@ bool DepthMapsData::InitViews(DepthData& depthData, IIndex idxNeighbor, IIndex n if (DepthData::ViewData::NeedScaleImage(viewTrg.scale)) viewTrg.camera = viewTrg.pImageData->GetCamera(scene.platforms, Image8U::computeResize(viewTrg.pImageData->image.size(), viewTrg.scale)); } - DEBUG_EXTRA("Reference image %3u paired with image %3u", idxImage, neighbor.idx.ID); + DEBUG_EXTRA("Reference image %3u paired with image %3u", idxImage, neighbor.ID); } else { // initialize all neighbor views too (global reconstruction is used) const float fMinScore(MAXF(depthData.neighbors.First().score*OPTDENSE::fViewMinScoreRatio, OPTDENSE::fViewMinScore)); @@ -338,8 +338,8 @@ bool DepthMapsData::InitViews(DepthData& depthData, IIndex idxNeighbor, IIndex n (neighbor.score < fMinScore)) break; DepthData::ViewData& viewTrg = depthData.images.AddEmpty(); - viewTrg.pImageData = &scene.images[neighbor.idx.ID]; - viewTrg.scale = neighbor.idx.scale; + viewTrg.pImageData = &scene.images[neighbor.ID]; + viewTrg.scale = neighbor.scale; viewTrg.camera = viewTrg.pImageData->camera; if (loadImages) { viewTrg.pImageData->image.toGray(viewTrg.image, cv::COLOR_BGR2GRAY, true); @@ -354,20 +354,20 @@ bool DepthMapsData::InitViews(DepthData& depthData, IIndex idxNeighbor, IIndex n // print selected views if (g_nVerbosityLevel > 2) { String msg; - for (IIndex i=1; icamera; @@ -1077,7 +1077,7 @@ bool DepthMapsData::FilterDepthMap(DepthData& depthDataRef, const IIndexArr& idx confMap.create(sizeRef); confMap.memset(0); } - const IIndex idxView = depthDataRef.neighbors[idxNeighbors[(IIndex)n]].idx.ID; + const IIndex idxView = depthDataRef.neighbors[idxNeighbors[(IIndex)n]].ID; const DepthData& depthData = arrDepthData[idxView]; const Camera& camera = depthData.images.First().camera; const Image8U::Size size(depthData.depthMap.size()); @@ -1180,7 +1180,7 @@ bool DepthMapsData::FilterDepthMap(DepthData& depthDataRef, const IIndexArr& idx negConf += confMaps[n](xRef); } else { // free-space violation - const DepthData& depthData = arrDepthData[depthDataRef.neighbors[idxNeighbors[n]].idx.ID]; + const DepthData& depthData = arrDepthData[depthDataRef.neighbors[idxNeighbors[n]].ID]; const Camera& camera = depthData.images.First().camera; const Point3 X(cameraRef.TransformPointI2W(Point3(xRef.x,xRef.y,depth))); const ImageRef x(ROUND2INT(camera.TransformPointW2I(X))); @@ -1389,55 +1389,103 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateColor, b typedef SEACAVE::cList ProjsArr; // find best connected images - IndexScoreArr connections(0, scene.images.GetSize()); + IndexScoreArr connections(scene.images.size()); size_t nPointsEstimate(0); bool bNormalMap(true); - FOREACH(i, scene.images) { - DepthData& depthData = arrDepthData[i]; - if (!depthData.IsValid()) + #ifdef DENSE_USE_OPENMP + bool bAbort(false); + #pragma omp parallel for shared(connections, nPointsEstimate, bNormalMap, bAbort) + for (int64_t i=0; i<(int64_t)scene.images.size(); ++i) { + #pragma omp flush (bAbort) + if (bAbort) continue; - if (depthData.IncRef(ComposeDepthFilePath(depthData.GetView().GetID(), "dmap")) == 0) + const IIndex idxImage((IIndex)i); + #else + FOREACH(idxImage, scene.images) { + #endif + IndexScore& connection = connections[idxImage]; + DepthData& depthData = arrDepthData[idxImage]; + if (!depthData.IsValid()) { + connection.idx = NO_ID; + connection.score = 0; + continue; + } + const String fileName(ComposeDepthFilePath(depthData.GetView().GetID(), "dmap")); + if (depthData.IncRef(fileName) == 0) { + #ifdef DENSE_USE_OPENMP + bAbort = true; + #pragma omp flush (bAbort) + continue; + #else return; + #endif + } ASSERT(!depthData.IsEmpty()); - IndexScore& connection = connections.AddEmpty(); - connection.idx = i; - connection.score = (float)scene.images[i].neighbors.GetSize(); + connection.idx = idxImage; + connection.score = (float)scene.images[idxImage].neighbors.size(); + if (bEstimateNormal && depthData.normalMap.empty()) { + EstimateNormalMap(depthData.images.front().camera.K, depthData.depthMap, depthData.normalMap); + if (!depthData.Save(fileName)) { + #ifdef DENSE_USE_OPENMP + bAbort = true; + #pragma omp flush (bAbort) + continue; + #else + return; + #endif + } + } + #ifdef DENSE_USE_OPENMP + #pragma omp critical + #endif + { nPointsEstimate += ROUND2INT(depthData.depthMap.area()*(0.5f/*valid*/*0.3f/*new*/)); if (depthData.normalMap.empty()) bNormalMap = false; + } } + #ifdef DENSE_USE_OPENMP + if (bAbort) + return; + #endif connections.Sort(); + while (!connections.empty() && connections.back().score <= 0) + connections.pop_back(); + if (connections.empty()) { + DEBUG("error: no valid depth-maps found"); + return; + } // fuse all depth-maps, processing the best connected images first - const unsigned nMinViewsFuse(MINF(OPTDENSE::nMinViewsFuse, scene.images.GetSize())); + const unsigned nMinViewsFuse(MINF(OPTDENSE::nMinViewsFuse, scene.images.size())); const float normalError(COS(FD2R(OPTDENSE::fNormalDiffThreshold))); CLISTDEF0(Depth*) invalidDepths(0, 32); size_t nDepths(0); typedef TImage DepthIndex; typedef cList DepthIndexArr; - DepthIndexArr arrDepthIdx(scene.images.GetSize()); + DepthIndexArr arrDepthIdx(scene.images.size()); ProjsArr projs(0, nPointsEstimate); if (bEstimateNormal && !bNormalMap) bEstimateNormal = false; - pointcloud.points.Reserve(nPointsEstimate); - pointcloud.pointViews.Reserve(nPointsEstimate); - pointcloud.pointWeights.Reserve(nPointsEstimate); + pointcloud.points.reserve(nPointsEstimate); + pointcloud.pointViews.reserve(nPointsEstimate); + pointcloud.pointWeights.reserve(nPointsEstimate); if (bEstimateColor) - pointcloud.colors.Reserve(nPointsEstimate); + pointcloud.colors.reserve(nPointsEstimate); if (bEstimateNormal) - pointcloud.normals.Reserve(nPointsEstimate); - Util::Progress progress(_T("Fused depth-maps"), connections.GetSize()); + pointcloud.normals.reserve(nPointsEstimate); + Util::Progress progress(_T("Fused depth-maps"), connections.size()); GET_LOGCONSOLE().Pause(); - FOREACHPTR(pConnection, connections) { + for (const IndexScore& connection: connections) { TD_TIMER_STARTD(); - const uint32_t idxImage(pConnection->idx); + const uint32_t idxImage(connection.idx); const DepthData& depthData(arrDepthData[idxImage]); - ASSERT(!depthData.images.IsEmpty() && !depthData.neighbors.IsEmpty()); + ASSERT(!depthData.images.empty() && !depthData.neighbors.empty()); for (const ViewScore& neighbor: depthData.neighbors) { - DepthIndex& depthIdxs = arrDepthIdx[neighbor.idx.ID]; + DepthIndex& depthIdxs = arrDepthIdx[neighbor.ID]; if (!depthIdxs.empty()) continue; - const DepthData& depthDataB(arrDepthData[neighbor.idx.ID]); + const DepthData& depthDataB(arrDepthData[neighbor.ID]); if (depthDataB.IsEmpty()) continue; depthIdxs.create(depthDataB.depthMap.size()); @@ -1445,14 +1493,14 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateColor, b } ASSERT(!depthData.IsEmpty()); const Image8U::Size sizeMap(depthData.depthMap.size()); - const Image& imageData = *depthData.images.First().pImageData; - ASSERT(&imageData-scene.images.Begin() == idxImage); + const Image& imageData = *depthData.images.front().pImageData; + ASSERT(&imageData-scene.images.data() == idxImage); DepthIndex& depthIdxs = arrDepthIdx[idxImage]; if (depthIdxs.empty()) { depthIdxs.create(Image8U::Size(imageData.width, imageData.height)); depthIdxs.memset((uint8_t)NO_ID); } - const size_t nNumPointsPrev(pointcloud.points.GetSize()); + const size_t nNumPointsPrev(pointcloud.points.size()); for (int i=0; i(imageData.camera.R.t()*Cast(depthData.normalMap(x))) : Normal(0,0,-1)); ASSERT(ISEQUAL(norm(normal), 1.f)); // check the projection in the neighbor depth-maps Point3 X(point*confidence); Pixel32F C(Cast(imageData.image(x))*confidence); PointCloud::Normal N(normal*confidence); - invalidDepths.Empty(); - FOREACHPTR(pNeighbor, depthData.neighbors) { - const IIndex idxImageB(pNeighbor->idx.ID); + invalidDepths.clear(); + for (const ViewScore& neighbor: depthData.neighbors) { + const IIndex idxImageB(neighbor.ID); DepthData& depthDataB = arrDepthData[idxImageB]; if (depthDataB.IsEmpty()) continue; @@ -1523,10 +1571,10 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateColor, b } if (pt.z < depthB) { // discard depth - invalidDepths.Insert(&depthB); + invalidDepths.emplace_back(&depthB); } } - if (views.GetSize() < nMinViewsFuse) { + if (views.size() < nMinViewsFuse) { // remove point FOREACH(v, views) { const IIndex idxImageB(views[v]); @@ -1534,49 +1582,50 @@ void DepthMapsData::FuseDepthMaps(PointCloud& pointcloud, bool bEstimateColor, b ASSERT(arrDepthIdx[idxImageB].isInside(x) && arrDepthIdx[idxImageB](x).idx != NO_ID); arrDepthIdx[idxImageB](x).idx = NO_ID; } - projs.RemoveLast(); - pointcloud.pointWeights.RemoveLast(); - pointcloud.pointViews.RemoveLast(); - pointcloud.points.RemoveLast(); + projs.pop_back(); + pointcloud.pointWeights.pop_back(); + pointcloud.pointViews.pop_back(); + pointcloud.points.pop_back(); } else { // this point is valid, store it const REAL nrm(REAL(1)/confidence); point = X*nrm; ASSERT(ISFINITE(point)); if (bEstimateColor) - pointcloud.colors.AddConstruct((C*(float)nrm).cast()); + pointcloud.colors.emplace_back((C*(float)nrm).cast()); if (bEstimateNormal) - pointcloud.normals.AddConstruct(normalized(N*(float)nrm)); + pointcloud.normals.emplace_back(normalized(N*(float)nrm)); // invalidate all neighbor depths that do not agree with it for (Depth* pDepth: invalidDepths) *pDepth = 0; } } } - ASSERT(pointcloud.points.GetSize() == pointcloud.pointViews.GetSize() && pointcloud.points.GetSize() == pointcloud.pointWeights.GetSize() && pointcloud.points.GetSize() == projs.GetSize()); - DEBUG_ULTIMATE("Depths map for reference image %3u fused using %u depths maps: %u new points (%s)", idxImage, depthData.images.GetSize()-1, pointcloud.points.GetSize()-nNumPointsPrev, TD_TIMER_GET_FMT().c_str()); - progress.display(pConnection-connections.Begin()); + ASSERT(pointcloud.points.size() == pointcloud.pointViews.size() && pointcloud.points.size() == pointcloud.pointWeights.size() && pointcloud.points.size() == projs.size()); + DEBUG_ULTIMATE("Depths map for reference image %3u fused using %u depths maps: %u new points (%s)", idxImage, depthData.images.size()-1, pointcloud.points.size()-nNumPointsPrev, TD_TIMER_GET_FMT().c_str()); + progress.display(&connection-connections.data()); } GET_LOGCONSOLE().Play(); progress.close(); arrDepthIdx.Release(); - DEBUG_EXTRA("Depth-maps fused and filtered: %u depth-maps, %u depths, %u points (%d%%%%) (%s)", connections.GetSize(), nDepths, pointcloud.points.GetSize(), ROUND2INT((100.f*pointcloud.points.GetSize())/nDepths), TD_TIMER_GET_FMT().c_str()); + DEBUG_EXTRA("Depth-maps fused and filtered: %u depth-maps, %u depths, %u points (%d%%%%) (%s)", + connections.size(), nDepths, pointcloud.points.size(), ROUND2INT((100.f*pointcloud.points.size())/nDepths), TD_TIMER_GET_FMT().c_str()); - if (bEstimateNormal && !pointcloud.points.IsEmpty() && pointcloud.normals.IsEmpty()) { + if (bEstimateNormal && !pointcloud.points.empty() && pointcloud.normals.empty()) { // estimate normal also if requested (quite expensive if normal-maps not available) TD_TIMER_STARTD(); - pointcloud.normals.Resize(pointcloud.points.GetSize()); - const int64_t nPoints((int64_t)pointcloud.points.GetSize()); + pointcloud.normals.resize(pointcloud.points.size()); + const int64_t nPoints((int64_t)pointcloud.points.size()); #ifdef DENSE_USE_OPENMP #pragma omp parallel for #endif for (int64_t i=0; i& hullFacet // find all facets on the convex-hull in camera's view // create the 4 frustum planes ASSERT(facets.empty()); - typedef TFrustum Frustum; - Frustum frustum(imageData.camera.P, imageData.width, imageData.height, 0, 1); + const TFrustum frustum(imageData.camera.P, imageData.width, imageData.height, 0, 1); // loop over all cells const point_t ptOrigin(MVS2CGAL(imageData.camera.C)); for (const facet_t& face: hullFacets) { @@ -1100,7 +1099,7 @@ bool Scene::ReconstructMesh(float distInsert, bool bUseFreeSpaceSupport, bool bU // create graph MaxFlow graph(delaunay.number_of_cells()); // set weights - constexpr edge_cap_t maxCap(FLT_MAX*0.0001f); + constexpr edge_cap_t maxCap(3.402823466e+34f/*FLT_MAX*0.0001f*/); for (delaunay_t::All_cells_iterator ci=delaunay.all_cells_begin(), ce=delaunay.all_cells_end(); ci!=ce; ++ci) { const cell_size_t ciID(ci->info()); const cell_info_t& ciInfo(infoCells[ciID]); @@ -1157,9 +1156,7 @@ bool Scene::ReconstructMesh(float distInsert, bool bUseFreeSpaceSupport, bool bU } // fix non-manifold vertices and edges - for (unsigned i=0; i Frustum; - const Frustum frustum(Frustum::MATRIX3x4(((PMatrix::CEMatMap)imageData.camera.P).cast()), (float)imageData.width, (float)imageData.height); + const TFrustum frustum(Matrix3x4f(imageData.camera.P), (float)imageData.width, (float)imageData.height); Mesh::FacesInserter inserter(arrCameraFaces[ID]); octree.Traverse(frustum, inserter); } @@ -1056,9 +1055,9 @@ void MeshRefine::ThSelectNeighbors(uint32_t idxImage, std::unordered_setidx.ID].IsValid()); - mapPairs.insert(MakePairIdx((uint32_t)idxImage, pNeighbor->idx.ID)); + for (const ViewScore& neighbor: neighbors) { + ASSERT(images[neighbor.ID].IsValid()); + mapPairs.insert(MakePairIdx((uint32_t)idxImage, neighbor.ID)); } } void MeshRefine::ThInitImage(uint32_t idxImage, Real scale, Real sigma) diff --git a/libs/MVS/SceneRefineCUDA.cpp b/libs/MVS/SceneRefineCUDA.cpp index c98803ac8..5fee76e0f 100644 --- a/libs/MVS/SceneRefineCUDA.cpp +++ b/libs/MVS/SceneRefineCUDA.cpp @@ -2091,9 +2091,9 @@ MeshRefineCUDA::MeshRefineCUDA(Scene& _scene, unsigned _nAlternatePair, float _w continue; ViewScoreArr neighbors(imageData.neighbors); Scene::FilterNeighborViews(neighbors, fMinArea, fMinScale, fMaxScale, fMinAngle, fMaxAngle, nMaxViews); - FOREACHPTR(pNeighbor, neighbors) { - ASSERT(images[pNeighbor->idx.ID].IsValid()); - mapPairs.insert(MakePairIdx((uint32_t)idxImage, pNeighbor->idx.ID)); + for (const ViewScore& neighbor: neighbors) { + ASSERT(images[neighbor.ID].IsValid()); + mapPairs.insert(MakePairIdx((uint32_t)idxImage, neighbor.ID)); } } pairs.Reserve(mapPairs.size()); @@ -2308,8 +2308,7 @@ void MeshRefineCUDA::ListCameraFaces() const Image& imageData = images[ID]; if (!imageData.IsValid()) continue; - typedef TFrustum Frustum; - const Frustum frustum(Frustum::MATRIX3x4(((PMatrix::CEMatMap)imageData.camera.P).cast()), (float)imageData.width, (float)imageData.height); + const TFrustum frustum(Matrix3x4f(imageData.camera.P), (float)imageData.width, (float)imageData.height); Mesh::FacesInserter inserter(arrCameraFaces[ID]); octree.Traverse(frustum, inserter); } @@ -2401,13 +2400,13 @@ void MeshRefineCUDA::SubdivideMesh(uint32_t maxArea, float fDecimate, unsigned n ListFaceAreas(maxAreas); ASSERT(!maxAreas.IsEmpty()); - const float maxArea((float)(maxArea > 0 ? maxArea : 64)); + const float maxAreaf((float)(maxArea > 0 ? maxArea : 64)); const float medianArea(6.f*(float)Mesh::AreaArr(maxAreas).GetMedian()); - if (medianArea < maxArea) { + if (medianArea < maxAreaf) { maxAreas.Empty(); // decimate to the auto detected resolution - scene.mesh.Clean(MAXF(0.1f, medianArea/maxArea), 0.f, false, nCloseHoles, 0u, 0.f, false); + scene.mesh.Clean(MAXF(0.1f, medianArea/maxAreaf), 0.f, false, nCloseHoles, 0u, 0.f, false); scene.mesh.Clean(1.f, 0.f, false, nCloseHoles, 0u, 0.f, true); #ifdef MESHOPT_ENSUREEDGESIZE diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index e8404ba50..568393ecc 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -133,6 +133,7 @@ typedef Mesh::VIndex VIndex; typedef Mesh::Face Face; typedef Mesh::FIndex FIndex; typedef Mesh::TexCoord TexCoord; +typedef Mesh::TexIndex TexIndex; typedef int MatIdx; typedef Eigen::Triplet MatEntry; @@ -243,7 +244,7 @@ struct MeshTexture { return patches[idx]; } inline void SortByPatchIndex(IndexArr& indices) const { - indices.Resize(patches.GetSize()); + indices.resize(patches.size()); std::iota(indices.Begin(), indices.End(), 0); std::sort(indices.Begin(), indices.End(), [&](IndexArr::Type i0, IndexArr::Type i1) -> bool { return patches[i0].idxPatch < patches[i1].idxPatch; @@ -279,7 +280,7 @@ struct MeshTexture { inline bool Next() { if (pPatches == NULL) return (idx++ == NO_ID); - if (++idx >= pPatches->GetSize()) + if (++idx >= pPatches->size()) return false; idxPatch = (*pPatches)[idx].idxPatch; return true; @@ -340,7 +341,7 @@ struct MeshTexture { void CreateSeamVertices(); void GlobalSeamLeveling(); void LocalSeamLeveling(); - void GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight); + void GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize); template static inline PIXEL RGB2YCBCR(const PIXEL& v) { @@ -387,7 +388,8 @@ struct MeshTexture { BoolArr& vertexBoundary; // for each vertex, stores if it is at the boundary or not Mesh::FaceFacesArr& faceFaces; // for each face, the list of adjacent faces, NO_ID for border edges (optional) Mesh::TexCoordArr& faceTexcoords; // for each face, the texture-coordinates of the vertices - Image8U3& textureDiffuse; // texture containing the diffuse color + Mesh::TexIndexArr& faceTexindices; // for each face, the texture-coordinates of the vertices + Mesh::Image8U3Arr& texturesDiffuse; // texture containing the diffuse color // constant the entire time Mesh::VertexArr& vertices; @@ -436,7 +438,8 @@ MeshTexture::MeshTexture(Scene& _scene, unsigned _nResolutionLevel, unsigned _nM vertexBoundary(_scene.mesh.vertexBoundary), faceFaces(_scene.mesh.faceFaces), faceTexcoords(_scene.mesh.faceTexcoords), - textureDiffuse(_scene.mesh.textureDiffuse), + faceTexindices(_scene.mesh.faceTexindices), + texturesDiffuse(_scene.mesh.texturesDiffuse), vertices(_scene.mesh.vertices), faces(_scene.mesh.faces), images(_scene.images), @@ -473,7 +476,7 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr views.resize(images.size()); std::iota(views.begin(), views.end(), IIndex(0)); } - facesDatas.Resize(faces.size()); + facesDatas.resize(faces.size()); Util::Progress progress(_T("Initialized views"), views.size()); typedef float real; TImage imageGradMag; @@ -536,8 +539,7 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr // select faces inside view frustum Mesh::FaceIdxArr cameraFaces; Mesh::FacesInserter inserter(cameraFaces); - typedef TFrustum Frustum; - const Frustum frustum(Frustum::MATRIX3x4(((PMatrix::CEMatMap)imageData.camera.P).cast()), (float)imageData.width, (float)imageData.height); + const TFrustum frustum(Matrix3x4f(imageData.camera.P), (float)imageData.width, (float)imageData.height); octree.Traverse(frustum, inserter); // project all triangles in this view and keep the closest ones faceMap.create(imageData.GetSize()); @@ -556,7 +558,7 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr #endif } rasterer.Clear(); - for (auto idxFace : cameraFaces) { + for (FIndex idxFace : cameraFaces) { rasterer.validFace = true; const Face& facet = faces[idxFace]; rasterer.idxFace = idxFace; @@ -566,7 +568,7 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr } // compute the projection area of visible faces #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA - CLISTDEF0IDX(uint32_t,FIndex) areas(faces.GetSize()); + CLISTDEF0IDX(uint32_t,FIndex) areas(faces.size()); areas.Memset(0); #endif @@ -590,10 +592,10 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr uint32_t& area = areas[idxFace]; if (area++ == 0) { #else - if (faceDatas.IsEmpty() || faceDatas.Last().idxView != idxView) { + if (faceDatas.empty() || faceDatas.back().idxView != idxView) { #endif // create new face-data - FaceData& faceData = faceDatas.AddEmpty(); + FaceData& faceData = faceDatas.emplace_back(); faceData.idxView = idxView; faceData.quality = imageGradMag(j,i); #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA @@ -601,8 +603,8 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr #endif } else { // update face-data - ASSERT(!faceDatas.IsEmpty()); - FaceData& faceData = faceDatas.Last(); + ASSERT(!faceDatas.empty()); + FaceData& faceData = faceDatas.back(); ASSERT(faceData.idxView == idxView); faceData.quality += imageGradMag(j,i); #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA @@ -813,7 +815,7 @@ void MeshTexture::CreateVirtualFaces(const FaceDataViewArr& facesDatas, FaceData } while (!currentVirtualFaceQueue.IsEmpty()); // compute virtual face quality and create virtual face for (IIndex idxView: selectedCams) { - FaceData& virtualFaceData = virtualFaceDatas.AddEmpty(); + FaceData& virtualFaceData = virtualFaceDatas.emplace_back(); virtualFaceData.quality = 0; virtualFaceData.idxView = idxView; #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA @@ -857,11 +859,11 @@ bool MeshTexture::FaceOutlierDetection(FaceDataArr& faceDatas, float thOutlier) thOutlier = 0.15f*255.f; // init colors array - if (faceDatas.GetSize() <= 3) + if (faceDatas.size() <= 3) return false; FloatArr channels[3]; for (int c=0; c<3; ++c) - channels[c].Resize(faceDatas.GetSize()); + channels[c].resize(faceDatas.size()); FOREACH(i, faceDatas) { const Color& color = faceDatas[i].color; for (int c=0; c<3; ++c) @@ -871,14 +873,14 @@ bool MeshTexture::FaceOutlierDetection(FaceDataArr& faceDatas, float thOutlier) // find median for (int c=0; c<3; ++c) channels[c].Sort(); - const unsigned idxMedian(faceDatas.GetSize() >> 1); + const unsigned idxMedian(faceDatas.size() >> 1); Color median; for (int c=0; c<3; ++c) median[c] = channels[c][idxMedian]; // abort if there are not at least 3 inliers int nInliers(0); - BoolArr inliers(faceDatas.GetSize()); + BoolArr inliers(faceDatas.size()); FOREACH(i, faceDatas) { const Color& color = faceDatas[i].color; for (int c=0; c<3; ++c) { @@ -891,7 +893,7 @@ bool MeshTexture::FaceOutlierDetection(FaceDataArr& faceDatas, float thOutlier) ++nInliers; CONTINUE_LOOP:; } - if (nInliers == faceDatas.GetSize()) + if (nInliers == faceDatas.size()) return true; if (nInliers < 3) return false; @@ -934,10 +936,10 @@ bool MeshTexture::FaceOutlierDetection(FaceDataArr& faceDatas, float thOutlier) const unsigned minInliers(4); // init colors array - if (faceDatas.GetSize() <= minInliers) + if (faceDatas.size() <= minInliers) return false; - Eigen::Matrix3Xd colorsAll(3, faceDatas.GetSize()); - BoolArr inliers(faceDatas.GetSize()); + Eigen::Matrix3Xd colorsAll(3, faceDatas.size()); + BoolArr inliers(faceDatas.size()); FOREACH(i, faceDatas) { colorsAll.col(i) = ((const Color::EVec)faceDatas[i].color).cast(); inliers[i] = true; @@ -945,7 +947,7 @@ bool MeshTexture::FaceOutlierDetection(FaceDataArr& faceDatas, float thOutlier) // perform outlier removal; abort if something goes wrong // (number of inliers below threshold or can not invert the covariance) - size_t numInliers(faceDatas.GetSize()); + size_t numInliers(faceDatas.size()); Eigen::Vector3d mean; Eigen::Matrix3d covariance; Eigen::Matrix3d covarianceInv; @@ -995,7 +997,7 @@ bool MeshTexture::FaceOutlierDetection(FaceDataArr& faceDatas, float thOutlier) } } } - if (numInliers == faceDatas.GetSize()) + if (numInliers == faceDatas.size()) return true; if (numInliers < minInliers) return false; @@ -1106,8 +1108,7 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr } ASSERT((Mesh::FIndex)boost::num_vertices(graph) == virtualFaces.size()); // assign the best view to each face - labels.resize(faces.size()); - components.resize(faces.size()); { + labels.resize(faces.size()); { // normalize quality values float maxQuality(0); for (const FaceDataArr& faceDatas: virtualFacesDatas) { @@ -1124,7 +1125,7 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr #if TEXOPT_INFERENCE == TEXOPT_INFERENCE_LBP // initialize inference structures - const LBPInference::EnergyType MaxEnergy(fRatioDataSmoothness*LBPInference::MaxEnergy); + const LBPInference::EnergyType MaxEnergy(fRatioDataSmoothness*(LBPInference::EnergyType)LBPInference::MaxEnergy); LBPInference inference; { inference.SetNumNodes(virtualFaces.size()); inference.SetSmoothCost(SmoothnessPotts); @@ -1133,7 +1134,6 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr for (boost::tie(ei, eie) = boost::out_edges(f, graph); ei != eie; ++ei) { ASSERT(f == (FIndex)ei->m_source); const FIndex fAdj((FIndex)ei->m_target); - ASSERT(components.empty() || components[f] == components[fAdj]); if (f < fAdj) // add edges only once inference.SetNeighbors(f, fAdj); } @@ -1162,7 +1162,7 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr virtualLabels.Memset(0xFF); FOREACH(l, virtualLabels) { const Label label(inference.GetLabel(l)); - ASSERT(label < images.GetSize()+1); + ASSERT(label < images.size()+1); if (label > 0) virtualLabels[l] = label-1; } @@ -1186,8 +1186,8 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr const FIndex idxFaceAdj = afaces[v]; if (idxFaceAdj == NO_ID || idxFace >= idxFaceAdj) continue; - const bool bInvisibleFace(facesDatas[idxFace].IsEmpty()); - const bool bInvisibleFaceAdj(facesDatas[idxFaceAdj].IsEmpty()); + const bool bInvisibleFace(facesDatas[idxFace].empty()); + const bool bInvisibleFaceAdj(facesDatas[idxFaceAdj].empty()); if (bInvisibleFace || bInvisibleFaceAdj) { if (bInvisibleFace != bInvisibleFaceAdj) seamEdges.emplace_back(idxFace, idxFaceAdj); @@ -1202,9 +1202,7 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr // start patch creation starting directly from individual faces if (!bUseVirtualFaces) { // assign the best view to each face - labels.resize(faces.size()); - components.resize(faces.size()); - { + labels.resize(faces.size()); { // normalize quality values float maxQuality(0); for (const FaceDataArr& faceDatas: facesDatas) { @@ -1221,7 +1219,7 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr #if TEXOPT_INFERENCE == TEXOPT_INFERENCE_LBP // initialize inference structures - const LBPInference::EnergyType MaxEnergy(fRatioDataSmoothness*LBPInference::MaxEnergy); + const LBPInference::EnergyType MaxEnergy(fRatioDataSmoothness*(LBPInference::EnergyType)LBPInference::MaxEnergy); LBPInference inference; { inference.SetNumNodes(faces.size()); inference.SetSmoothCost(SmoothnessPotts); @@ -1230,7 +1228,6 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr for (boost::tie(ei, eie) = boost::out_edges(f, graph); ei != eie; ++ei) { ASSERT(f == (FIndex)ei->m_source); const FIndex fAdj((FIndex)ei->m_target); - ASSERT(components.empty() || components[f] == components[fAdj]); if (f < fAdj) // add edges only once inference.SetNeighbors(f, fAdj); } @@ -1266,18 +1263,20 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr #if TEXOPT_INFERENCE == TEXOPT_INFERENCE_TRWS // find connected components + ASSERT((FIndex)boost::num_vertices(graph) == faces.size()); + components.resize(faces.size()); const FIndex nComponents(boost::connected_components(graph, components.data())); // map face ID from global to component space typedef cList NodeIDs; - NodeIDs nodeIDs(faces.GetSize()); + NodeIDs nodeIDs(faces.size()); NodeIDs sizes(nComponents); sizes.Memset(0); FOREACH(c, components) nodeIDs[c] = sizes[components[c]]++; // initialize inference structures - const LabelID numLabels(images.GetSize()+1); + const LabelID numLabels(images.size()+1); CLISTDEFIDX(TRWSInference, FIndex) inferences(nComponents); FOREACH(s, sizes) { const NodeID numNodes(sizes[s]); @@ -1326,7 +1325,7 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr // assign the optimal view (label) to each face #ifdef TEXOPT_USE_OPENMP #pragma omp parallel for schedule(dynamic) - for (int i=0; i<(int)inferences.GetSize(); ++i) { + for (int i=0; i<(int)inferences.size(); ++i) { #else FOREACH(i, inferences) { #endif @@ -1343,7 +1342,7 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr continue; const Label label(inference.GetLabel(nodeIDs[l])); ASSERT(label >= 0 && label < numLabels); - if (label < images.GetSize()) + if (label < images.size()) labels[l] = label; } #endif @@ -1358,7 +1357,7 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr for (boost::tie(ei, eie) = boost::edges(graph); ei != eie; ++ei) { const FIndex fSource((FIndex)ei->m_source); const FIndex fTarget((FIndex)ei->m_target); - ASSERT(components[fSource] == components[fTarget]); + ASSERT(components.empty() || components[fSource] == components[fTarget]); if (labels[fSource] != labels[fTarget]) seamEdges.emplace_back(fSource, fTarget); } @@ -1366,7 +1365,8 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr boost::remove_edge(pEdge->i, pEdge->j, graph); // find connected components: texture patches - ASSERT((FIndex)boost::num_vertices(graph) == components.GetSize()); + ASSERT((FIndex)boost::num_vertices(graph) == faces.size()); + components.resize(faces.size()); const FIndex nComponents(boost::connected_components(graph, components.data())); // create texture patches; @@ -1375,27 +1375,27 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr sizes.Memset(0); FOREACH(c, components) ++sizes[components[c]]; - texturePatches.Resize(nComponents+1); - texturePatches.Last().label = NO_ID; + texturePatches.resize(nComponents+1); + texturePatches.back().label = NO_ID; FOREACH(f, faces) { const Label label(labels[f]); const FIndex c(components[f]); TexturePatch& texturePatch = texturePatches[c]; - ASSERT(texturePatch.label == label || texturePatch.faces.IsEmpty()); + ASSERT(texturePatch.label == label || texturePatch.faces.empty()); if (label == NO_ID) { texturePatch.label = NO_ID; - texturePatches.Last().faces.Insert(f); + texturePatches.back().faces.Insert(f); } else { - if (texturePatch.faces.IsEmpty()) { + if (texturePatch.faces.empty()) { texturePatch.label = label; - texturePatch.faces.Reserve(sizes[c]); + texturePatch.faces.reserve(sizes[c]); } texturePatch.faces.Insert(f); } } // remove all patches with invalid label (except the last one) // and create the map from the old index to the new one - mapIdxPatch.Resize(nComponents); + mapIdxPatch.resize(nComponents); std::iota(mapIdxPatch.Begin(), mapIdxPatch.End(), 0); for (FIndex t = nComponents; t-- > 0; ) { if (texturePatches[t].label == NO_ID) { @@ -1403,14 +1403,14 @@ bool MeshTexture::FaceViewSelection(unsigned minCommonCameras, float fOutlierThr mapIdxPatch.RemoveAtMove(t); } } - const unsigned numPatches(texturePatches.GetSize()-1); + const unsigned numPatches(texturePatches.size()-1); uint32_t idxPatch(0); - for (IndexArr::IDX i=0; i mapVertexSeam; - const unsigned numPatches(texturePatches.GetSize()-1); + const unsigned numPatches(texturePatches.size()-1); for (const PairIdx& edge: seamEdges) { // store edge for the later seam optimization ASSERT(edge.i < edge.j); @@ -1444,12 +1444,12 @@ void MeshTexture::CreateSeamVertices() vs[0] = faces[edge.i][vs0[0]]; vs[1] = faces[edge.i][vs0[1]]; - const auto itSeamVertex0(mapVertexSeam.emplace(std::make_pair(vs[0], seamVertices.GetSize()))); + const auto itSeamVertex0(mapVertexSeam.emplace(std::make_pair(vs[0], seamVertices.size()))); if (itSeamVertex0.second) seamVertices.emplace_back(vs[0]); SeamVertex& seamVertex0 = seamVertices[itSeamVertex0.first->second]; - const auto itSeamVertex1(mapVertexSeam.emplace(std::make_pair(vs[1], seamVertices.GetSize()))); + const auto itSeamVertex1(mapVertexSeam.emplace(std::make_pair(vs[1], seamVertices.size()))); if (itSeamVertex1.second) seamVertices.emplace_back(vs[1]); SeamVertex& seamVertex1 = seamVertices[itSeamVertex1.first->second]; @@ -1482,11 +1482,11 @@ void MeshTexture::CreateSeamVertices() void MeshTexture::GlobalSeamLeveling() { - ASSERT(!seamVertices.IsEmpty()); - const unsigned numPatches(texturePatches.GetSize()-1); + ASSERT(!seamVertices.empty()); + const unsigned numPatches(texturePatches.size()-1); // find the patch ID for each vertex - PatchIndices patchIndices(vertices.GetSize()); + PatchIndices patchIndices(vertices.size()); patchIndices.Memset(0); FOREACH(f, faces) { const uint32_t idxPatch(mapIdxPatch[components[f]]); @@ -1496,17 +1496,17 @@ void MeshTexture::GlobalSeamLeveling() } FOREACH(i, seamVertices) { const SeamVertex& seamVertex = seamVertices[i]; - ASSERT(!seamVertex.patches.IsEmpty()); + ASSERT(!seamVertex.patches.empty()); PatchIndex& patchIndex = patchIndices[seamVertex.idxVertex]; patchIndex.bIndex = true; patchIndex.idxSeamVertex = i; } // assign a row index within the solution vector x to each vertex/patch - ASSERT(vertices.GetSize() < static_cast(std::numeric_limits::max())); + ASSERT(vertices.size() < static_cast(std::numeric_limits::max())); MatIdx rowsX(0); typedef std::unordered_map VertexPatch2RowMap; - cList vertpatch2rows(vertices.GetSize()); + cList vertpatch2rows(vertices.size()); FOREACH(i, vertices) { const PatchIndex& patchIndex = patchIndices[i]; VertexPatch2RowMap& vertpatch2row = vertpatch2rows[i]; @@ -1529,7 +1529,7 @@ void MeshTexture::GlobalSeamLeveling() const float lambda(0.1f); MatIdx rowsGamma(0); Mesh::VertexIdxArr adjVerts; - CLISTDEF0(MatEntry) rows(0, vertices.GetSize()*4); + CLISTDEF0(MatEntry) rows(0, vertices.size()*4); FOREACH(v, vertices) { adjVerts.Empty(); scene.mesh.GetAdjVertices(v, adjVerts); @@ -1555,7 +1555,7 @@ void MeshTexture::GlobalSeamLeveling() } } } - ASSERT(rows.GetSize()/2 < static_cast(std::numeric_limits::max())); + ASSERT(rows.size()/2 < static_cast(std::numeric_limits::max())); SparseMat Gamma(rowsGamma, rowsX); Gamma.setFromTriplets(rows.Begin(), rows.End()); @@ -1566,10 +1566,10 @@ void MeshTexture::GlobalSeamLeveling() Colors vertexColors; Colors coeffB; for (const SeamVertex& seamVertex: seamVertices) { - if (seamVertex.patches.GetSize() < 2) + if (seamVertex.patches.size() < 2) continue; seamVertex.SortByPatchIndex(indices); - vertexColors.Resize(indices.GetSize()); + vertexColors.resize(indices.size()); FOREACH(i, indices) { const SeamVertex::Patch& patch0 = seamVertex.patches[indices[i]]; ASSERT(patch0.idxPatch < numPatches); @@ -1584,26 +1584,26 @@ void MeshTexture::GlobalSeamLeveling() vertexColors[i] = sampler.GetColor(); } const VertexPatch2RowMap& vertpatch2row = vertpatch2rows[seamVertex.idxVertex]; - for (IDX i=0; i(std::numeric_limits::max())); + ASSERT(coeffB.size() < static_cast(std::numeric_limits::max())); - const MatIdx rowsA((MatIdx)coeffB.GetSize()); + const MatIdx rowsA((MatIdx)coeffB.size()); SparseMat A(rowsA, rowsX); A.setFromTriplets(rows.Begin(), rows.End()); rows.Release(); @@ -2109,11 +2109,12 @@ void MeshTexture::LocalSeamLeveling() } } -void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight) +void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize) { // project patches in the corresponding view and compute texture-coordinates and bounding-box const int border(2); faceTexcoords.resize(faces.size()*3); + faceTexindices.resize(faces.size()); #ifdef TEXOPT_USE_OPENMP const unsigned numPatches(texturePatches.size()-1); #pragma omp parallel for schedule(dynamic) @@ -2152,7 +2153,7 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel } { // init last patch to point to a small uniform color patch - TexturePatch& texturePatch = texturePatches.Last(); + TexturePatch& texturePatch = texturePatches.back(); const int sizePatch(border*2+1); texturePatch.rect = cv::Rect(0,0, sizePatch,sizePatch); for (const FIndex idxFace: texturePatch.faces) { @@ -2163,7 +2164,7 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel } // perform seam leveling - if (texturePatches.GetSize() > 2 && (bGlobalSeamLeveling || bLocalSeamLeveling)) { + if (texturePatches.size() > 2 && (bGlobalSeamLeveling || bLocalSeamLeveling)) { // create seam vertices and edges CreateSeamVertices(); @@ -2210,84 +2211,115 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel // create texture { // arrange texture patches to fit the smallest possible texture image - RectsBinPack::RectArr rects(texturePatches.GetSize()); - FOREACH(i, texturePatches) - rects[i] = texturePatches[i].rect; - int textureSize(RectsBinPack::ComputeTextureSize(rects, nTextureSizeMultiple)); - // increase texture size till all patches fit - while (true) { - TD_TIMER_STARTD(); - bool bPacked(false); + RectsBinPack::RectWIdxArr unplacedRects(texturePatches.size()); + FOREACH(i, texturePatches) { + if (maxTextureSize > 0 && (texturePatches[i].rect.width > maxTextureSize || texturePatches[i].rect.height > maxTextureSize)) { + DEBUG("error: a patch of size %u x %u does not fit the texture", texturePatches[i].rect.width, texturePatches[i].rect.height); + ABORT("the maximum texture size chosen cannot fit a patch"); + } + unplacedRects[i] = {texturePatches[i].rect, i}; + } + + // pack patches: one pack per texture file + CLISTDEF2IDX(RectsBinPack::RectWIdxArr, TexIndex) placedRects; { + // increase texture size till all patches fit const unsigned typeRectsBinPack(nRectPackingHeuristic/100); const unsigned typeSplit((nRectPackingHeuristic-typeRectsBinPack*100)/10); const unsigned typeHeuristic(nRectPackingHeuristic%10); - switch (typeRectsBinPack) { - case 0: { - MaxRectsBinPack pack(textureSize, textureSize); - bPacked = pack.Insert(rects, (MaxRectsBinPack::FreeRectChoiceHeuristic)typeHeuristic); - break; } - case 1: { - SkylineBinPack pack(textureSize, textureSize, typeSplit!=0); - bPacked = pack.Insert(rects, (SkylineBinPack::LevelChoiceHeuristic)typeHeuristic); - break; } - case 2: { - GuillotineBinPack pack(textureSize, textureSize); - bPacked = pack.Insert(rects, false, (GuillotineBinPack::FreeRectChoiceHeuristic)typeHeuristic, (GuillotineBinPack::GuillotineSplitHeuristic)typeSplit); - break; } - default: - ABORT("error: unknown RectsBinPack type"); + int textureSize = 0; + while (!unplacedRects.empty()) { + TD_TIMER_STARTD(); + if (textureSize == 0) { + textureSize = RectsBinPack::ComputeTextureSize(unplacedRects, nTextureSizeMultiple); + if (maxTextureSize > 0 && textureSize > maxTextureSize) + textureSize = maxTextureSize; + } + + RectsBinPack::RectWIdxArr newPlacedRects; + switch (typeRectsBinPack) { + case 0: { + MaxRectsBinPack pack(textureSize, textureSize); + newPlacedRects = pack.Insert(unplacedRects, (MaxRectsBinPack::FreeRectChoiceHeuristic)typeHeuristic); + break; } + case 1: { + SkylineBinPack pack(textureSize, textureSize, typeSplit!=0); + newPlacedRects = pack.Insert(unplacedRects, (SkylineBinPack::LevelChoiceHeuristic)typeHeuristic); + break; } + case 2: { + GuillotineBinPack pack(textureSize, textureSize); + newPlacedRects = pack.Insert(unplacedRects, false, (GuillotineBinPack::FreeRectChoiceHeuristic)typeHeuristic, (GuillotineBinPack::GuillotineSplitHeuristic)typeSplit); + break; } + default: + ABORT("error: unknown RectsBinPack type"); + } + DEBUG_ULTIMATE("\tpacking texture completed: %u initial patches, %u placed patches, %u texture-size, %u textures (%s)", texturePatches.size(), newPlacedRects.size(), textureSize, placedRects.size(), TD_TIMER_GET_FMT().c_str()); + + if (textureSize == maxTextureSize || unplacedRects.empty()) { + // create texture image + placedRects.emplace_back(std::move(newPlacedRects)); + texturesDiffuse.emplace_back(textureSize, textureSize).setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + textureSize = 0; + } else { + // try again with a bigger texture + textureSize *= 2; + if (maxTextureSize > 0) + textureSize = std::max(textureSize, maxTextureSize); + unplacedRects.JoinRemove(newPlacedRects); + } } - DEBUG_ULTIMATE("\tpacking texture completed: %u patches, %u texture-size (%s)", rects.size(), textureSize, TD_TIMER_GET_FMT().c_str()); - if (bPacked) - break; - textureSize *= 2; } - // create texture image - textureDiffuse.create(textureSize, textureSize); - textureDiffuse.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); #ifdef TEXOPT_USE_OPENMP #pragma omp parallel for schedule(dynamic) - for (int_t i=0; i<(int_t)texturePatches.size(); ++i) { - const uint32_t idxPatch((uint32_t)i); + for (int_t i=0; i<(int_t)placedRects.size(); ++i) { + for (int_t j=0; j<(int_t)placedRects[(TexIndex)i].size(); ++j) { + const TexIndex idxTexture((TexIndex)i); + const uint32_t idxPlacedPatch((uint32_t)j); #else - FOREACH(idxPatch, texturePatches) { + FOREACH(idxTexture, placedRects) { + FOREACH(idxPlacedPatch, placedRects[idxTexture]) { #endif - const TexturePatch& texturePatch = texturePatches[idxPatch]; - const RectsBinPack::Rect& rect = rects[idxPatch]; - // copy patch image - ASSERT((rect.width == texturePatch.rect.width && rect.height == texturePatch.rect.height) || - (rect.height == texturePatch.rect.width && rect.width == texturePatch.rect.height)); - int x(0), y(1); - if (texturePatch.label != NO_ID) { - const Image& imageData = images[texturePatch.label]; - cv::Mat patch(imageData.image(texturePatch.rect)); - if (rect.width != texturePatch.rect.width) { - // flip patch and texture-coordinates - patch = patch.t(); - x = 1; y = 0; + const TexturePatch& texturePatch = texturePatches[placedRects[idxTexture][idxPlacedPatch].patchIdx]; + const RectsBinPack::Rect& rect = placedRects[idxTexture][idxPlacedPatch].rect; + // copy patch image + ASSERT((rect.width == texturePatch.rect.width && rect.height == texturePatch.rect.height) || + (rect.height == texturePatch.rect.width && rect.width == texturePatch.rect.height)); + int x(0), y(1); + if (texturePatch.label != NO_ID) { + const Image& imageData = images[texturePatch.label]; + cv::Mat patch(imageData.image(texturePatch.rect)); + if (rect.width != texturePatch.rect.width) { + // flip patch and texture-coordinates + patch = patch.t(); + x = 1; y = 0; + } + patch.copyTo(texturesDiffuse[idxTexture](rect)); } - patch.copyTo(textureDiffuse(rect)); - } - // compute final texture coordinates - const TexCoord offset(rect.tl()); - for (const FIndex idxFace: texturePatch.faces) { - TexCoord* texcoords = faceTexcoords.data()+idxFace*3; - for (int v=0; v<3; ++v) { - TexCoord& texcoord = texcoords[v]; - texcoord = TexCoord( - texcoord[x]+offset.x, - texcoord[y]+offset.y - ); + // compute final texture coordinates + const TexCoord offset(rect.tl()); + for (const FIndex idxFace: texturePatch.faces) { + TexCoord* texcoords = faceTexcoords.data()+idxFace*3; + faceTexindices[idxFace] = idxTexture; + for (int v=0; v<3; ++v) { + TexCoord& texcoord = texcoords[v]; + texcoord = TexCoord( + texcoord[x]+offset.x, + texcoord[y]+offset.y + ); + } } } } + if (texturesDiffuse.size() == 1) + faceTexindices.Release(); // apply some sharpening if (fSharpnessWeight > 0) { constexpr double sigma = 1.5; - Image8U3 blurryTextureDiffuse; - cv::GaussianBlur(textureDiffuse, blurryTextureDiffuse, cv::Size(), sigma); - cv::addWeighted(textureDiffuse, 1+fSharpnessWeight, blurryTextureDiffuse, -fSharpnessWeight, 0, textureDiffuse); + for (auto &textureDiffuse: texturesDiffuse) { + Image8U3 blurryTextureDiffuse; + cv::GaussianBlur(textureDiffuse, blurryTextureDiffuse, cv::Size(), sigma); + cv::addWeighted(textureDiffuse, 1+fSharpnessWeight, blurryTextureDiffuse, -fSharpnessWeight, 0, textureDiffuse); + } } } } @@ -2298,7 +2330,7 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel // - nIgnoreMaskLabel: label value to ignore in the image mask, stored in the MVS scene or next to each image with '.mask.png' extension (-1 - auto estimate mask for lens distortion, -2 - disabled) bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, - int nIgnoreMaskLabel, const IIndexArr& views) + int nIgnoreMaskLabel, int maxTextureSize, const IIndexArr& views) { MeshTexture texture(*this, nResolutionLevel, nMinResolution); @@ -2307,14 +2339,14 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi TD_TIMER_STARTD(); if (!texture.FaceViewSelection(minCommonCameras, fOutlierThreshold, fRatioDataSmoothness, nIgnoreMaskLabel, views)) return false; - DEBUG_EXTRA("Assigning the best view to each face completed: %u faces (%s)", mesh.faces.GetSize(), TD_TIMER_GET_FMT().c_str()); + DEBUG_EXTRA("Assigning the best view to each face completed: %u faces (%s)", mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); } // generate the texture image and atlas { TD_TIMER_STARTD(); - texture.GenerateTexture(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, nRectPackingHeuristic, colEmpty, fSharpnessWeight); - DEBUG_EXTRA("Generating texture atlas and image completed: %u patches, %u image size (%s)", texture.texturePatches.GetSize(), mesh.textureDiffuse.width(), TD_TIMER_GET_FMT().c_str()); + texture.GenerateTexture(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize); + DEBUG_EXTRA("Generating texture atlas and image completed: %u patches, %u image size, %u textures (%s)", texture.texturePatches.size(), mesh.texturesDiffuse[0].width(), mesh.texturesDiffuse.size(), TD_TIMER_GET_FMT().c_str()); } return true; diff --git a/libs/MVS/SemiGlobalMatcher.cpp b/libs/MVS/SemiGlobalMatcher.cpp index 2b0757921..5a9366c21 100644 --- a/libs/MVS/SemiGlobalMatcher.cpp +++ b/libs/MVS/SemiGlobalMatcher.cpp @@ -49,7 +49,7 @@ using namespace STEREO; #ifdef _USE_FILTER_DEMO #include "opencv2/ximgproc/disparity_filter.hpp" -const cv::String keys = +const String keys = "{help h usage ? | | print this message }" "{GT |None | optional ground-truth disparity (MPI-Sintel or Middlebury format) }" "{dst_path |None | optional path to save the resulting filtered disparity map }" @@ -93,12 +93,12 @@ int disparityFiltering(cv::Mat left, cv::Mat right, int argc, const LPCSTR* argv return 0; } - cv::String GT_path = parser.get("GT"); - cv::String dst_path = parser.get("dst_path"); - cv::String dst_raw_path = parser.get("dst_raw_path"); - cv::String dst_conf_path = parser.get("dst_conf_path"); - cv::String algo = parser.get("algorithm"); - cv::String filter = parser.get("filter"); + String GT_path = parser.get("GT"); + String dst_path = parser.get("dst_path"); + String dst_raw_path = parser.get("dst_raw_path"); + String dst_conf_path = parser.get("dst_conf_path"); + String algo = parser.get("algorithm"); + String filter = parser.get("filter"); bool no_display = parser.has("no-display"); bool no_downscale = parser.has("no-downscale"); int max_disp = parser.get("max_disparity"); @@ -534,12 +534,12 @@ void SemiGlobalMatcher::Match(const Scene& scene, IIndex idxImage, IIndex numNei FOREACH(idxNeighbor, leftImage.neighbors) { const ViewScore& neighbor = leftImage.neighbors[idxNeighbor]; // exclude neighbors that over the limit or too small score - ASSERT(scene.images[neighbor.idx.ID].IsValid()); + ASSERT(scene.images[neighbor.ID].IsValid()); if ((numNeighbors && idxNeighbor >= numNeighbors) || (neighbor.score < fMinScore)) break; // check if the disparity-map was already estimated for the same image pairs - const Image& rightImage = scene.images[neighbor.idx.ID]; + const Image& rightImage = scene.images[neighbor.ID]; const String pairName(MAKE_PATH(String::FormatString("%04u_%04u", leftImage.ID, rightImage.ID))); if (File::isPresent((pairName+".dimap").c_str()) || File::isPresent(MAKE_PATH(String::FormatString("%04u_%04u.dimap", rightImage.ID, leftImage.ID)))) continue; @@ -555,7 +555,7 @@ void SemiGlobalMatcher::Match(const Scene& scene, IIndex idxImage, IIndex numNei const PointCloud::ViewArr& views = scene.pointcloud.pointViews[idxPoint]; if (views.FindFirst(idxImage) != PointCloud::ViewArr::NO_INDEX) { points.push_back((uint32_t)idxPoint); - if (views.FindFirst(neighbor.idx.ID) != PointCloud::ViewArr::NO_INDEX) { + if (views.FindFirst(neighbor.ID) != PointCloud::ViewArr::NO_INDEX) { const Point3 X(scene.pointcloud.points[idxPoint]); leftPoints.emplace_back(leftImage.camera.TransformPointW2I3(X)); rightPoints.emplace_back(rightImage.camera.TransformPointW2I3(X)); @@ -753,12 +753,12 @@ void SemiGlobalMatcher::Fuse(const Scene& scene, IIndex idxImage, IIndex numNeig FOREACH(idxNeighbor, leftImage.neighbors) { const ViewScore& neighbor = leftImage.neighbors[idxNeighbor]; // exclude neighbors that over the limit or too small score - ASSERT(scene.images[neighbor.idx.ID].IsValid()); + ASSERT(scene.images[neighbor.ID].IsValid()); if ((numNeighbors && idxNeighbor >= numNeighbors) || (neighbor.score < fMinScore)) break; // check if the disparity-map was estimated for this images pair - const Image& rightImage = scene.images[neighbor.idx.ID]; + const Image& rightImage = scene.images[neighbor.ID]; Disparity subpixelSteps; cv::Size imageSize; Matrix3x3 H; Matrix4x4 Q; DisparityMap disparityMap; AccumCostMap costMap; @@ -1871,7 +1871,8 @@ void SemiGlobalMatcher::Disparity2DepthMap(const DisparityMap& disparityMap, con auto pixel = [&](int, int r, int c) { const ImageRef x(c,r); Point2f u; ProjectVertex_3x3_2_2(H.val, x.ptr(), u.ptr()); - u.x -= halfWindowSizeX; u.y -= halfWindowSizeY; + u.x -= (float)halfWindowSizeX; + u.y -= (float)halfWindowSizeY; float disparity; if (!disparityMap.sampleSafe(disparity, u, [](Disparity d) { return d != NO_DISP; })) { depthMap(x) = 0; @@ -1897,7 +1898,8 @@ void SemiGlobalMatcher::Disparity2DepthMap(const DisparityMap& disparityMap, con auto pixel = [&](int, int r, int c) { const ImageRef x(c,r); Point2f u; ProjectVertex_3x3_2_2(H.val, x.ptr(), u.ptr()); - u.x -= halfWindowSizeX; u.y -= halfWindowSizeY; + u.x -= (float)halfWindowSizeX; + u.y -= (float)halfWindowSizeY; float disparity; if (!disparityMap.sampleSafe(disparity, u, [](Disparity d) { return d != NO_DISP; })) depthMap(x) = 0; @@ -2354,7 +2356,7 @@ bool MVS::STEREO::ExportCamerasEngin(const Scene& scene, const String& fileName) image.neighbors.size() ); for (const auto& neighbor: image.neighbors) - f.print(" %u", neighbor.idx.ID); + f.print(" %u", neighbor.ID); f.print("\n"); } diff --git a/libs/Math/SimilarityTransform.cpp b/libs/Math/SimilarityTransform.cpp index 1b5685d95..f35b220e1 100644 --- a/libs/Math/SimilarityTransform.cpp +++ b/libs/Math/SimilarityTransform.cpp @@ -57,8 +57,9 @@ bool SEACAVE::SimilarityTransform(const CLISTDEF0(Point3)& points, const CLISTDE void SEACAVE::DecomposeSimilarityTransform(const Matrix4x4& transform, Matrix3x3& R, Point3& t, REAL& s) { const Eigen::Transform T(static_cast(transform)); - Eigen::Matrix scaling; - T.computeRotationScaling(&static_cast(R), &scaling); + Eigen::Matrix rotation, scaling; + T.computeRotationScaling(&rotation, &scaling); + R = rotation; t = T.translation(); s = scaling.diagonal().mean(); } // DecomposeSimilarityTransform diff --git a/MvgMvsPipeline.py b/scripts/python/MvgMvsPipeline.py similarity index 96% rename from MvgMvsPipeline.py rename to scripts/python/MvgMvsPipeline.py index edf00e7e3..fd380ebf6 100644 --- a/MvgMvsPipeline.py +++ b/scripts/python/MvgMvsPipeline.py @@ -264,13 +264,13 @@ def __init__(self): ["scene.mvs", "--dense-config-file", "Densify.ini", "--resolution-level", "1", "--number-views", "8", "-w", "\"%mvs_dir%\""]], ["Reconstruct the mesh", # 18 os.path.join(OPENMVS_BIN, "ReconstructMesh"), - ["scene_dense.mvs", "-w", "\"%mvs_dir%\""]], + ["scene_dense.mvs", "-p", "scene_dense.ply", "-w", "\"%mvs_dir%\""]], ["Refine the mesh", # 19 os.path.join(OPENMVS_BIN, "RefineMesh"), - ["scene_dense_mesh.mvs", "--scales", "1", "--gradient-step", "25.05", "-w", "\"%mvs_dir%\""]], + ["scene_dense.mvs", "-m", "scene_dense_mesh.ply", "-o", "scene_dense_mesh_refine.mvs", "--scales", "1", "--gradient-step", "25.05", "-w", "\"%mvs_dir%\""]], ["Texture the mesh", # 20 os.path.join(OPENMVS_BIN, "TextureMesh"), - ["scene_dense_mesh_refine.mvs", "--decimate", "0.5", "-w", "\"%mvs_dir%\""]], + ["scene_dense.mvs", "-m", "scene_dense_mesh_refine.ply", "-o", "scene_dense_mesh_refine_texture.mvs", "--decimate", "0.5", "-w", "\"%mvs_dir%\""]], ["Estimate disparity-maps", # 21 os.path.join(OPENMVS_BIN, "DensifyPointCloud"), ["scene.mvs", "--dense-config-file", "Densify.ini", "--fusion-mode", "-1", "-w", "\"%mvs_dir%\""]], @@ -391,7 +391,8 @@ def mkdir_ine(dirname): if 20 in CONF.steps: # TextureMesh if 19 not in CONF.steps: # RefineMesh # RefineMesh step is not run, use ReconstructMesh output - STEPS.replace_opt(20, "scene_dense_mesh_refine.mvs", "scene_dense_mesh.mvs") + STEPS.replace_opt(20, "scene_dense_mesh_refine.ply", "scene_dense_mesh.ply") + STEPS.replace_opt(20, "scene_dense_mesh_refine_texture.mvs", "scene_dense_mesh_texture.mvs") for cstep in CONF.steps: printout("#%i. %s" % (cstep, STEPS[cstep].info), effect=INVERSE) diff --git a/MvgOptimizeSfM.py b/scripts/python/MvgOptimizeSfM.py similarity index 100% rename from MvgOptimizeSfM.py rename to scripts/python/MvgOptimizeSfM.py diff --git a/scripts/python/MvsReadDMAP.py b/scripts/python/MvsReadDMAP.py new file mode 100644 index 000000000..deb354744 --- /dev/null +++ b/scripts/python/MvsReadDMAP.py @@ -0,0 +1,42 @@ +''' +Example usage of MvsUtils.py for reading DMAP file content. + +usage: MvsReadDMAP.py [-h] [--input INPUT] [--output OUTPUT] +''' + +from argparse import ArgumentParser +from concurrent.futures import ProcessPoolExecutor +from glob import glob +from MvsUtils import loadDMAP +import numpy as np +import os +import pyvips + +def exportDMAPContent(dmap_path): + dmap = loadDMAP(dmap_path) + + basename = os.path.splitext(os.path.basename(dmap['file_name']))[0] + + pyvips.Image.new_from_array(np.uint8(dmap['depth_map'] * (1 / dmap['depth_max']) * 255)).write_to_file('%s_depth_map.png' % basename) + if dmap['has_normal']: + pyvips.Image.new_from_array(np.uint8((dmap['normal_map'] @ -dmap['R'] + 1) * 0.5 * 255)).write_to_file('%s_normal_map.png' % basename) + if dmap['has_conf']: + pyvips.Image.new_from_array(np.uint8(dmap['confidence_map'] * (1 / dmap['confidence_map'].max()) * 255)).write_to_file('%s_confidence_map.png' % basename) + +def main(): + parser = ArgumentParser() + parser.add_argument('-i', '--input', type=str, required=True, help='Path to the DMAP file directory') + parser.add_argument('-t', '--threads', type=int, default=int(os.cpu_count() * 0.5) - 1, help='Number of parallel computations') + parser.add_argument('-o', '--output', type=str, required=True, help='Path to the output directory') + args = parser.parse_args() + + dmap_paths = glob(os.path.join(args.input, '*.dmap')) + + os.makedirs(args.output, exist_ok = True) + os.chdir(args.output) + + with ProcessPoolExecutor(max_workers=args.threads) as executor: + executor.map(exportDMAPContent, dmap_paths) + +if __name__ == '__main__': + main() diff --git a/scripts/python/MvsReadMVS.py b/scripts/python/MvsReadMVS.py new file mode 100644 index 000000000..597e962a3 --- /dev/null +++ b/scripts/python/MvsReadMVS.py @@ -0,0 +1,35 @@ +''' +Example usage of MvsUtils.py for reading MVS interface archive content. + +usage: MvsReadMVS.py [-h] [--input INPUT] [--output OUTPUT] +''' + +from argparse import ArgumentParser +import json +from MvsUtils import loadMVSInterface +import os + +def main(): + parser = ArgumentParser() + parser.add_argument('-i', '--input', type=str, required=True, help='Path to the MVS interface archive') + parser.add_argument('-o', '--output', type=str, required=True, help='Path to the output json file') + args = parser.parse_args() + + mvs = loadMVSInterface(args.input) + + for platform_index in range(len(mvs['platforms'])): + for camera_index in range(len(mvs['platforms'][platform_index]['cameras'])): + camera = mvs['platforms'][platform_index]['cameras'][camera_index] + image_max = max(camera['width'], camera['height']) + fx = camera['K'][0][0] / image_max + fy = camera['K'][1][1] / image_max + poses_size = len(camera['poses']) + print('Camera model loaded: platform {}; camera {}; f {:.3f}x{:.3f}; poses {}'.format(platform_index, camera_index, fx, fy, poses_size)) + + os.makedirs(os.path.dirname(args.output), exist_ok = True) + + with open(args.output, 'w') as file: + json.dump(mvs, file, indent=2) + +if __name__ == '__main__': + main() diff --git a/MvsScalablePipeline.py b/scripts/python/MvsScalablePipeline.py similarity index 100% rename from MvsScalablePipeline.py rename to scripts/python/MvsScalablePipeline.py diff --git a/scripts/python/MvsUtils.py b/scripts/python/MvsUtils.py new file mode 100644 index 000000000..9286cf13c --- /dev/null +++ b/scripts/python/MvsUtils.py @@ -0,0 +1,190 @@ +''' +OpenMVS python utilities. + +E.g., from MvsUtils import loadDMAP, loadMVSInterface +''' + +import numpy as np + +def loadDMAP(dmap_path): + with open(dmap_path, 'rb') as dmap: + file_type = dmap.read(2).decode() + content_type = np.frombuffer(dmap.read(1), dtype=np.dtype('B')) + reserve = np.frombuffer(dmap.read(1), dtype=np.dtype('B')) + + has_depth = content_type > 0 + has_normal = content_type in [3, 7, 11, 15] + has_conf = content_type in [5, 7, 13, 15] + has_views = content_type in [9, 11, 13, 15] + + image_width, image_height = np.frombuffer(dmap.read(8), dtype=np.dtype('I')) + depth_width, depth_height = np.frombuffer(dmap.read(8), dtype=np.dtype('I')) + + if (file_type != 'DR' or has_depth == False or depth_width <= 0 or depth_height <= 0 or image_width < depth_width or image_height < depth_height): + print('error: opening file \'{}\' for reading depth-data'.format(dmap_path)) + return + + depth_min, depth_max = np.frombuffer(dmap.read(8), dtype=np.dtype('f')) + + file_name_size = np.frombuffer(dmap.read(2), dtype=np.dtype('H'))[0] + file_name = dmap.read(file_name_size).decode() + + view_ids_size = np.frombuffer(dmap.read(4), dtype=np.dtype('I'))[0] + reference_view_id, *neighbor_view_ids = np.frombuffer(dmap.read(4 * view_ids_size), dtype=np.dtype('I')) + + K = np.frombuffer(dmap.read(72), dtype=np.dtype('d')).reshape(3, 3) + R = np.frombuffer(dmap.read(72), dtype=np.dtype('d')).reshape(3, 3) + C = np.frombuffer(dmap.read(24), dtype=np.dtype('d')) + + data = { + 'has_normal': has_normal, + 'has_conf': has_conf, + 'has_views': has_views, + 'image_width': image_width, + 'image_height': image_height, + 'depth_width': depth_width, + 'depth_height': depth_height, + 'depth_min': depth_min, + 'depth_max': depth_max, + 'file_name': file_name, + 'reference_view_id': reference_view_id, + 'neighbor_view_ids': neighbor_view_ids, + 'K': K, + 'R': R, + 'C': C + } + + map_size = depth_width * depth_height + depth_map = np.frombuffer(dmap.read(4 * map_size), dtype=np.dtype('f')).reshape(depth_height, depth_width) + data.update({'depth_map': depth_map}) + if has_normal: + normal_map = np.frombuffer(dmap.read(4 * map_size * 3), dtype=np.dtype('f')).reshape(depth_height, depth_width, 3) + data.update({'normal_map': normal_map}) + if has_conf: + confidence_map = np.frombuffer(dmap.read(4 * map_size), dtype=np.dtype('f')).reshape(depth_height, depth_width) + data.update({'confidence_map': confidence_map}) + if has_views: + views_map = np.frombuffer(dmap.read(map_size * 4), dtype=np.dtype('B')).reshape(depth_height, depth_width, 4) + data.update({'views_map': views_map}) + + return data + +def loadMVSInterface(archive_path): + with open(archive_path, 'rb') as mvs: + archive_type = mvs.read(4).decode() + version = np.frombuffer(mvs.read(4), dtype=np.dtype('I')).tolist()[0] + reserve = np.frombuffer(mvs.read(4), dtype=np.dtype('I')) + + if archive_type != 'MVSI': + print('error: opening file \'{}\''.format(archive_path)) + return + + data = { + 'project_stream': archive_type, + 'project_stream_version': version, + 'platforms': [], + 'images': [], + 'vertices': [], + 'vertices_normal': [], + 'vertices_color': [] + } + + platforms_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for platform_index in range(platforms_size): + platform_name_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + platform_name = mvs.read(platform_name_size).decode() + data['platforms'].append({'name': platform_name, 'cameras': []}) + cameras_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for camera_index in range(cameras_size): + camera_name_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + camera_name = mvs.read(camera_name_size).decode() + data['platforms'][platform_index]['cameras'].append({'name': camera_name}) + if version > 3: + band_name_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + band_name = mvs.read(band_name_size).decode() + data['platforms'][platform_index]['cameras'][camera_index].update({'band_name': band_name}) + if version > 0: + width, height = np.frombuffer(mvs.read(8), dtype=np.dtype('I')).tolist() + data['platforms'][platform_index]['cameras'][camera_index].update({'width': width, 'height': height}) + K = np.asarray(np.frombuffer(mvs.read(72), dtype=np.dtype('d'))).reshape(3, 3).tolist() + data['platforms'][platform_index]['cameras'][camera_index].update({'K': K, 'poses': []}) + identity_matrix = np.asarray(np.frombuffer(mvs.read(96), dtype=np.dtype('d'))).reshape(4, 3) + poses_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for _ in range(poses_size): + R = np.asarray(np.frombuffer(mvs.read(72), dtype=np.dtype('d'))).reshape(3, 3).tolist() + C = np.asarray(np.frombuffer(mvs.read(24), dtype=np.dtype('d'))).tolist() + data['platforms'][platform_index]['cameras'][camera_index]['poses'].append({'R': R, 'C': C}) + + images_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for image_index in range(images_size): + name_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + name = mvs.read(name_size).decode() + data['images'].append({'name': name}) + if version > 4: + mask_name_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + mask_name = mvs.read(mask_name_size).decode() + data['images'][image_index].update({'mask_name': mask_name}) + platform_id, camera_id, pose_id = np.frombuffer(mvs.read(12), dtype=np.dtype('I')).tolist() + data['images'][image_index].update({'platform_id': platform_id, 'camera_id': camera_id, 'pose_id': pose_id}) + if version > 2: + id = np.frombuffer(mvs.read(4), dtype=np.dtype('I')).tolist()[0] + data['images'][image_index].update({'id': id}) + if version > 6: + min_depth, avg_depth, max_depth = np.frombuffer(mvs.read(12), dtype=np.dtype('f')).tolist() + data['images'][image_index].update({'min_depth': min_depth, 'avg_depth': avg_depth, 'max_depth': max_depth, 'view_scores': []}) + view_score_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for _ in range(view_score_size): + id, points = np.frombuffer(mvs.read(8), dtype=np.dtype('I')).tolist() + scale, angle, area, score = np.frombuffer(mvs.read(16), dtype=np.dtype('f')).tolist() + data['images'][image_index]['view_scores'].append({'id': id, 'points': points, 'scale': scale, 'angle': angle, 'area': area, 'score': score}) + + vertices_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for vertex_index in range(vertices_size): + X = np.frombuffer(mvs.read(12), dtype=np.dtype('f')).tolist() + data['vertices'].append({'X': X, 'views': []}) + views_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for _ in range(views_size): + image_id = np.frombuffer(mvs.read(4), dtype=np.dtype('I')).tolist()[0] + confidence = np.frombuffer(mvs.read(4), dtype=np.dtype('f')).tolist()[0] + data['vertices'][vertex_index]['views'].append({'image_id': image_id, 'confidence': confidence}) + + vertices_normal_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for _ in range(vertices_normal_size): + normal = np.frombuffer(mvs.read(12), dtype=np.dtype('f')).tolist() + data['vertices_normal'].append(normal) + + vertices_color_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for _ in range(vertices_color_size): + color = np.frombuffer(mvs.read(3), dtype=np.dtype('B')).tolist() + data['vertices_color'].append(color) + + if version > 0: + data.update({'lines': [], 'lines_normal': [], 'lines_color': []}) + lines_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for line_index in range(lines_size): + pt1 = np.frombuffer(mvs.read(12), dtype=np.dtype('f')).tolist() + pt2 = np.frombuffer(mvs.read(12), dtype=np.dtype('f')).tolist() + data['lines'].append({'pt1': pt1, 'pt2': pt2, 'views': []}) + views_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for _ in range(views_size): + image_id = np.frombuffer(mvs.read(4), dtype=np.dtype('I')).tolist()[0] + confidence = np.frombuffer(mvs.read(4), dtype=np.dtype('f')).tolist()[0] + data['lines'][line_index]['views'].append({'image_id': image_id, 'confidence': confidence}) + lines_normal_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for _ in range(lines_normal_size): + normal = np.frombuffer(mvs.read(12), dtype=np.dtype('f')).tolist() + data['lines_normal'].append(normal) + lines_color_size = np.frombuffer(mvs.read(8), dtype=np.dtype('Q'))[0] + for _ in range(lines_color_size): + color = np.frombuffer(mvs.read(3), dtype=np.dtype('B')).tolist() + data['lines_color'].append(color) + if version > 1: + transform = np.frombuffer(mvs.read(128), dtype=np.dtype('d')).reshape(4, 4).tolist() + data.update({'transform': transform}) + if version > 5: + rot = np.frombuffer(mvs.read(72), dtype=np.dtype('d')).reshape(3, 3).tolist() + pt_min = np.frombuffer(mvs.read(24), dtype=np.dtype('d')).tolist() + pt_max = np.frombuffer(mvs.read(24), dtype=np.dtype('d')).tolist() + data.update({'obb': {'rot': rot, 'pt_min': pt_min, 'pt_max': pt_max}}) + + return data diff --git a/vcpkg.json b/vcpkg.json index a74f701ea..6c4891c03 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "openmvs", - "version": "2.1.0", + "version": "2.3.0", "description": "OpenMVS: open Multi-View Stereo reconstruction library", "homepage": "https://cdcseacave.github.io/openMVS", "dependencies": [ @@ -17,8 +17,14 @@ "glew", "glfw3", "libpng", - "opencv", - "opengl", + { + "name": "opencv", + "features": [ + "eigen", + "openexr" + ] + }, + "opengl", "tiff", "vcglib", "zlib"