diff --git a/.appveyor.yml b/.appveyor.yml index 84d82a06f..5b963bae5 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -48,18 +48,17 @@ for: # remove outdated versions - vcpkg remove --outdated --recurse # install dependencies - - vcpkg install --recurse --triplet %VCPKG_ARCH% zlib boost-iostreams boost-program-options boost-system boost-serialization eigen3 cgal[core] opencv glew glfw3 - - vcpkg integrate install + - vcpkg install --recurse yasm-tool:x86-windows + - vcpkg install --recurse --triplet %VCPKG_ARCH% zlib boost-iostreams boost-program-options boost-system boost-serialization eigen3 cgal[core] opencv vcglib glew glfw3 - cd "%APPVEYOR_BUILD_FOLDER%" # preserve contents of selected directories and files across project builds cache: - 'C:\tools\vcpkg\installed' build_script: - - git clone https://github.com/cdcseacave/VCG.git - if "%platform%"=="Win32" set CMAKE_GENERATOR=-G"Visual Studio 15 2017" - if "%platform%"=="x64" set CMAKE_GENERATOR=-G"Visual Studio 15 2017 Win64" - mkdir bin && cd bin - - cmake %CMAKE_GENERATOR% -DCMAKE_BUILD_TYPE=%Configuration% -DCMAKE_TOOLCHAIN_FILE="C:\tools\vcpkg\scripts\buildsystems\vcpkg.cmake" -DVCG_ROOT="%APPVEYOR_BUILD_FOLDER%\VCG" .. + - cmake %CMAKE_GENERATOR% -DCMAKE_BUILD_TYPE=%Configuration% -DCMAKE_TOOLCHAIN_FILE="C:\tools\vcpkg\scripts\buildsystems\vcpkg.cmake" .. - cmake --build . --target ALL_BUILD --config %Configuration% -- /maxcpucount:4 - #------------------ @@ -85,7 +84,7 @@ for: - '/usr/lib/x86_64-linux-gnu/' build_script: - git clone https://github.com/cdcseacave/VCG.git - - git clone --single-branch --branch 3.2 https://gitlab.com/libeigen/eigen.git + - git clone --single-branch --branch 3.4 https://gitlab.com/libeigen/eigen.git - mkdir eigen_build && cd eigen_build - cmake . ../eigen - make && sudo make install diff --git a/.gitignore b/.gitignore index 1880d2e51..a12459285 100644 --- a/.gitignore +++ b/.gitignore @@ -35,5 +35,4 @@ CMakeSettings.json .idea/ .vscode/ out/ -bin/ -binaries/ +bin*/ diff --git a/BUILD.md b/BUILD.md index 52e4dc0b8..4ddcd4b81 100644 --- a/BUILD.md +++ b/BUILD.md @@ -3,7 +3,7 @@ Dependencies ------------ *OpenMVS* relies on a number of open source libraries, some of which are optional. For details on customizing the build process, see the compilation instructions. -* [Eigen](http://eigen.tuxfamily.org) version 3.2 (or higher on Windows only) +* [Eigen](http://eigen.tuxfamily.org) version 3.4 or higher * [OpenCV](http://opencv.org) version 2.4 or higher * [Ceres](http://ceres-solver.org) version 1.10 or higher * [CGAL](http://www.cgal.org) version 4.2 or higher @@ -33,7 +33,7 @@ cd OpenMVS #Get and install dependencies using vcpkg; #choose the desired triplet, like "x64-windows", by setting the VCPKG_DEFAULT_TRIPLET environment variable or by specifying it after each package: -vcpkg install zlib boost-iostreams boost-program-options boost-system boost-serialization eigen3 cgal[core] opencv glew glfw3 +vcpkg install zlib boost-iostreams boost-program-options boost-system boost-serialization eigen3 cgal[core] opencv vcglib glew glfw3 #Get VCGLib (Required): git clone https://github.com/cdcseacave/VCG.git @@ -46,7 +46,7 @@ mkdir build cd build #Run CMake, where VCPKG_ROOT environment variable points to the root of vcpkg installation: -cmake . ..\src -G "Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DVCG_ROOT="..\VCG" +cmake . ..\src -G "Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows #Open the solution in MSVC and build it ``` @@ -64,7 +64,7 @@ sudo apt-get -y install git cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mes main_path=`pwd` #Eigen (Required) -git clone https://gitlab.com/libeigen/eigen.git --branch 3.2 +git clone https://gitlab.com/libeigen/eigen.git --branch 3.4 mkdir eigen_build && cd eigen_build cmake . ../eigen make && sudo make install @@ -82,7 +82,7 @@ sudo apt-get -y install libcgal-dev libcgal-qt5-dev #VCGLib (Required) git clone https://github.com/cdcseacave/VCG.git vcglib -#Ceres (optional) +#Ceres (Optional) sudo apt-get -y install libatlas-base-dev libsuitesparse-dev git clone https://ceres-solver.googlesource.com/ceres-solver ceres-solver mkdir ceres_build && cd ceres_build @@ -128,7 +128,11 @@ git clone https://github.com/cdcseacave/openMVS.git #Build OpenMVS mkdir openMVS_build && cd openMVS_build -cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_DIR="$main_path/vcglib" +cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_ROOT="$main_path/vcglib" + +#Alternatively, build using XCode +cmake . ../openMVS -G "Xcode" -DCMAKE_BUILD_TYPE=Release -DVCG_ROOT="$main_path/vcglib" +xcodebuild -configuration Release #If you want to use OpenMVS as shared library, add to the CMake command: -DBUILD_SHARED_LIBS=ON @@ -136,3 +140,24 @@ cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_DIR="$main_path/vcglib" #Install OpenMVS library (optional): make && sudo make install ``` + +------------------- +Library usage +------------------- + +In order to use *OpenMVS* as a third-party libray in your project, first compile it as described above or simply use `vcpgk`: +``` +vcpkg install openmvs +``` + +And inside your project CMake script, use: +``` +find_package(OpenMVS) +if(OpenMVS_FOUND) + include_directories(${OpenMVS_INCLUDE_DIRS}) + add_definitions(${OpenMVS_DEFINITIONS}) +endif() + +add_executable(your_project source_code.cpp) +target_link_libraries(your_project PRIVATE OpenMVS::MVS) +``` diff --git a/CMakeLists.txt b/CMakeLists.txt index fff705bda..074e153b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,12 +4,23 @@ # # Project-wide settings CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0) -if(POLICY CMP0011) - cmake_policy(SET CMP0011 NEW) -endif() -if(POLICY CMP0074) - cmake_policy(SET CMP0074 NEW) -endif() +IF(POLICY CMP0011) + CMAKE_POLICY(SET CMP0011 NEW) +ENDIF() +IF(POLICY CMP0074) + CMAKE_POLICY(SET CMP0074 NEW) +ENDIF() +IF(POLICY CMP0104) + CMAKE_POLICY(SET CMP0104 NEW) +ENDIF() + +# Load automatically VCPKG toolchain if available +IF(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{VCPKG_ROOT}) + SET(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") + IF(NOT DEFINED VCPKG_TARGET_TRIPLET AND DEFINED ENV{VCPKG_DEFAULT_TRIPLET}) + SET(VCPKG_TARGET_TRIPLET "$ENV{VCPKG_DEFAULT_TRIPLET}" CACHE STRING "") + ENDIF() +ENDIF() # Name of the project. # @@ -18,25 +29,27 @@ endif() # ${OpenMVS_BINARY_DIR}. PROJECT(OpenMVS) -set(OpenMVS_MAJOR_VERSION 1) -set(OpenMVS_MINOR_VERSION 1) -set(OpenMVS_PATCH_VERSION 1) -set(OpenMVS_VERSION ${OpenMVS_MAJOR_VERSION}.${OpenMVS_MINOR_VERSION}.${OpenMVS_PATCH_VERSION}) - -# Find dependencies: -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/build/Modules) +SET(OpenMVS_MAJOR_VERSION 1) +SET(OpenMVS_MINOR_VERSION 1) +SET(OpenMVS_PATCH_VERSION 1) +SET(OpenMVS_VERSION ${OpenMVS_MAJOR_VERSION}.${OpenMVS_MINOR_VERSION}.${OpenMVS_PATCH_VERSION}) -# fix CMake IntDir variable -if(MSVC AND "${MSVC_VERSION}" STRGREATER "1500") - SET(CMAKE_CFG_INTDIR "$(Platform)/$(Configuration)") -endif() -SET(COTIRE_INTDIR "cotire") +# List configuration options +SET(OpenMVS_BUILD_TOOLS ON CACHE BOOL "Build example applications") +SET(OpenMVS_USE_NONFREE ON CACHE BOOL "Build non-free (patented) functionality") +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_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_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_SSE ON CACHE BOOL "Enable SSE optimizations") +SET(OpenMVS_MAX_CUDA_COMPATIBILITY OFF CACHE BOOL "Build for maximum CUDA device compatibility") # Define helper functions and macros. INCLUDE(build/Utils.cmake) -if(ENABLE_PRECOMPILED_HEADERS) - INCLUDE(build/Cotire.cmake) -endif() # Init session with macros defined in Utils.cmake GetOperatingSystemArchitectureBitness(SYSTEM) @@ -44,29 +57,20 @@ ComposePackageLibSuffix() ConfigCompilerAndLinker() ConfigLibrary() -# List configuration options -SET(OpenMVS_USE_NONFREE ON CACHE BOOL "Build non-free (patented) functionality") -SET(OpenMVS_USE_CERES OFF CACHE BOOL "Enable CERES optimization library") -SET(OpenMVS_USE_FAST_FLOAT2INT ON 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_SSE ON CACHE BOOL "Enable SSE optimizations") -SET(OpenMVS_USE_OPENMP ON CACHE BOOL "Enable OpenMP library") -SET(OpenMVS_USE_OPENGL ON CACHE BOOL "Enable OpenGL library") -SET(OpenMVS_USE_CUDA ON CACHE BOOL "Enable CUDA library") -SET(OpenMVS_USE_BREAKPAD ON CACHE BOOL "Enable BreakPad library") -SET(OpenMVS_CONFIG_INCLUDE_DIR "${CMAKE_BINARY_DIR}/" CACHE PATH "Where to create the build/platform specific header") - -INCLUDE_DIRECTORIES("${OpenMVS_SOURCE_DIR}") +# Find dependencies: +SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/Modules) # Find required packages +SET(OpenMVS_EXTRA_INCLUDES "") +SET(OpenMVS_DEFINITIONS "") SET(OpenMVS_EXTRA_LIBS "") + if(OpenMVS_USE_OPENMP) SET(OpenMP_LIBS "") FIND_PACKAGE(OpenMP) if(OPENMP_FOUND) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - ADD_DEFINITIONS(-D_USE_OPENMP) + LIST(APPEND OpenMVS_DEFINITIONS -D_USE_OPENMP) SET(_USE_OPENMP TRUE) #cmake only check for separate OpenMP library on AppleClang 7+ #https://github.com/Kitware/CMake/blob/42212f7539040139ecec092547b7d58ef12a4d72/Modules/FindOpenMP.cmake#L252 @@ -75,7 +79,7 @@ if(OpenMVS_USE_OPENMP) LIST(APPEND OpenMVS_EXTRA_LIBS ${OpenMP_LIBS}) endif() else() - message("-- Can't find OpenMP. Continuing without it.") + MESSAGE("-- Can't find OpenMP. Continuing without it.") endif() endif() @@ -86,7 +90,8 @@ if(OpenMVS_USE_OPENGL) FIND_PACKAGE(OpenGL) if(OPENGL_FOUND) INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIR}) - ADD_DEFINITIONS(${OpenGL_DEFINITIONS} -D_USE_OPENGL) + LIST(APPEND OpenMVS_DEFINITIONS -D_USE_OPENGL) + ADD_DEFINITIONS(${OpenGL_DEFINITIONS}) SET(_USE_OPENGL TRUE) else() MESSAGE("-- Can't find OpenGL. Continuing without it.") @@ -96,8 +101,42 @@ endif() if(OpenMVS_USE_CUDA) FIND_PACKAGE(CUDA) if(CUDA_FOUND) + ENABLE_LANGUAGE(CUDA) + if(NOT OpenMVS_MAX_CUDA_COMPATIBILITY) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + SET(CMAKE_CUDA_ARCHITECTURES 75) + endif() + else() + # Build for maximum compatibility + # https://arnon.dk/matching-sm-architectures-arch-and-gencode-for-various-nvidia-cards/ + + # Extract list of arch and gencodes + EXECUTE_PROCESS(COMMAND "${CMAKE_CUDA_COMPILER}" --list-gpu-arch + OUTPUT_VARIABLE LIST_GPU_ARCH + ERROR_QUIET) + STRING(REPLACE "\r" "" LIST_GPU_ARCH ${LIST_GPU_ARCH}) + STRING(REPLACE "\n" ";" LIST_GPU_ARCH ${LIST_GPU_ARCH}) + + EXECUTE_PROCESS(COMMAND "${CMAKE_CUDA_COMPILER}" --list-gpu-code + OUTPUT_VARIABLE LIST_GPU_CODE + ERROR_QUIET) + STRING(REPLACE "\r" "" LIST_GPU_CODE ${LIST_GPU_CODE}) + STRING(REPLACE "\n" ";" LIST_GPU_CODE ${LIST_GPU_CODE}) + + LIST(GET LIST_GPU_CODE 0 TARGET_GPU_CODE) + SET(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -arch=${TARGET_GPU_CODE}") + + SET(IDX 0) + foreach(GPU_ARCH ${LIST_GPU_ARCH}) + LIST(GET LIST_GPU_CODE ${IDX} GPU_CODE) + SET(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -gencode=arch=${GPU_ARCH},code=${GPU_CODE}") + MATH(EXPR IDX "${IDX}+1") + endforeach() + MESSAGE("-- Set CUDA flags: " ${CMAKE_CUDA_FLAGS}) + endif() + SET(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") INCLUDE_DIRECTORIES(${CUDA_INCLUDE_DIRS}) - ADD_DEFINITIONS(-D_USE_CUDA) + LIST(APPEND OpenMVS_DEFINITIONS -D_USE_CUDA) SET(_USE_CUDA TRUE) else() SET(CUDA_CUDA_LIBRARY "") @@ -111,7 +150,8 @@ if(OpenMVS_USE_BREAKPAD) FIND_PACKAGE(BREAKPAD) if(BREAKPAD_FOUND) INCLUDE_DIRECTORIES(${BREAKPAD_INCLUDE_DIRS}) - ADD_DEFINITIONS(${BREAKPAD_DEFINITIONS} -D_USE_BREAKPAD) + LIST(APPEND OpenMVS_DEFINITIONS -D_USE_BREAKPAD) + ADD_DEFINITIONS(${BREAKPAD_DEFINITIONS}) SET(_USE_BREAKPAD TRUE) LIST(APPEND OpenMVS_EXTRA_LIBS ${BREAKPAD_LIBS}) else() @@ -119,23 +159,29 @@ if(OpenMVS_USE_BREAKPAD) endif() endif() -FIND_PACKAGE(Boost ${SYSTEM_PACKAGE_REQUIRED} COMPONENTS iostreams program_options system serialization) +FIND_PACKAGE(Boost COMPONENTS iostreams program_options system serialization REQUIRED) if(Boost_FOUND) + LIST(APPEND OpenMVS_EXTRA_INCLUDES ${Boost_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) - ADD_DEFINITIONS(${Boost_DEFINITIONS} -D_USE_BOOST) + LIST(APPEND OpenMVS_DEFINITIONS -D_USE_BOOST) + ADD_DEFINITIONS(${Boost_DEFINITIONS}) LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) SET(_USE_BOOST TRUE) endif() -FIND_PACKAGE(Eigen ${SYSTEM_PACKAGE_REQUIRED}) -if(EIGEN_FOUND) - INCLUDE_DIRECTORIES(${EIGEN_INCLUDE_DIRS}) - ADD_DEFINITIONS(${EIGEN_DEFINITIONS} -D_USE_EIGEN) +FIND_PACKAGE(Eigen3 REQUIRED) +if(EIGEN3_FOUND) + LIST(APPEND OpenMVS_EXTRA_INCLUDES ${EIGEN3_INCLUDE_DIR}) + INCLUDE_DIRECTORIES(${EIGEN3_INCLUDE_DIR}) + LIST(APPEND OpenMVS_DEFINITIONS -D_USE_EIGEN) + ADD_DEFINITIONS(${EIGEN3_DEFINITIONS}) SET(_USE_EIGEN TRUE) + MESSAGE(STATUS "Eigen ${EIGEN3_VERSION} found (include: ${EIGEN3_INCLUDE_DIR})") endif() -FIND_PACKAGE(OpenCV ${SYSTEM_PACKAGE_REQUIRED}) +FIND_PACKAGE(OpenCV REQUIRED) if(OpenCV_FOUND) + LIST(APPEND OpenMVS_EXTRA_INCLUDES ${OpenCV_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS}) ADD_DEFINITIONS(${OpenCV_DEFINITIONS}) SET(_USE_OPENCV TRUE) @@ -144,8 +190,10 @@ else() MESSAGE("-- Can't find OpenCV. Please specify OpenCV directory using OpenCV_DIR variable") endif() +LIST(REMOVE_DUPLICATES OpenMVS_EXTRA_INCLUDES) +LIST(REMOVE_DUPLICATES OpenMVS_EXTRA_LIBS) + # Set defines -SET(OpenMVS_DEFINITIONS "") if(OpenMVS_USE_NONFREE) LIST(APPEND OpenMVS_DEFINITIONS -D_USE_NONFREE) SET(_USE_NONFREE TRUE) @@ -166,54 +214,58 @@ if(OpenMVS_USE_SSE) LIST(APPEND OpenMVS_DEFINITIONS -D_USE_SSE) SET(_USE_SSE TRUE) endif() -ADD_DEFINITIONS(${OpenMVS_DEFINITIONS}) - -# Add modules -ADD_SUBDIRECTORY(libs) -ADD_SUBDIRECTORY(apps) -ADD_SUBDIRECTORY(docs) - if(OpenMVS_USE_CERES) + LIST(APPEND OpenMVS_DEFINITIONS -D_USE_CERES) SET(_USE_CERES TRUE) endif() -# Set configuration file -CONFIGURE_FILE("${OpenMVS_SOURCE_DIR}/build/Templates/ConfigLocal.h.in" "${OpenMVS_CONFIG_INCLUDE_DIR}/ConfigLocal.h") -INSTALL(FILES "${OpenMVS_CONFIG_INCLUDE_DIR}/ConfigLocal.h" DESTINATION "${INSTALL_INCLUDE_DIR}") -INCLUDE_DIRECTORIES(${OpenMVS_CONFIG_INCLUDE_DIR}) +ADD_DEFINITIONS(${OpenMVS_DEFINITIONS}) -# Add all targets to the build-tree export set -export(TARGETS Common IO Math MVS FILE "${PROJECT_BINARY_DIR}/OpenMVSTargets.cmake") +INCLUDE_DIRECTORIES("${OpenMVS_SOURCE_DIR}") +INCLUDE_DIRECTORIES("${CMAKE_BINARY_DIR}") + +# Add modules +ADD_SUBDIRECTORY(libs) +if(OpenMVS_BUILD_TOOLS) + ADD_SUBDIRECTORY(apps) +endif() +ADD_SUBDIRECTORY(docs) # Export the package for use from the build-tree # (this registers the build-tree with a global CMake-registry) export(PACKAGE OpenMVS) - + +# Install the export set for use with the install-tree +INSTALL(EXPORT OpenMVSTargets + NAMESPACE OpenMVS:: + DESTINATION "${INSTALL_CMAKE_DIR}") + +# Install configuration file +CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/build/Templates/ConfigLocal.h.in" "${CMAKE_BINARY_DIR}/ConfigLocal.h") +INSTALL(FILES "${CMAKE_BINARY_DIR}/ConfigLocal.h" DESTINATION "${INSTALL_INCLUDE_DIR}") + # Create the OpenMVSConfig.cmake and OpenMVSConfigVersion files -file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}" "${INSTALL_INCLUDE_DIR}") -# ... for the build tree -set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}") -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/build/OpenMVSConfig.cmake.in" "${PROJECT_BINARY_DIR}/OpenMVSConfig.cmake" @ONLY) -# ... for the install tree -set(CONF_INCLUDE_DIRS "${INSTALL_CMAKE_DIR}/${REL_INCLUDE_DIR}") -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/build/OpenMVSConfig.cmake.in" "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMVSConfig.cmake" @ONLY) -# ... for both -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/build/OpenMVSConfigVersion.cmake.in" "${PROJECT_BINARY_DIR}/OpenMVSConfigVersion.cmake" @ONLY) - +INCLUDE(CMakePackageConfigHelpers) +write_basic_package_version_file("${PROJECT_BINARY_DIR}/OpenMVSConfigVersion.cmake" + VERSION ${OpenMVS_VERSION} + COMPATIBILITY AnyNewerVersion) +SET(INSTALL_INCLUDE_DIR_IN ${INSTALL_INCLUDE_DIR_PREFIX} ${OpenMVS_EXTRA_INCLUDES}) +SET(INSTALL_CMAKE_DIR_IN ${INSTALL_CMAKE_DIR_PREFIX}) +configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/build/Templates/OpenMVSConfig.cmake.in" + "${PROJECT_BINARY_DIR}/OpenMVSConfig.cmake" + INSTALL_DESTINATION ${PROJECT_BINARY_DIR} + NO_SET_AND_CHECK_MACRO) # Install the OpenMVSConfig.cmake and OpenMVSConfigVersion.cmake -install(FILES - "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMVSConfig.cmake" +INSTALL(FILES + "${PROJECT_BINARY_DIR}/OpenMVSConfig.cmake" "${PROJECT_BINARY_DIR}/OpenMVSConfigVersion.cmake" - DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev) - -# Install the export set for use with the install-tree -install(EXPORT OpenMVSTargets DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev) + DESTINATION "${INSTALL_CMAKE_DIR}") # uninstall target -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake_uninstall.cmake.in" +CONFIGURE_FILE( + "${CMAKE_CURRENT_SOURCE_DIR}/build/Templates/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) -add_custom_target(uninstall +ADD_CUSTOM_TARGET(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) diff --git a/MvgMvsPipeline.py b/MvgMvsPipeline.py index 106857f7e..05004bc4d 100644 --- a/MvgMvsPipeline.py +++ b/MvgMvsPipeline.py @@ -12,25 +12,28 @@ [--9 9 [9 ...]] [--10 10 [10 ...]] [--11 11 [11 ...]] [--12 12 [12 ...]] [--13 13 [13 ...]] [--14 14 [14 ...]] [--15 15 [15 ...]] + [--16 16 [16 ...]] [--17 17 [17 ...]] input_dir output_dir Photogrammetry reconstruction with these steps: 0. Intrinsics analysis openMVG_main_SfMInit_ImageListing 1. Compute features openMVG_main_ComputeFeatures - 2. Compute matches openMVG_main_ComputeMatches - 3. Incremental reconstruction openMVG_main_IncrementalSfM - 4. Global reconstruction openMVG_main_GlobalSfM - 5. Colorize Structure openMVG_main_ComputeSfM_DataColor - 6. Structure from Known Poses openMVG_main_ComputeStructureFromKnownPoses - 7. Colorized robust triangulation openMVG_main_ComputeSfM_DataColor - 8. Control Points Registration ui_openMVG_control_points_registration - 9. Export to openMVS openMVG_main_openMVG2openMVS - 10. Densify point-cloud DensifyPointCloud - 11. Reconstruct the mesh ReconstructMesh - 12. Refine the mesh RefineMesh - 13. Texture the mesh TextureMesh - 14. Estimate disparity-maps DensifyPointCloud - 15. Fuse disparity-maps DensifyPointCloud + 2. Compute pairs openMVG_main_PairGenerator + 3. Compute matches openMVG_main_ComputeMatches + 4. Filter matches openMVG_main_GeometricFilter + 5. Incremental reconstruction openMVG_main_IncrementalSfM + 6. Global reconstruction openMVG_main_GlobalSfM + 7. Colorize Structure openMVG_main_ComputeSfM_DataColor + 8. Structure from Known Poses openMVG_main_ComputeStructureFromKnownPoses + 9. Colorized robust triangulation openMVG_main_ComputeSfM_DataColor + 10. Control Points Registration ui_openMVG_control_points_registration + 11. Export to openMVS openMVG_main_openMVG2openMVS + 12. Densify point-cloud DensifyPointCloud + 13. Reconstruct the mesh ReconstructMesh + 14. Refine the mesh RefineMesh + 15. Texture the mesh TextureMesh + 16. Estimate disparity-maps DensifyPointCloud + 17. Fuse disparity-maps DensifyPointCloud positional arguments: input_dir the directory wich contains the pictures set. @@ -40,11 +43,12 @@ -h, --help show this help message and exit --steps STEPS [STEPS ...] steps to process --preset PRESET steps list preset in - SEQUENTIAL = [0, 1, 2, 3, 9, 10, 11, 12, 13] - GLOBAL = [0, 1, 2, 4, 9, 10, 11, 12, 13] - MVG_SEQ = [0, 1, 2, 3, 5, 6, 7] - MVG_GLOBAL = [0, 1, 2, 4, 5, 6, 7] - MVS_SGM = [14, 15] + SEQUENTIAL = [0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15] + GLOBAL = [0, 1, 2, 3, 4, 6, 11, 12, 13, 14, 15] + MVG_SEQ = [0, 1, 2, 3, 4, 5, 7, 8, 9] + MVG_GLOBAL = [0, 1, 2, 3, 4, 6, 7, 8, 9] + MVS = [11, 12, 13, 14, 15] + MVS_SGM = [16, 17] default : SEQUENTIAL Passthrough: @@ -113,11 +117,12 @@ def find(afile): CAMERA_SENSOR_DB_DIRECTORY = input("openMVG camera database (%s) folder?\n" % CAMERA_SENSOR_DB_FILE) -PRESET = {'SEQUENTIAL': [0, 1, 2, 3, 9, 10, 11, 12, 13], - 'GLOBAL': [0, 1, 2, 4, 9, 10, 11, 12, 13], - 'MVG_SEQ': [0, 1, 2, 3, 5, 6, 7], - 'MVG_GLOBAL': [0, 1, 2, 4, 5, 6, 7], - 'MVS_SGM': [14, 15]} +PRESET = {'SEQUENTIAL': [0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15], + 'GLOBAL': [0, 1, 2, 3, 4, 6, 11, 12, 13, 14, 15], + 'MVG_SEQ': [0, 1, 2, 3, 4, 5, 7, 8, 9], + 'MVG_GLOBAL': [0, 1, 2, 3, 4, 6, 7, 8, 9], + 'MVS': [11, 12, 13, 14, 15], + 'MVS_SGM': [16, 17]} PRESET_DEFAULT = 'SEQUENTIAL' @@ -183,47 +188,53 @@ def __init__(self): ["-i", "%input_dir%", "-o", "%matches_dir%", "-d", "%camera_file_params%"]], ["Compute features", # 1 os.path.join(OPENMVG_BIN, "openMVG_main_ComputeFeatures"), - ["-i", "%matches_dir%/sfm_data.json", "-o", "%matches_dir%", "-m", "SIFT", "-n", "4"]], - ["Compute matches", # 2 + ["-i", "%matches_dir%/sfm_data.json", "-o", "%matches_dir%", "-m", "SIFT"]], + ["Compute pairs", # 2 + os.path.join(OPENMVG_BIN, "openMVG_main_PairGenerator"), + ["-i", "%matches_dir%/sfm_data.json", "-o", "%matches_dir%/pairs.bin"]], + ["Compute matches", # 3 os.path.join(OPENMVG_BIN, "openMVG_main_ComputeMatches"), - ["-i", "%matches_dir%/sfm_data.json", "-o", "%matches_dir%", "-n", "HNSWL2", "-r", ".8"]], - ["Incremental reconstruction", # 3 - os.path.join(OPENMVG_BIN, "openMVG_main_IncrementalSfM"), - ["-i", "%matches_dir%/sfm_data.json", "-m", "%matches_dir%", "-o", "%reconstruction_dir%"]], - ["Global reconstruction", # 4 - os.path.join(OPENMVG_BIN, "openMVG_main_GlobalSfM"), - ["-i", "%matches_dir%/sfm_data.json", "-m", "%matches_dir%", "-o", "%reconstruction_dir%"]], - ["Colorize Structure", # 5 + ["-i", "%matches_dir%/sfm_data.json", "-p", "%matches_dir%/pairs.bin", "-o", "%matches_dir%/matches.putative.bin", "-n", "AUTO"]], + ["Filter matches", # 4 + os.path.join(OPENMVG_BIN, "openMVG_main_GeometricFilter"), + ["-i", "%matches_dir%/sfm_data.json", "-m", "%matches_dir%/matches.putative.bin", "-o", "%matches_dir%/matches.f.bin"]], + ["Incremental reconstruction", # 5 + os.path.join(OPENMVG_BIN, "openMVG_main_SfM"), + ["-i", "%matches_dir%/sfm_data.json", "-m", "%matches_dir%", "-o", "%reconstruction_dir%", "-s", "INCREMENTAL"]], + ["Global reconstruction", # 6 + os.path.join(OPENMVG_BIN, "openMVG_main_SfM"), + ["-i", "%matches_dir%/sfm_data.json", "-m", "%matches_dir%", "-o", "%reconstruction_dir%", "-s", "GLOBAL", "-M", "%matches_dir%/matches.e.bin"]], + ["Colorize Structure", # 7 os.path.join(OPENMVG_BIN, "openMVG_main_ComputeSfM_DataColor"), ["-i", "%reconstruction_dir%/sfm_data.bin", "-o", "%reconstruction_dir%/colorized.ply"]], - ["Structure from Known Poses", # 6 + ["Structure from Known Poses", # 8 os.path.join(OPENMVG_BIN, "openMVG_main_ComputeStructureFromKnownPoses"), ["-i", "%reconstruction_dir%/sfm_data.bin", "-m", "%matches_dir%", "-f", "%matches_dir%/matches.f.bin", "-o", "%reconstruction_dir%/robust.bin"]], - ["Colorized robust triangulation", # 7 + ["Colorized robust triangulation", # 9 os.path.join(OPENMVG_BIN, "openMVG_main_ComputeSfM_DataColor"), ["-i", "%reconstruction_dir%/robust.bin", "-o", "%reconstruction_dir%/robust_colorized.ply"]], - ["Control Points Registration", # 8 + ["Control Points Registration", # 10 os.path.join(OPENMVG_BIN, "ui_openMVG_control_points_registration"), ["-i", "%reconstruction_dir%/sfm_data.bin"]], - ["Export to openMVS", # 9 + ["Export to openMVS", # 11 os.path.join(OPENMVG_BIN, "openMVG_main_openMVG2openMVS"), ["-i", "%reconstruction_dir%/sfm_data.bin", "-o", "%mvs_dir%/scene.mvs", "-d", "%mvs_dir%/images"]], - ["Densify point cloud", # 10 + ["Densify point cloud", # 12 os.path.join(OPENMVS_BIN, "DensifyPointCloud"), ["scene.mvs", "--dense-config-file", "Densify.ini", "--resolution-level", "1", "-w", "%mvs_dir%"]], - ["Reconstruct the mesh", # 11 + ["Reconstruct the mesh", # 13 os.path.join(OPENMVS_BIN, "ReconstructMesh"), ["scene_dense.mvs", "-w", "%mvs_dir%"]], - ["Refine the mesh", # 12 + ["Refine the mesh", # 14 os.path.join(OPENMVS_BIN, "RefineMesh"), ["scene_dense_mesh.mvs", "--scales", "2", "-w", "%mvs_dir%"]], - ["Texture the mesh", # 13 + ["Texture the mesh", # 15 os.path.join(OPENMVS_BIN, "TextureMesh"), ["scene_dense_mesh_refine.mvs", "--decimate", "0.5", "-w", "%mvs_dir%"]], - ["Estimate disparity-maps", # 14 + ["Estimate disparity-maps", # 16 os.path.join(OPENMVS_BIN, "DensifyPointCloud"), ["scene.mvs", "--dense-config-file", "Densify.ini", "--fusion-mode", "-1", "-w", "%mvs_dir%"]], - ["Fuse disparity-maps", # 15 + ["Fuse disparity-maps", # 17 os.path.join(OPENMVS_BIN, "DensifyPointCloud"), ["scene.mvs", "--dense-config-file", "Densify.ini", "--fusion-mode", "-2", "-w", "%mvs_dir%"]] ] @@ -322,10 +333,11 @@ def mkdir_ine(dirname): print("# output dir: %s" % CONF.output_dir) print("# Steps: %s" % str(CONF.steps)) -if 2 in CONF.steps: # ComputeMatches - if 4 in CONF.steps: # GlobalReconstruction +if 4 in CONF.steps: # GeometricFilter + if 6 in CONF.steps: # GlobalReconstruction # Set the geometric_model of ComputeMatches to Essential - STEPS[2].opt.extend(["-g", "e"]) + STEPS[4].opt = STEPS[4].opt.replace("/matches.f.bin", "/matches.e.bin") + STEPS[4].opt.extend(["-g", "e"]) for cstep in CONF.steps: printout("#%i. %s" % (cstep, STEPS[cstep].info), effect=INVERSE) diff --git a/MvgOptimizeSfM.py b/MvgOptimizeSfM.py new file mode 100644 index 000000000..930d6805a --- /dev/null +++ b/MvgOptimizeSfM.py @@ -0,0 +1,77 @@ +#!/usr/bin/python3 +# -*- encoding: utf-8 -*- +""" +This script is for comparing the poses stored in an OpenMVS project to the poses optimized by OpenMVG + +usage: run 'MvgOptimizeSfM.py' in a sub-folder to the OpenMVS project folder containing + 'scene.mvs' and images stored in 'images' folder; structure ex: + + -OpenMVS_project + -images + -scene.mvs + -mvg + -run script here +""" + +import os +import sys +import subprocess + +if sys.platform.startswith('win'): + PATH_DELIM = ';' +else: + PATH_DELIM = ':' + +# add this script's directory to PATH +os.environ['PATH'] += PATH_DELIM + os.path.dirname(os.path.abspath(__file__)) + +# add current directory to PATH +os.environ['PATH'] += PATH_DELIM + os.getcwd() + + +def whereis(afile): + """ + return directory in which afile is, None if not found. Look in PATH + """ + if sys.platform.startswith('win'): + cmd = "where" + else: + cmd = "which" + try: + ret = subprocess.run([cmd, afile], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True) + return os.path.split(ret.stdout.decode())[0] + except subprocess.CalledProcessError: + return None + + +def launch(cmdline): + # Launch the current step + print('Cmd: ' + ' '.join(cmdline)) + try: + pStep = subprocess.Popen(cmdline) + pStep.wait() + if pStep.returncode != 0: + return + except KeyboardInterrupt: + sys.exit('\r\nProcess canceled by user, all files remains') + + +# Try to find openMVG and openMVS binaries in PATH +OPENMVG_BIN = whereis("openMVG_main_SfMInit_ImageListing") +OPENMVS_BIN = whereis("ReconstructMesh") + +# Ask user for openMVG and openMVS directories if not found +if not OPENMVG_BIN: + OPENMVG_BIN = input("openMVG binary folder?\n") +if not OPENMVS_BIN: + OPENMVS_BIN = input("openMVS binary folder?\n") + +launch([os.path.join(OPENMVS_BIN, 'InterfaceCOLMAP'), '../scene.mvs', '-o', '../gt_dense_cameras.camera']) +launch([os.path.join(OPENMVG_BIN, 'openMVG_main_SfMInit_ImageListingFromKnownPoses'), '-i', '../images', '-g', '../gt_dense_cameras', '-t', '1', '-o', '.']) +launch([os.path.join(OPENMVG_BIN, 'openMVG_main_ComputeFeatures'), '-i', 'sfm_data.json', '-o', '.']) +launch([os.path.join(OPENMVG_BIN, 'openMVG_main_ComputeMatches'), '-i', 'sfm_data.json', '-o', '.', '-m', '1']) +launch([os.path.join(OPENMVG_BIN, 'openMVG_main_ComputeStructureFromKnownPoses'), '-i', 'sfm_data.json', '-m', '.', '-o', 'sfm_data_struct.bin', '-b']) +launch([os.path.join(OPENMVG_BIN, 'openMVG_main_ComputeSfM_DataColor'), '-i', 'sfm_data_struct.bin', '-o', 'scene.ply']) +launch([os.path.join(OPENMVG_BIN, 'openMVG_main_openMVG2openMVS'), '-i', 'sfm_data_struct.bin', '-o', 'scene.mvs', '-d', 'images']) +launch([os.path.join(OPENMVS_BIN, 'InterfaceCOLMAP'), 'scene.mvs', '-o', 'cameras.camera']) +launch([os.path.join(OPENMVG_BIN, 'openMVG_main_evalQuality'), '-i', '..', '-c', 'sfm_data_struct.bin', '-o', 'compare']) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index dfc4f88f8..b24044839 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -1,6 +1,6 @@ # Add applications ADD_SUBDIRECTORY(InterfaceCOLMAP) -ADD_SUBDIRECTORY(InterfaceVisualSFM) +ADD_SUBDIRECTORY(InterfaceMetashape) ADD_SUBDIRECTORY(DensifyPointCloud) ADD_SUBDIRECTORY(ReconstructMesh) ADD_SUBDIRECTORY(RefineMesh) diff --git a/apps/DensifyPointCloud/CMakeLists.txt b/apps/DensifyPointCloud/CMakeLists.txt index 5dddebee4..38e27310b 100644 --- a/apps/DensifyPointCloud/CMakeLists.txt +++ b/apps/DensifyPointCloud/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(DensifyPointCloud "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(DensifyPointCloud "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS DensifyPointCloud diff --git a/apps/DensifyPointCloud/DensifyPointCloud.cpp b/apps/DensifyPointCloud/DensifyPointCloud.cpp index d82c07294..fabf96d00 100644 --- a/apps/DensifyPointCloud/DensifyPointCloud.cpp +++ b/apps/DensifyPointCloud/DensifyPointCloud.cpp @@ -43,14 +43,19 @@ using namespace MVS; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; String strOutputFileName; +String strViewNeighborsFileName; String strMeshFileName; String strDenseConfigFileName; +float fMaxSubsceneArea; float fSampleMesh; -int thFilterPointCloud; int nFusionMode; +int thFilterPointCloud; +int nExportNumViews; int nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -71,7 +76,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(2), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 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 @@ -86,6 +91,9 @@ bool Initialize(size_t argc, LPCTSTR* argv) ; // group of options allowed both on command line and in config file + #ifdef _USE_CUDA + unsigned nCUDADevice; + #endif unsigned nResolutionLevel; unsigned nMaxResolution; unsigned nMinResolution; @@ -93,26 +101,35 @@ bool Initialize(size_t argc, LPCTSTR* argv) unsigned nMinViewsFuse; unsigned nEstimateColors; unsigned nEstimateNormals; + int nIgnoreMaskLabel; 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") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the dense point-cloud") + ("view-neighbors-file", boost::program_options::value(&OPT::strViewNeighborsFileName), "input filename containing the list of views and their neighbors (optional)") + #ifdef _USE_CUDA + ("cuda-device", boost::program_options::value(&nCUDADevice)->default_value(0), "CUDA device number to be used for depth-map estimation (-1 - CPU processing)") + #endif ("resolution-level", boost::program_options::value(&nResolutionLevel)->default_value(1), "how many times to scale down the images before point cloud computation") ("max-resolution", boost::program_options::value(&nMaxResolution)->default_value(3200), "do not scale images higher than this resolution") ("min-resolution", boost::program_options::value(&nMinResolution)->default_value(640), "do not scale images lower than this resolution") ("number-views", boost::program_options::value(&nNumViews)->default_value(5), "number of views used for depth-map estimation (0 - all neighbor views available)") - ("number-views-fuse", boost::program_options::value(&nMinViewsFuse)->default_value(3), "minimum number of images that agrees with an estimate during fusion in order to consider it inlier") + ("number-views-fuse", boost::program_options::value(&nMinViewsFuse)->default_value(3), "minimum number of images that agrees with an estimate during fusion in order to consider it inlier (<2 - only merge depth-maps)") + ("ignore-mask-label", boost::program_options::value(&nIgnoreMaskLabel)->default_value(-1), "integer value for the label to ignore in the segmentation mask (<0 - disabled)") ("estimate-colors", boost::program_options::value(&nEstimateColors)->default_value(2), "estimate the colors for the dense point-cloud (0 - disabled, 1 - final, 2 - estimate)") ("estimate-normals", boost::program_options::value(&nEstimateNormals)->default_value(2), "estimate the normals for the dense point-cloud (0 - disabled, 1 - final, 2 - estimate)") + ("sub-scene-area", boost::program_options::value(&OPT::fMaxSubsceneArea)->default_value(0.f), "split the scene in sub-scenes such that each sub-scene surface does not exceed the given maximum sampling area (0 - disabled)") ("sample-mesh", boost::program_options::value(&OPT::fSampleMesh)->default_value(0.f), "uniformly samples points on a mesh (0 - disabled, <0 - number of points, >0 - sample density per square unit)") - ("filter-point-cloud", boost::program_options::value(&OPT::thFilterPointCloud)->default_value(0), "filter dense point-cloud based on visibility (0 - disabled)") ("fusion-mode", boost::program_options::value(&OPT::nFusionMode)->default_value(0), "depth map fusion mode (-2 - fuse disparity-maps, -1 - export disparity-maps only, 0 - depth-maps & fusion, 1 - export depth-maps only)") + ("filter-point-cloud", boost::program_options::value(&OPT::thFilterPointCloud)->default_value(0), "filter dense point-cloud based on visibility (0 - disabled)") + ("export-number-views", boost::program_options::value(&OPT::nExportNumViews)->default_value(0), "export points with >= number of views (0 - disabled)") ; // 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 used for image pair overlap estimation") ("dense-config-file", boost::program_options::value(&OPT::strDenseConfigFileName), "optional configuration file for the densifier (overwritten by the command line options)") ; @@ -151,27 +168,30 @@ bool Initialize(size_t argc, LPCTSTR* argv) // validate input Util::ensureValidPath(OPT::strInputFileName); - Util::ensureUnifySlash(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; // initialize optional options Util::ensureValidPath(OPT::strOutputFileName); - Util::ensureUnifySlash(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) + Util::ensureValidPath(OPT::strViewNeighborsFileName); + Util::ensureValidPath(OPT::strMeshFileName); + if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_dense.mvs"); // init dense options - if (!OPT::strDenseConfigFileName.IsEmpty()) + if (!OPT::strDenseConfigFileName.empty()) OPT::strDenseConfigFileName = MAKE_PATH_SAFE(OPT::strDenseConfigFileName); OPTDENSE::init(); const bool bValidConfig(OPTDENSE::oConfig.Load(OPT::strDenseConfigFileName)); OPTDENSE::update(); + #ifdef _USE_CUDA + OPTDENSE::nCUDADevice = nCUDADevice; + #endif OPTDENSE::nResolutionLevel = nResolutionLevel; OPTDENSE::nMaxResolution = nMaxResolution; OPTDENSE::nMinResolution = nMinResolution; @@ -179,7 +199,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) OPTDENSE::nMinViewsFuse = nMinViewsFuse; OPTDENSE::nEstimateColors = nEstimateColors; OPTDENSE::nEstimateNormals = nEstimateNormals; - if (!bValidConfig && !OPT::strDenseConfigFileName.IsEmpty()) + OPTDENSE::nIgnoreMaskLabel = nIgnoreMaskLabel; + if (!bValidConfig && !OPT::strDenseConfigFileName.empty()) OPTDENSE::oConfig.Save(OPT::strDenseConfigFileName); // initialize global options @@ -211,6 +232,8 @@ void Finalize() CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -240,10 +263,20 @@ int main(int argc, LPCTSTR* argv) // load and estimate a dense point-cloud if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) return EXIT_FAILURE; - if (scene.pointcloud.IsEmpty()) { + if (!OPT::strMeshFileName.empty()) + scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName)); + if (scene.pointcloud.IsEmpty() && OPT::strViewNeighborsFileName.empty() && scene.mesh.IsEmpty()) { VERBOSE("error: empty initial point-cloud"); return EXIT_FAILURE; } + if (OPT::fMaxSubsceneArea > 0) { + // split the scene in sub-scenes by maximum sampling area + Scene::ImagesChunkArr chunks; + scene.Split(chunks, OPT::fMaxSubsceneArea); + scene.ExportChunks(chunks, Util::getFilePath(MAKE_PATH_SAFE(OPT::strOutputFileName)), (ARCHIVE_TYPE)OPT::nArchiveType); + Finalize(); + return EXIT_SUCCESS; + } if (OPT::thFilterPointCloud < 0) { // filter point-cloud based on camera-point visibility intersections scene.PointCloudFilter(OPT::thFilterPointCloud); @@ -253,7 +286,16 @@ int main(int argc, LPCTSTR* argv) Finalize(); return EXIT_SUCCESS; } + if (OPT::nExportNumViews && scene.pointcloud.IsValid()) { + // export point-cloud containing only points with N+ views + const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); + scene.pointcloud.SaveNViews(baseFileName+String::FormatString(_T("_%dviews.ply"), OPT::nExportNumViews), (IIndex)OPT::nExportNumViews); + Finalize(); + return EXIT_SUCCESS; + } if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS) { + if (!OPT::strViewNeighborsFileName.empty()) + scene.LoadViewNeighbors(MAKE_PATH_SAFE(OPT::strViewNeighborsFileName)); TD_TIMER_START(); if (!scene.DenseReconstruction(OPT::nFusionMode)) { if (ABS(OPT::nFusionMode) != 1) @@ -265,7 +307,7 @@ int main(int argc, LPCTSTR* argv) VERBOSE("Densifying point-cloud completed: %u points (%s)", scene.pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str()); } - // save the final mesh + // 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")); diff --git a/apps/InterfaceCOLMAP/CMakeLists.txt b/apps/InterfaceCOLMAP/CMakeLists.txt index 0a5b0fc0e..5d48c79ec 100644 --- a/apps/InterfaceCOLMAP/CMakeLists.txt +++ b/apps/InterfaceCOLMAP/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(InterfaceCOLMAP "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(InterfaceCOLMAP "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS InterfaceCOLMAP diff --git a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp index dbe796ca6..9ea6a361d 100644 --- a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp +++ b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp @@ -63,8 +63,10 @@ using namespace MVS; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { -bool b3Dnovator2COLMAP; // conversion direction +bool bFromOpenMVS; // conversion direction bool bNormalizeIntrinsics; String strInputFileName; String strOutputFileName; @@ -89,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(2), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 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 @@ -109,7 +111,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input COLMAP folder containing cameras, images and points files OR input MVS project file") ("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") - ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(true), "normalize intrinsics while exporting to MVS format") + ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(false), "normalize intrinsics while exporting to MVS format") ; boost::program_options::options_description cmdline_options; @@ -125,6 +127,8 @@ 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); + WORKING_FOLDER = (File::isFolder(OPT::strInputFileName) ? OPT::strInputFileName : Util::getFilePath(OPT::strInputFileName)); INIT_WORKING_FOLDER; // parse configuration file std::ifstream ifs(MAKE_PATH_SAFE(OPT::strConfigFileName)); @@ -146,9 +150,6 @@ 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); - const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); - OPT::b3Dnovator2COLMAP = (strInputFileNameExt == MVS_EXT); const bool bInvalidCommand(OPT::strInputFileName.empty()); if (OPT::vm.count("help") || bInvalidCommand) { boost::program_options::options_description visible("Available options"); @@ -166,7 +167,9 @@ bool Initialize(size_t argc, LPCTSTR* argv) // initialize optional options Util::ensureValidFolderPath(OPT::strImageFolder); Util::ensureValidPath(OPT::strOutputFileName); - if (OPT::b3Dnovator2COLMAP) { + const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); + OPT::bFromOpenMVS = (strInputFileNameExt == MVS_EXT); + if (OPT::bFromOpenMVS) { if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFilePath(OPT::strInputFileName); } else { @@ -279,11 +282,9 @@ struct Camera { }; bool Read(std::istream& stream, bool binary) { - if (binary) { + if (binary) return ReadBIN(stream); - } else { - return ReadTXT(stream); - } + return ReadTXT(stream); } // Camera list with one line of data per camera: @@ -295,6 +296,7 @@ struct Camera { in >> ID >> model >> width >> height; if (in.fail()) return false; + --ID; if (model != _T("PINHOLE")) return false; params.resize(4); @@ -305,7 +307,6 @@ struct Camera { // See: colmap/src/base/reconstruction.cc // void Reconstruction::ReadCamerasBinary(const std::string& path) bool ReadBIN(std::istream& stream) { - if (stream.peek() == EOF) return false; @@ -315,7 +316,7 @@ struct Camera { parsedNumCameras = true; } - ID = ReadBinaryLittleEndian(&stream); + ID = ReadBinaryLittleEndian(&stream)-1; model = mapCameraModel.at(ReadBinaryLittleEndian(&stream)); width = (uint32_t)ReadBinaryLittleEndian(&stream); height = (uint32_t)ReadBinaryLittleEndian(&stream); @@ -359,11 +360,9 @@ struct Image { bool operator < (const Image& rhs) const { return ID < rhs.ID; } bool Read(std::istream& stream, bool binary) { - if (binary) { + if (binary) return ReadBIN(stream); - } else { - return ReadTXT(stream); - } + return ReadTXT(stream); } // Image list with two lines of data per image: @@ -379,6 +378,7 @@ struct Image { >> idCamera >> name; if (in.fail()) return false; + --ID; --idCamera; Util::ensureValidPath(name); if (!NextLine(stream, in, false)) return false; @@ -388,7 +388,7 @@ struct Image { in >> proj.p(0) >> proj.p(1) >> (int&)proj.idPoint; if (in.fail()) break; - projs.push_back(proj); + projs.emplace_back(proj); } return true; } @@ -396,7 +396,6 @@ struct Image { // See: colmap/src/base/reconstruction.cc // void Reconstruction::ReadImagesBinary(const std::string& path) bool ReadBIN(std::istream& stream) { - if (stream.peek() == EOF) return false; @@ -406,7 +405,7 @@ struct Image { parsedNumRegImages = true; } - ID = ReadBinaryLittleEndian(&stream); + ID = ReadBinaryLittleEndian(&stream)-1; q.w() = ReadBinaryLittleEndian(&stream); q.x() = ReadBinaryLittleEndian(&stream); q.y() = ReadBinaryLittleEndian(&stream); @@ -414,7 +413,7 @@ struct Image { t(0) = ReadBinaryLittleEndian(&stream); t(1) = ReadBinaryLittleEndian(&stream); t(2) = ReadBinaryLittleEndian(&stream); - idCamera = ReadBinaryLittleEndian(&stream); + idCamera = ReadBinaryLittleEndian(&stream)-1; name = ""; char nameChar; @@ -433,7 +432,7 @@ struct Image { proj.p(0) = (float)ReadBinaryLittleEndian(&stream); proj.p(1) = (float)ReadBinaryLittleEndian(&stream); proj.idPoint = (uint32_t)ReadBinaryLittleEndian(&stream); - projs.push_back(proj); + projs.emplace_back(proj); } return true; } @@ -472,11 +471,9 @@ struct Point { bool operator < (const Image& rhs) const { return ID < rhs.ID; } bool Read(std::istream& stream, bool binary) { - if (binary) { + if (binary) return ReadBIN(stream); - } else { - return ReadTXT(stream); - } + return ReadTXT(stream); } // 3D point list with one line of data per point: @@ -495,13 +492,15 @@ struct Point { c.z = CLAMP(r,0,255); if (in.fail()) return false; + --ID; tracks.clear(); while (true) { Track track; in >> track.idImage >> track.idProj; if (in.fail()) break; - tracks.push_back(track); + --track.idImage; --track.idProj; + tracks.emplace_back(track); } return !tracks.empty(); } @@ -509,7 +508,6 @@ struct Point { // See: colmap/src/base/reconstruction.cc // void Reconstruction::ReadPoints3DBinary(const std::string& path) bool ReadBIN(std::istream& stream) { - if (stream.peek() == EOF) return false; @@ -520,7 +518,7 @@ struct Point { } int r,g,b; - ID = (uint32_t)ReadBinaryLittleEndian(&stream); + ID = (uint32_t)ReadBinaryLittleEndian(&stream)-1; p.x = (float)ReadBinaryLittleEndian(&stream); p.y = (float)ReadBinaryLittleEndian(&stream); p.z = (float)ReadBinaryLittleEndian(&stream); @@ -536,9 +534,9 @@ struct Point { tracks.clear(); for (size_t j = 0; j < trackLength; ++j) { Track track; - track.idImage = ReadBinaryLittleEndian(&stream); - track.idProj = ReadBinaryLittleEndian(&stream); - tracks.push_back(track); + track.idImage = ReadBinaryLittleEndian(&stream)-1; + track.idProj = ReadBinaryLittleEndian(&stream)-1; + tracks.emplace_back(track); } return !tracks.empty(); } @@ -560,6 +558,44 @@ struct Point { } }; typedef std::vector Points; +// structure describing an 2D dynamic matrix +template +struct Mat { + size_t width_ = 0; + size_t height_ = 0; + size_t depth_ = 0; + std::vector data_; + + size_t GetNumBytes() const { + return data_.size() * sizeof(T); + } + const T* GetChannelPtr(size_t c) const { + return data_.data()+width_*height_*c; + } + + // See: colmap/src/mvs/mat.h + void Read(const std::string& path) { + std::streampos pos; { + std::fstream text_file(path, std::ios::in | std::ios::binary); + char unused_char; + text_file >> width_ >> unused_char >> height_ >> unused_char >> depth_ >> + unused_char; + pos = text_file.tellg(); + } + data_.resize(width_ * height_ * depth_); + std::fstream binary_file(path, std::ios::in | std::ios::binary); + binary_file.seekg(pos); + ReadBinaryLittleEndian(&binary_file, &data_); + } + void Write(const std::string& path) const { + { + std::fstream text_file(path, std::ios::out); + text_file << width_ << "&" << height_ << "&" << depth_ << "&"; + } + std::fstream binary_file(path, std::ios::out | std::ios::binary | std::ios::app); + WriteBinaryLittleEndian(&binary_file, data_); + } +}; } // namespace COLMAP typedef Eigen::Matrix EMat33d; @@ -586,7 +622,7 @@ bool DetermineInputSource(const String& filenameTXT, const String& filenameBIN, } -bool ImportScene(const String& strFolder, Interface& scene) +bool ImportScene(const String& strFolder, const String& strOutFolder, Interface& scene) { COLMAP::DefineCameraModels(); // read camera list @@ -627,17 +663,13 @@ bool ImportScene(const String& strFolder, Interface& scene) camera.C = Interface::Pos3d(0,0,0); if (OPT::bNormalizeIntrinsics) { // normalize camera intrinsics - const REAL fScale(REAL(1)/Camera::GetNormalizationScale(colmapCamera.width, colmapCamera.height)); - camera.K(0,0) *= fScale; - camera.K(1,1) *= fScale; - camera.K(0,2) *= fScale; - camera.K(1,2) *= fScale; + camera.K = Camera::ScaleK(camera.K, 1.0/Camera::GetNormalizationScale(colmapCamera.width, colmapCamera.height)); } else { camera.width = colmapCamera.width; camera.height = colmapCamera.height; } - platform.cameras.push_back(camera); - scene.platforms.push_back(platform); + platform.cameras.emplace_back(camera); + scene.platforms.emplace_back(platform); } } if (mapCameras.empty()) { @@ -667,14 +699,14 @@ bool ImportScene(const String& strFolder, Interface& scene) EnsureRotationMatrix((Matrix3x3d&)pose.R); Eigen::Map(&pose.C.x) = -(imageColmap.q.inverse() * imageColmap.t); Interface::Image image; - image.name = OPT::strImageFolder+imageColmap.name; + image.name = MAKE_PATH_REL(strOutFolder,OPT::strImageFolder+imageColmap.name); image.platformID = mapCameras.at(imageColmap.idCamera); image.cameraID = 0; image.ID = imageColmap.ID; Interface::Platform& platform = scene.platforms[image.platformID]; image.poseID = (uint32_t)platform.poses.size(); - platform.poses.push_back(pose); - scene.images.push_back(image); + platform.poses.emplace_back(pose); + scene.images.emplace_back(image); } } @@ -691,7 +723,7 @@ bool ImportScene(const String& strFolder, Interface& scene) if (!DetermineInputSource(filenamePointsTXT, filenamePointsBIN, file, filenamePoints, binary)) { return false; } - LOG_OUT() << "Reading images: " << filenamePoints << std::endl; + LOG_OUT() << "Reading points: " << filenamePoints << std::endl; COLMAP::Point point; while (file.good() && point.Read(file, binary)) { @@ -745,6 +777,95 @@ bool ImportScene(const String& strFolder, Interface& scene) scene.verticesColor.emplace_back(Interface::Color{pointcloud.colors[i]}); } } + + // read depth-maps + const String pathDepthMaps(strFolder+COLMAP_STEREO_DEPTHMAPS_FOLDER); + const String pathNormalMaps(strFolder+COLMAP_STEREO_NORMALMAPS_FOLDER); + if (File::isFolder(pathDepthMaps) && File::isFolder(pathNormalMaps)) { + // read patch-match list + CLISTDEF2IDX(IIndexArr,IIndex) imagesNeighbors((IIndex)scene.images.size()); + { + const String filenameFusion(strFolder+COLMAP_PATCHMATCH); + LOG_OUT() << "Reading patch-match configuration: " << filenameFusion << std::endl; + std::ifstream file(filenameFusion); + if (!file.good()) { + VERBOSE("error: unable to open file '%s'", filenameFusion.c_str()); + return false; + } + while (true) { + String imageName, neighbors; + std::getline(file, imageName); + std::getline(file, neighbors); + if (file.fail() || imageName.empty() || neighbors.empty()) + break; + const ImagesMap::const_iterator it_image = std::find_if(mapImages.begin(), mapImages.end(), + [&imageName](const ImagesMap::value_type& image) { + return image.first.name == imageName; + }); + if (it_image == mapImages.end()) + continue; + IIndexArr& imageNeighbors = imagesNeighbors[it_image->second]; + CLISTDEF2(String) neighborNames; + Util::strSplit(neighbors, _T(','), neighborNames); + FOREACH(i, neighborNames) { + String& neighborName = neighborNames[i]; + Util::strTrim(neighborName, _T(" ")); + const ImagesMap::const_iterator it_neighbor = std::find_if(mapImages.begin(), mapImages.end(), + [&neighborName](const ImagesMap::value_type& image) { + return image.first.name == neighborName; + }); + if (it_neighbor == mapImages.end()) { + if (i == 0) + break; + continue; + } + imageNeighbors.emplace_back(scene.images[it_neighbor->second].ID); + } + } + } + LOG_OUT() << "Reading depth-maps/normal-maps: " << pathDepthMaps << " and " << pathNormalMaps << std::endl; + Util::ensureFolder(strOutFolder); + const String strType[] = {".geometric.bin", ".photometric.bin"}; + FOREACH(idx, scene.images) { + const Interface::Image& image = scene.images[idx]; + COLMAP::Mat colDepthMap, colNormalMap; + const String filenameImage(Util::getFileNameExt(image.name)); + for (int i=0; i<2; ++i) { + const String filenameDepthMaps(pathDepthMaps+filenameImage+strType[i]); + if (File::isFile(filenameDepthMaps)) { + colDepthMap.Read(filenameDepthMaps); + const String filenameNormalMaps(pathNormalMaps+filenameImage+strType[i]); + if (File::isFile(filenameNormalMaps)) { + colNormalMap.Read(filenameNormalMaps); + } + break; + } + } + if (!colDepthMap.data_.empty()) { + IIndexArr IDs = {image.ID}; + IDs.Join(imagesNeighbors[(IIndex)idx]); + const Interface::Platform& platform = scene.platforms[image.platformID]; + const Interface::Platform::Pose pose(platform.GetPose(image.cameraID, image.poseID)); + const Interface::Mat33d K(platform.GetFullK(image.cameraID, (uint32_t)colDepthMap.width_, (uint32_t)colDepthMap.height_)); + MVS::DepthMap depthMap((int)colDepthMap.height_, (int)colDepthMap.width_); + memcpy(depthMap.getData(), colDepthMap.data_.data(), colDepthMap.GetNumBytes()); + MVS::NormalMap normalMap; + if (!colNormalMap.data_.empty()) { + normalMap.create((int)colNormalMap.height_, (int)colNormalMap.width_); + cv::merge(std::vector{ + cv::Mat((int)colNormalMap.height_, (int)colNormalMap.width_, CV_32F, (void*)colNormalMap.GetChannelPtr(0)), + cv::Mat((int)colNormalMap.height_, (int)colNormalMap.width_, CV_32F, (void*)colNormalMap.GetChannelPtr(1)), + cv::Mat((int)colNormalMap.height_, (int)colNormalMap.width_, CV_32F, (void*)colNormalMap.GetChannelPtr(2)) + }, normalMap); + } + MVS::ConfidenceMap confMap; + const auto depthMM(std::minmax_element(colDepthMap.data_.cbegin(), colDepthMap.data_.cend())); + const MVS::Depth dMin(*depthMM.first), dMax(*depthMM.second); + if (!ExportDepthDataRaw(strOutFolder+String::FormatString("depth%04u.dmap", image.ID), MAKE_PATH_FULL(strOutFolder, image.name), IDs, depthMap.size(), K, pose.R, pose.C, dMin, dMax, depthMap, normalMap, confMap)) + return false; + } + } + } return true; } @@ -774,16 +895,12 @@ bool ExportScene(const String& strFolder, const Interface& scene) ASSERT(platform.cameras.size() == 1); // only one camera per platform supported const Interface::Platform::Camera& camera = platform.cameras[0]; cam.ID = ID; - 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); if (camera.width == 0 || camera.height == 0) { // find one image using this camera const Interface::Image* pImage(NULL); for (uint32_t i=0; i<(uint32_t)scene.images.size(); ++i) { const Interface::Image& image = scene.images[i]; - if (image.platformID == ID && image.cameraID == 0 && image.poseID != NO_ID) { + if (image.platformID == ID && image.cameraID == 0 && image.poseID != MVS::NO_ID) { pImage = ℑ break; } @@ -797,20 +914,23 @@ bool ExportScene(const String& strFolder, const Interface& scene) return false; cam.width = ptrImage->GetWidth(); cam.height = ptrImage->GetHeight(); - // denormalize camera intrinsics - const double fScale(MVS::Camera::GetNormalizationScale(cam.width, cam.height)); - cam.params[0] *= fScale; - cam.params[1] *= fScale; - cam.params[2] *= fScale; - cam.params[3] *= fScale; + // 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); } 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); } if (!cam.Write(file)) return false; - KMatrix& K = Ks.AddEmpty(); - K = KMatrix::IDENTITY; + 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]; @@ -831,7 +951,7 @@ bool ExportScene(const String& strFolder, const Interface& scene) cameras.resize(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 == NO_ID) + if (image.poseID == MVS::NO_ID) continue; const Interface::Platform& platform = scene.platforms[image.platformID]; const Interface::Platform::Pose& pose = platform.poses[image.poseID]; @@ -875,15 +995,12 @@ bool ExportScene(const String& strFolder, const Interface& scene) point.p = vertex.X; for (const Interface::Vertex::View& view: vertex.views) { COLMAP::Image& img = images[view.imageID]; - COLMAP::Point::Track track; - track.idImage = view.imageID; - track.idProj = (uint32_t)img.projs.size(); - point.tracks.push_back(track); + point.tracks.emplace_back(COLMAP::Point::Track{view.imageID, (uint32_t)img.projs.size()}); COLMAP::Image::Proj proj; proj.idPoint = ID; const Point3 X(vertex.X); ProjectVertex_3x4_3_2(cameras[view.imageID].P.val, X.ptr(), proj.p.data()); - img.projs.push_back(proj); + img.projs.emplace_back(proj); } point.c = scene.verticesColor.empty() ? Interface::Col3(255,255,255) : scene.verticesColor[ID].c; point.e = 0; @@ -982,7 +1099,9 @@ bool ExportScene(const String& strFolder, const Interface& scene) file << _T("# IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME") << std::endl; file << _T("# POINTS2D[] as (X, Y, POINT3D_ID)") << std::endl; for (const COLMAP::Image& img: images) { - if ((bSparsePointCloud && img.projs.empty()) || !img.Write(file)) + if (bSparsePointCloud && img.projs.empty()) + continue; + if (!img.Write(file)) return false; } } @@ -1003,11 +1122,16 @@ bool ExportImagesLog(const String& fileName, const Interface& scene) return false; } out << std::setprecision(12); - for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { + IIndexArr orderedImages((uint32_t)scene.images.size()); + std::iota(orderedImages.begin(), orderedImages.end(), 0u); + orderedImages.Sort([&scene](IIndex i, IIndex j) { + return scene.images[i].ID < scene.images[j].ID; + }); + for (IIndex ID: orderedImages) { const Interface::Image& image = scene.images[ID]; Eigen::Matrix3d R(Eigen::Matrix3d::Identity()); Eigen::Vector3d t(Eigen::Vector3d::Zero()); - if (image.poseID != NO_ID) { + if (image.poseID != MVS::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(); @@ -1025,6 +1149,67 @@ bool ExportImagesLog(const String& fileName, const Interface& scene) return !out.fail(); } + +// export poses in Strecha camera format: +// Strecha model is P = K[R^T|-R^T t] +// our model is P = K[R|t], t = -RC +bool ExportImagesCamera(const String& pathName, const Interface& scene) +{ + LOG_OUT() << "Writing poses: " << pathName << std::endl; + Util::ensureFolder(pathName); + for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { + const Interface::Image& image = scene.images[ID]; + String imageFileName(image.name); + Util::ensureValidPath(imageFileName); + const String fileName(pathName+Util::getFileNameExt(imageFileName)+".camera"); + std::ofstream out(fileName); + if (!out.good()) { + VERBOSE("error: unable to open file '%s'", fileName.c_str()); + return false; + } + out << std::setprecision(12); + KMatrix K(KMatrix::IDENTITY); + RMatrix R(RMatrix::IDENTITY); + CMatrix t(CMatrix::ZERO); + unsigned width(0), height(0); + if (image.platformID != MVS::NO_ID && image.cameraID != MVS::NO_ID) { + const Interface::Platform& platform = scene.platforms[image.platformID]; + const Interface::Platform::Camera& camera = platform.cameras[image.cameraID]; + if (camera.HasResolution()) { + width = camera.width; + height = camera.height; + K = camera.K; + } else { + IMAGEPTR pImage = Image::ReadImageHeader(image.name); + width = pImage->GetWidth(); + height = pImage->GetHeight(); + K = platform.GetFullK(image.cameraID, width, height); + } + if (image.poseID != MVS::NO_ID) { + const Interface::Platform::Pose& pose = platform.poses[image.poseID]; + R = pose.R.t(); + t = pose.C; + } + } + out << K(0,0) << _T(" ") << K(0,1) << _T(" ") << K(0,2) << _T("\n"); + out << K(1,0) << _T(" ") << K(1,1) << _T(" ") << K(1,2) << _T("\n"); + out << K(2,0) << _T(" ") << K(2,1) << _T(" ") << K(2,2) << _T("\n"); + out << _T("0 0 0") << _T("\n"); + out << R(0,0) << _T(" ") << R(0,1) << _T(" ") << R(0,2) << _T("\n"); + out << R(1,0) << _T(" ") << R(1,1) << _T(" ") << R(1,2) << _T("\n"); + out << R(2,0) << _T(" ") << R(2,1) << _T(" ") << R(2,2) << _T("\n"); + out << t.x << _T(" ") << t.y << _T(" ") << t.z << _T("\n"); + out << width << _T(" ") << height << _T("\n"); + if (out.fail()) { + VERBOSE("error: unable to write file '%s'", fileName.c_str()); + return false; + } + } + return true; +} + +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -1037,7 +1222,7 @@ int main(int argc, LPCTSTR* argv) TD_TIMER_START(); - if (OPT::b3Dnovator2COLMAP) { + if (OPT::bFromOpenMVS) { // read MVS input data Interface scene; if (!ARCHIVE::SerializeLoad(scene, MAKE_PATH_SAFE(OPT::strInputFileName))) @@ -1045,6 +1230,10 @@ int main(int argc, LPCTSTR* argv) if (Util::getFileExt(OPT::strOutputFileName) == _T(".log")) { // write poses in log format ExportImagesLog(MAKE_PATH_SAFE(OPT::strOutputFileName), scene); + } else + if (Util::getFileExt(OPT::strOutputFileName) == _T(".camera")) { + // write poses in Strecha camera format + ExportImagesCamera((OPT::strOutputFileName=Util::getFileFullName(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName)))+PATH_SEPARATOR, scene); } else { // write COLMAP input data Util::ensureFolderSlash(OPT::strOutputFileName); @@ -1054,11 +1243,12 @@ int main(int argc, LPCTSTR* argv) } else { // read COLMAP input data Interface scene; - if (!ImportScene(MAKE_PATH_SAFE(OPT::strInputFileName), scene)) + const String strOutFolder(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName))); + if (!ImportScene(MAKE_PATH_SAFE(OPT::strInputFileName), strOutFolder, scene)) return EXIT_FAILURE; // write MVS input data - Util::ensureFolder(Util::getFullPath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName))); - if (!ARCHIVE::SerializeSave(scene, MAKE_PATH_SAFE(OPT::strOutputFileName), (uint32_t)OPT::bNormalizeIntrinsics?0:1)) + 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()); } diff --git a/apps/InterfacePhotoScan/CMakeLists.txt b/apps/InterfaceMetashape/CMakeLists.txt similarity index 57% rename from apps/InterfacePhotoScan/CMakeLists.txt rename to apps/InterfaceMetashape/CMakeLists.txt index e030ce189..100e829f5 100644 --- a/apps/InterfacePhotoScan/CMakeLists.txt +++ b/apps/InterfaceMetashape/CMakeLists.txt @@ -5,9 +5,9 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(InterfacePhotoScan "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(InterfaceMetashape "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install -INSTALL(TARGETS InterfacePhotoScan +INSTALL(TARGETS InterfaceMetashape EXPORT OpenMVSTargets RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin) diff --git a/apps/InterfacePhotoScan/InterfacePhotoScan.cpp b/apps/InterfaceMetashape/InterfaceMetashape.cpp similarity index 75% rename from apps/InterfacePhotoScan/InterfacePhotoScan.cpp rename to apps/InterfaceMetashape/InterfaceMetashape.cpp index 0b5f1effa..a13356d0e 100644 --- a/apps/InterfacePhotoScan/InterfacePhotoScan.cpp +++ b/apps/InterfaceMetashape/InterfaceMetashape.cpp @@ -1,7 +1,7 @@ /* - * InterfacePhotoScan.cpp + * InterfaceMetashape.cpp * - * Copyright (c) 2014-2015 SEACAVE + * Copyright (c) 2014-2021 SEACAVE * * Author(s): * @@ -31,12 +31,15 @@ #include "../../libs/MVS/Common.h" #include "../../libs/MVS/Scene.h" +#include "../../libs/IO/TinyXML2.h" #include +using namespace MVS; + // D E F I N E S /////////////////////////////////////////////////// -#define APPNAME _T("InterfacePhotoScan") +#define APPNAME _T("InterfaceMetashape") // previously PhotoScan #define MVS_EXT _T(".mvs") #define XML_EXT _T(".xml") #define PLY_EXT _T(".ply") @@ -44,9 +47,11 @@ // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { -String strPointsFileName; String strInputFileName; +String strPointsFileName; String strOutputFileName; String strOutputImageFolder; unsigned nArchiveType; @@ -69,11 +74,11 @@ 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(2), "project archive type: 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)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: 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 - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -86,8 +91,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) // group of options allowed both on command line and in config file boost::program_options::options_description config("Main options"); config.add_options() - ("points-file,p", boost::program_options::value(&OPT::strPointsFileName), "input filename containing the 3D points") ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("points-file,p", boost::program_options::value(&OPT::strPointsFileName), "input filename containing the 3D points") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") ("output-image-folder", boost::program_options::value(&OPT::strOutputImageFolder)->default_value("undistorted_images"), "output folder to store undistorted images") ; @@ -127,13 +132,11 @@ bool Initialize(size_t argc, LPCTSTR* argv) // validate input Util::ensureValidPath(OPT::strPointsFileName); - Util::ensureUnifySlash(OPT::strPointsFileName); Util::ensureValidPath(OPT::strInputFileName); - Util::ensureUnifySlash(OPT::strInputFileName); - Util::ensureUnifySlash(OPT::strOutputImageFolder); - Util::ensureDirectorySlash(OPT::strOutputImageFolder); + Util::ensureValidPath(OPT::strOutputImageFolder); + Util::ensureFolderSlash(OPT::strOutputImageFolder); const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); - const bool bInvalidCommand(OPT::strInputFileName.IsEmpty() || OPT::strPointsFileName.IsEmpty()); + const bool bInvalidCommand(OPT::strInputFileName.empty()); if (OPT::vm.count("help") || bInvalidCommand) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(config); @@ -146,7 +149,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) Util::ensureValidPath(OPT::strOutputFileName); Util::ensureUnifySlash(OPT::strOutputFileName); if (OPT::strOutputFileName.IsEmpty()) - OPT::strOutputFileName = Util::getFullFileName(OPT::strInputFileName) + MVS_EXT; + OPT::strOutputFileName = Util::getFileName(OPT::strInputFileName) + MVS_EXT; // initialize global options Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); @@ -176,12 +179,39 @@ void Finalize() } struct DistCoeff { - REAL k1, k2, k3, p1, p2, k4, k5, k6; - DistCoeff() : k1(0), k2(0), k3(0), p1(0), p2(0), k4(0), k5(0), k6(0) {} + union { + REAL coeff[8]; + struct { + REAL k1, k2, p1, p2, k3, k4, k5, k6; + }; + }; + DistCoeff() : k1(0), k2(0), p1(0), p2(0), k3(0), k4(0), k5(0), k6(0) {} }; typedef cList DistCoeffs; typedef cList PlatformDistCoeffs; +void ImageListParseC(const LPSTR* argv, Point3& C) +{ + // read position vector + C.x = String::FromString(argv[0]); + C.y = String::FromString(argv[1]); + C.z = String::FromString(argv[2]); +} + +void ImageListParseR(const LPSTR* argv, Matrix3x3& R) +{ + // read rotation matrix + R(0, 0) = String::FromString(argv[0]); + R(0, 1) = String::FromString(argv[1]); + R(0, 2) = String::FromString(argv[2]); + R(1, 0) = String::FromString(argv[3]); + R(1, 1) = String::FromString(argv[4]); + R(1, 2) = String::FromString(argv[5]); + R(2, 0) = String::FromString(argv[6]); + R(2, 1) = String::FromString(argv[7]); + R(2, 2) = String::FromString(argv[8]); +} + void ImageListParseP(const LPSTR* argv, Matrix3x4& P) { // read projection matrix @@ -201,10 +231,8 @@ void ImageListParseP(const LPSTR* argv, Matrix3x4& P) // parse images list containing calibration and pose information // and load the corresponding 3D point-cloud -bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, size_t& nCameras, size_t& nPoses) +bool ParseImageListXML(Scene& scene, PlatformDistCoeffs& pltDistCoeffs, size_t& nCameras, size_t& nPoses) { - using namespace MVS; - // open image list nCameras = nPoses = 0; const String strInputFileName(MAKE_PATH_SAFE(OPT::strInputFileName)); @@ -223,10 +251,15 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz doc.Parse(strCameras.c_str(), nLen); if (doc.ErrorID() != tinyxml2::XML_SUCCESS) goto InvalidDocument; + { tinyxml2::XMLElement* document = doc.FirstChildElement(_T("document"))->FirstChildElement(_T("chunk")); if (document == NULL) goto InvalidDocument; - bool bPhotoScanFile(false); + { + bool bMetashapeFile(false); + CLISTDEF0(cv::Size) resolutions; + std::unordered_map mapPlatformID; + std::unordered_map mapImageID; // parse platform and camera models { @@ -240,10 +273,10 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz goto InvalidDocument; { // add new camera - enum CameraModel {PHOTOSCAN=0, VSFM}; - int model(PHOTOSCAN); + enum CameraModel {METASHAPE=0, VSFM}; + int model(METASHAPE); sensor->QueryIntAttribute(_T("model"), &model); - ASSERT(scene.platforms.GetSize() == ID); + mapPlatformID.emplace(ID, scene.platforms.size()); Platform& platform = scene.platforms.AddEmpty(); LPCTSTR name; if ((name=sensor->Attribute(_T("label"))) != NULL) @@ -253,11 +286,13 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz if (calibration == NULL) goto InvalidDocument; { - REAL scale(1); if ((elem=calibration->FirstChildElement(_T("resolution"))) != NULL) { - scale = REAL(1)/(REAL)Camera::GetNormalizationScale(elem->UnsignedAttribute(_T("width")), elem->UnsignedAttribute(_T("height"))); - ASSERT(model == PHOTOSCAN); - bPhotoScanFile = true; + resolutions.emplace_back( + elem->UnsignedAttribute(_T("width")), + elem->UnsignedAttribute(_T("height")) + ); + ASSERT(model == METASHAPE); + bMetashapeFile = true; } Platform::Camera& camera = platform.cameras.AddEmpty(); camera.K = KMatrix::IDENTITY; @@ -266,23 +301,19 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz DistCoeff& dc = pltDistCoeffs.AddEmpty().AddEmpty(); for (elem=calibration->FirstChildElement(); elem!=NULL; elem=elem->NextSiblingElement()) { if (0 == _tcsicmp(elem->Value(), _T("f"))) { - camera.K(0,0) = camera.K(1,1) = String::FromString(elem->GetText())*scale; + camera.K(0,0) = camera.K(1,1) = String::FromString(elem->GetText()); } else if (0 == _tcsicmp(elem->Value(), _T("fx"))) { elem->QueryDoubleText(&camera.K(0,0)); - camera.K(0,0) *= scale; } else if (0 == _tcsicmp(elem->Value(), _T("fy"))) { elem->QueryDoubleText(&camera.K(1,1)); - camera.K(1,1) *= scale; } else if (0 == _tcsicmp(elem->Value(), _T("cx"))) { elem->QueryDoubleText(&camera.K(0,2)); - camera.K(0,2) *= scale; } else if (0 == _tcsicmp(elem->Value(), _T("cy"))) { elem->QueryDoubleText(&camera.K(1,2)); - camera.K(1,2) *= scale; } else if (0 == _tcsicmp(elem->Value(), _T("k1"))) { elem->QueryDoubleText(&dc.k1); @@ -309,6 +340,13 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz elem->QueryDoubleText(&dc.k6); } } + if (bMetashapeFile) { + const cv::Size& resolution = resolutions.back(); + camera.K(0,2) += resolution.width*REAL(0.5); + camera.K(1,2) += resolution.height*REAL(0.5); + camera.K = camera.GetScaledK(REAL(1)/Camera::GetNormalizationScale(resolution.width, resolution.height)); + std::swap(dc.p1, dc.p2); + } ++nCameras; } } @@ -331,7 +369,7 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz goto InvalidDocument; { // add new image - ASSERT(scene.images.GetSize() == ID); + mapImageID.emplace(ID, scene.images.size()); Image& imageData = scene.images.AddEmpty(); LPCTSTR name; if ((name=camera->Attribute(_T("type"))) != NULL && _tcsicmp(name, _T("frame")) != 0) { @@ -341,11 +379,16 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz if ((name=camera->Attribute(_T("label"))) != NULL) imageData.name = name; Util::ensureUnifySlash(imageData.name); + if (Util::getFileExt(imageData.name).empty()) + imageData.name += _T(".jpg"); imageData.name = MAKE_PATH_FULL(strPath, imageData.name); - imageData.platformID = camera->UnsignedAttribute(_T("sensor_id")); + imageData.platformID = mapPlatformID.at(camera->UnsignedAttribute(_T("sensor_id"))); imageData.cameraID = 0; // only one camera per platform supported by this format - imageData.ID = ID; - if (!camera->BoolAttribute(_T("enabled"))) { + imageData.ID = mapImageID.at(ID); + const cv::Size& resolution = resolutions[imageData.platformID]; + imageData.width = resolution.width; + imageData.height = resolution.height; + if (!bMetashapeFile && !camera->BoolAttribute(_T("enabled"))) { imageData.poseID = NO_ID; DEBUG_EXTRA("warning: uncalibrated image '%s'", name); continue; @@ -354,17 +397,17 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz CAutoPtrArr argv; if ((elem=camera->FirstChildElement(_T("transform"))) == NULL || (argv=Util::CommandLineToArgvA(elem->GetText(), argc)) == NULL || - (argc != (bPhotoScanFile ? 16 : 12))) + (argc != (bMetashapeFile ? 16 : 12))) { VERBOSE("Invalid image list camera: %u", ID); continue; } Platform& platform = scene.platforms[imageData.platformID]; - imageData.poseID = platform.poses.GetSize(); + imageData.poseID = platform.poses.size(); Platform::Pose& pose = platform.poses.AddEmpty(); ImageListParseP(argv, P); DecomposeProjectionMatrix(P, pose.R, pose.C); - if (bPhotoScanFile) { + if (bMetashapeFile) { pose.C = pose.R*(-pose.C); pose.R = pose.R.t(); } @@ -373,6 +416,48 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz } } } + + // parse bounding-box + { + tinyxml2::XMLElement* region = document->FirstChildElement(_T("region")); + if (region == NULL) + goto InvalidDocument; + { + size_t argc; + CAutoPtrArr argv; + Point3 C, E; Matrix3x3 R; + if ((elem=region->FirstChildElement(_T("center"))) == NULL || + (argv=Util::CommandLineToArgvA(elem->GetText(), argc)) == NULL || + argc != 3) + { + VERBOSE("Invalid image list region: %s", elem->GetText()); + goto InvalidDocument; + } + ImageListParseC(argv, C); + if ((elem=region->FirstChildElement(_T("size"))) == NULL || + (argv=Util::CommandLineToArgvA(elem->GetText(), argc)) == NULL || + argc != 3) + { + VERBOSE("Invalid image list region: %s", elem->GetText()); + goto InvalidDocument; + } + ImageListParseC(argv, E); + E *= REAL(0.5); + if ((elem=region->FirstChildElement(_T("R"))) == NULL || + (argv=Util::CommandLineToArgvA(elem->GetText(), argc)) == NULL || + argc != 9) + { + VERBOSE("Invalid image list region: %s", elem->GetText()); + goto InvalidDocument; + } + ImageListParseR(argv, R); + scene.obb.m_rot = Cast(R); + scene.obb.m_pos = Cast(C); + scene.obb.m_ext = Cast(E); + } + } + } + } } return true; @@ -382,24 +467,14 @@ bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, siz } // undistort image using Brown's model -bool UndistortBrown(MVS::Image& imageData, uint32_t ID, const DistCoeff& dc, const String& pathData) +bool UndistortBrown(Image& imageData, uint32_t ID, const DistCoeff& dc, const String& pathData) { - using namespace MVS; - // load image pixels if (!imageData.ReloadImage()) return false; // initialize intrinsics - cv::Vec distCoeffs; - distCoeffs(0) = dc.k1; - distCoeffs(1) = dc.k2; - distCoeffs(2) = dc.p1; - distCoeffs(3) = dc.p2; - distCoeffs(4) = dc.k3; - distCoeffs(5) = dc.k4; - distCoeffs(6) = dc.k5; - distCoeffs(7) = dc.k6; + const cv::Vec& distCoeffs = *reinterpret_cast*>(dc.coeff); const KMatrix prevK(imageData.camera.GetK(imageData.width, imageData.height)); #if 1 const KMatrix& K(prevK); @@ -424,15 +499,13 @@ bool UndistortBrown(MVS::Image& imageData, uint32_t ID, const DistCoeff& dc, con // save undistorted image imageData.image = imgUndist; imageData.name = pathData + String::FormatString(_T("%05u.png"), ID); - Util::ensureDirectory(imageData.name); + Util::ensureFolder(imageData.name); return imageData.image.Save(imageData.name); } // project all points in this image and keep those looking at the camera and are most in front -void AssignPoints(const MVS::Image& imageData, uint32_t ID, MVS::PointCloud& pointcloud) +void AssignPoints(const Image& imageData, uint32_t ID, PointCloud& pointcloud) { - using namespace MVS; - ASSERT(pointcloud.IsValid()); const int CHalfSize(1); const int FHalfSize(5); @@ -524,6 +597,8 @@ void AssignPoints(const MVS::Image& imageData, uint32_t ID, MVS::PointCloud& poi DEBUG_ULTIMATE("\tview %3u sees %u points", ID, nNumPoints); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -536,14 +611,17 @@ int main(int argc, LPCTSTR* argv) TD_TIMER_START(); - // read the 3D point-cloud - MVS::Scene scene(OPT::nMaxThreads); - if (!scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointsFileName))) - return EXIT_FAILURE; - ASSERT(!scene.pointcloud.IsValid()); - scene.pointcloud.pointViews.Resize(scene.pointcloud.points.GetSize()); + Scene scene(OPT::nMaxThreads); + + // read the 3D point-cloud if available + if (!OPT::strPointsFileName.empty()) { + if (!scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointsFileName))) + return EXIT_FAILURE; + ASSERT(!scene.pointcloud.IsValid()); + scene.pointcloud.pointViews.Resize(scene.pointcloud.points.GetSize()); + } - // convert data from PhotoScan format to OpenMVS + // convert data from Metashape format to OpenMVS PlatformDistCoeffs pltDistCoeffs; size_t nCameras, nPoses; if (!ParseImageListXML(scene, pltDistCoeffs, nCameras, nPoses)) @@ -551,12 +629,12 @@ int main(int argc, LPCTSTR* argv) // undistort images const String pathData(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputImageFolder)); - Util::Progress progress(_T("Processed images"), scene.images.GetSize()); + Util::Progress progress(_T("Processed images"), scene.images.size()); GET_LOGCONSOLE().Pause(); #ifdef _USE_OPENMP bool bAbort(false); #pragma omp parallel for shared(bAbort) schedule(dynamic) - for (int ID=0; ID<(int)scene.images.GetSize(); ++ID) { + for (int ID=0; ID<(int)scene.images.size(); ++ID) { #pragma omp flush (bAbort) if (bAbort) continue; @@ -564,7 +642,7 @@ int main(int argc, LPCTSTR* argv) FOREACH(ID, scene.images) { #endif ++progress; - MVS::Image& imageData = scene.images[ID]; + Image& imageData = scene.images[ID]; if (!UndistortBrown(imageData, ID, pltDistCoeffs[imageData.platformID][imageData.cameraID], pathData)) { #ifdef _USE_OPENMP bAbort = true; @@ -575,7 +653,8 @@ int main(int argc, LPCTSTR* argv) #endif } imageData.UpdateCamera(scene.platforms); - AssignPoints(imageData, ID, scene.pointcloud); + if (scene.pointcloud.IsValid()) + AssignPoints(imageData, ID, scene.pointcloud); } GET_LOGCONSOLE().Play(); #ifdef _USE_OPENMP @@ -584,6 +663,13 @@ int main(int argc, LPCTSTR* argv) #endif progress.close(); + // filter invalid points + if (!scene.pointcloud.IsEmpty()) { + RFOREACH(i, scene.pointcloud.points) + if (scene.pointcloud.pointViews[i].size() < 2) + scene.pointcloud.RemovePoint(i); + } + // write OpenMVS input data scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); diff --git a/apps/InterfaceOpenMVG/CMakeLists.txt b/apps/InterfaceOpenMVG/CMakeLists.txt index 5dc56a0d9..6377497ea 100644 --- a/apps/InterfaceOpenMVG/CMakeLists.txt +++ b/apps/InterfaceOpenMVG/CMakeLists.txt @@ -15,7 +15,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(InterfaceOpenMVG "Apps" "${cxx_default}" "${LIBS_DEPEND};${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(InterfaceOpenMVG "Apps" "${cxx_default}" "${LIBS_DEPEND};${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS InterfaceOpenMVG diff --git a/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp b/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp index c410d0d75..3f4a171ee 100644 --- a/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp +++ b/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp @@ -53,6 +53,8 @@ // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace openMVS { namespace MVS_IO { @@ -350,11 +352,11 @@ 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(2), "project archive type: 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)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 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 - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -371,7 +373,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") ("output-image-folder", boost::program_options::value(&OPT::strOutputImageFolder)->default_value("undistorted_images"), "output folder to store undistorted images") - ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(true), "normalize intrinsics while exporting to OpenMVS format") + ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(true), "normalize intrinsics while exporting to OpenMVS format") ; boost::program_options::options_description cmdline_options; @@ -471,6 +473,8 @@ void Finalize() CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO diff --git a/apps/InterfaceVisualSFM/CMakeLists.txt b/apps/InterfaceVisualSFM/CMakeLists.txt index 9bd8bdedf..a1b16af7b 100644 --- a/apps/InterfaceVisualSFM/CMakeLists.txt +++ b/apps/InterfaceVisualSFM/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(InterfaceVisualSFM "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(InterfaceVisualSFM "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS InterfaceVisualSFM diff --git a/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp b/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp index c686abf8b..c0278aa11 100644 --- a/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp +++ b/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp @@ -48,10 +48,13 @@ // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; String strOutputFileName; String strOutputImageFolder; +bool bFromOpenMVS; // conversion direction unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -72,7 +75,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(2), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 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 @@ -145,8 +148,17 @@ bool Initialize(size_t argc, LPCTSTR* argv) Util::ensureUnifySlash(OPT::strOutputFileName); Util::ensureUnifySlash(OPT::strOutputImageFolder); Util::ensureFolderSlash(OPT::strOutputImageFolder); - if (OPT::strOutputFileName.IsEmpty()) - OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + MVS_EXT; + const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); + OPT::bFromOpenMVS = (strInputFileNameExt == MVS_EXT); + if (OPT::bFromOpenMVS) { + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = Util::getFilePath(OPT::strInputFileName); + } else { + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = Util::getFilePath(OPT::strInputFileName) + _T("scene") MVS_EXT; + else + OPT::strOutputImageFolder = Util::getRelativePath(Util::getFilePath(OPT::strOutputFileName), Util::getFilePath(OPT::strInputFileName)+OPT::strOutputImageFolder); + } // initialize global options Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); @@ -177,6 +189,8 @@ void Finalize() CLOSE_LOG(); } +} // unnamed namespace + #define PBA_PRECISION float namespace PBA { @@ -275,7 +289,86 @@ void UndistortImage(const Camera& camera, const REAL& k1, const Image8U3 imgIn, } // namespace MVS -int ImportSceneVSFM() +bool ExportSceneVSFM() +{ + TD_TIMER_START(); + + // read MVS input data + MVS::Scene scene(OPT::nMaxThreads); + if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + return false; + + // convert and write data from OpenMVS to VisualSFM + std::vector cameras; + std::vector vertices; + std::vector measurements; // the array of 2D projections (only inliers) + std::vector correspondingPoint; // 3D point index corresponding to each 2D projection + std::vector correspondingView; // and camera index + std::vector names; + std::vector ptc; + cameras.reserve(scene.images.size()); + names.reserve(scene.images.size()); + MVS::IIndexArr mapIdx(scene.images.size()); + bool bFocalWarning(false), bPrincipalpointWarning(false); + FOREACH(idx, scene.images) { + const MVS::Image& image = scene.images[idx]; + if (!image.IsValid()) { + mapIdx[idx] = NO_ID; + continue; + } + if (!bFocalWarning && !ISEQUAL(image.camera.K(0, 0), image.camera.K(1, 1))) { + DEBUG("warning: fx != fy and NVM format does not support it"); + bFocalWarning = true; + } + if (!bPrincipalpointWarning && (!ISEQUAL(REAL(image.width-1)*0.5, image.camera.K(0, 2)) || !ISEQUAL(REAL(image.height-1)*0.5, image.camera.K(1, 2)))) { + DEBUG("warning: cx, cy are not the image center and NVM format does not support it"); + bPrincipalpointWarning = true; + } + PBA::Camera cameraNVM; + cameraNVM.SetFocalLength((image.camera.K(0, 0) + image.camera.K(1, 1)) * 0.5); + cameraNVM.SetMatrixRotation(image.camera.R.val); + cameraNVM.SetCameraCenterAfterRotation(image.camera.C.ptr()); + mapIdx[idx] = static_cast(cameras.size()); + cameras.emplace_back(cameraNVM); + names.emplace_back(MAKE_PATH_REL(WORKING_FOLDER_FULL, image.name)); + } + vertices.reserve(scene.pointcloud.points.size()); + measurements.reserve(scene.pointcloud.pointViews.size()); + correspondingPoint.reserve(scene.pointcloud.pointViews.size()); + correspondingView.reserve(scene.pointcloud.pointViews.size()); + FOREACH(idx, scene.pointcloud.points) { + const MVS::PointCloud::Point& X = scene.pointcloud.points[idx]; + const MVS::PointCloud::ViewArr& views = scene.pointcloud.pointViews[idx]; + const size_t prevMeasurements(measurements.size()); + for (MVS::IIndex idxView: views) { + const MVS::Image& image = scene.images[idxView]; + const Point2f pt(image.camera.TransformPointW2I(Cast(X))); + if (pt.x < 0 || pt.y < 0 || pt.x > image.width-1 || pt.y > image.height-1) + continue; + measurements.emplace_back(pt.x, pt.y); + correspondingView.emplace_back(static_cast(mapIdx[idxView])); + correspondingPoint.emplace_back(static_cast(vertices.size())); + } + if (prevMeasurements < measurements.size()) + vertices.emplace_back(PBA::Point3D{X.x, X.y, X.z}); + } + if (!scene.pointcloud.colors.empty()) { + ptc.reserve(scene.pointcloud.colors.size()*3); + FOREACH(idx, scene.pointcloud.points) { + const MVS::PointCloud::Color& c = scene.pointcloud.colors[idx]; + ptc.emplace_back(c.r); + ptc.emplace_back(c.g); + ptc.emplace_back(c.b); + } + } + PBA::SaveModelFile(MAKE_PATH_SAFE(OPT::strOutputFileName), cameras, vertices, measurements, correspondingPoint, correspondingView, names, ptc); + + VERBOSE("Input data exported: %u images & %u points (%s)", scene.images.size(), scene.pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str()); + return true; +} + + +bool ImportSceneVSFM() { TD_TIMER_START(); @@ -315,11 +408,7 @@ int ImportSceneVSFM() camera.R = RMatrix::IDENTITY; camera.C = CMatrix::ZERO; // normalize camera intrinsics - const REAL fScale(REAL(1)/MVS::Camera::GetNormalizationScale(image.width, image.height)); - camera.K(0,0) *= fScale; - camera.K(1,1) *= fScale; - camera.K(0,2) *= fScale; - camera.K(1,2) *= fScale; + camera.K = camera.GetScaledK(REAL(1)/MVS::Camera::GetNormalizationScale(image.width, image.height)); // set pose image.poseID = platform.poses.GetSize(); MVS::Platform::Pose& pose = platform.poses.AddEmpty(); @@ -333,16 +422,16 @@ int ImportSceneVSFM() const PBA::Point3D& X = vertices[idx]; scene.pointcloud.points.AddConstruct(X.xyz[0], X.xyz[1], X.xyz[2]); } - if (ptc.size() == vertices.size()*3) { - scene.pointcloud.colors.Reserve(ptc.size()); - for (size_t idx=0; idx(&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(2), "project archive type: 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)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 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 - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -104,19 +108,20 @@ bool Initialize(size_t argc, LPCTSTR* argv) config_main.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") ("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)") - ("constant-weight", boost::program_options::value(&OPT::bUseConstantWeight)->default_value(true), "considers all view weights 1 instead of the available weight") - ("free-space-support,f", boost::program_options::value(&OPT::bUseFreeSpaceSupport)->default_value(false), "exploits the free-space support in order to reconstruct weakly-represented surfaces") - ("thickness-factor", boost::program_options::value(&OPT::fThicknessFactor)->default_value(1.f), "multiplier adjusting the minimum thickness considered during visibility weighting") - ("quality-factor", boost::program_options::value(&OPT::fQualityFactor)->default_value(1.f), "multiplier adjusting the quality weight considered during graph-cut") + ("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)") + ("constant-weight", boost::program_options::value(&OPT::bUseConstantWeight)->default_value(true), "considers all view weights 1 instead of the available weight") + ("free-space-support,f", boost::program_options::value(&OPT::bUseFreeSpaceSupport)->default_value(false), "exploits the free-space support in order to reconstruct weakly-represented surfaces") + ("thickness-factor", boost::program_options::value(&OPT::fThicknessFactor)->default_value(1.f), "multiplier adjusting the minimum thickness considered during visibility weighting") + ("quality-factor", boost::program_options::value(&OPT::fQualityFactor)->default_value(1.f), "multiplier adjusting the quality weight considered during graph-cut") ; boost::program_options::options_description config_clean("Clean options"); config_clean.add_options() - ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(1.f), "decimation factor in range (0..1] to be applied to the reconstructed surface (1 - disabled)") - ("remove-spurious", boost::program_options::value(&OPT::fRemoveSpurious)->default_value(20.f), "spurious factor for removing faces with too long edges or isolated components (0 - disabled)") - ("remove-spikes", boost::program_options::value(&OPT::bRemoveSpikes)->default_value(true), "flag controlling the removal of spike faces") - ("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the reconstructed surface (0 - disabled)") - ("smooth", boost::program_options::value(&OPT::nSmoothMesh)->default_value(2), "number of iterations to smooth the reconstructed surface (0 - disabled)") + ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(1.f), "decimation factor in range (0..1] to be applied to the reconstructed surface (1 - disabled)") + ("target-face-num", boost::program_options::value(&OPT::nTargetFaceNum)->default_value(0), "target number of faces to be applied to the reconstructed surface. (0 - disabled)") + ("remove-spurious", boost::program_options::value(&OPT::fRemoveSpurious)->default_value(20.f), "spurious factor for removing faces with too long edges or isolated components (0 - disabled)") + ("remove-spikes", boost::program_options::value(&OPT::bRemoveSpikes)->default_value(true), "flag controlling the removal of spike faces") + ("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the reconstructed surface (0 - disabled)") + ("smooth", boost::program_options::value(&OPT::nSmoothMesh)->default_value(2), "number of iterations to smooth the reconstructed surface (0 - disabled)") ; // hidden options, allowed both on command line and @@ -124,7 +129,8 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description hidden("Hidden options"); hidden.add_options() ("mesh-file", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to clean (skips the reconstruction step)") - ("mesh-export", boost::program_options::value(&OPT::bMeshExport)->default_value(false), "just export the mesh contained in loaded project") + ("mesh-export", boost::program_options::value(&OPT::bMeshExport)->default_value(false), "just export the mesh contained in loaded project") + ("split-max-area", boost::program_options::value(&OPT::fSplitMaxArea)->default_value(0.f), "maximum surface area that a sub-mesh can contain (0 - disabled)") ; boost::program_options::options_description cmdline_options; @@ -207,6 +213,8 @@ void Finalize() CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -219,8 +227,17 @@ int main(int argc, LPCTSTR* argv) Scene scene(OPT::nMaxThreads); // load project - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), OPT::fSplitMaxArea > 0 || OPT::fDecimateMesh < 1 || OPT::nTargetFaceNum > 0)) 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; + } if (OPT::bMeshExport) { // check there is a mesh to export if (scene.mesh.IsEmpty()) @@ -230,10 +247,10 @@ int main(int argc, LPCTSTR* argv) scene.mesh.Save(fileName); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) - scene.ExportCamerasMLP(Util::getFileFullName(OPT::strOutputFileName)+_T(".mlp"), fileName); + scene.ExportCamerasMLP(baseFileName+_T(".mlp"), fileName); #endif } else { - if (OPT::strMeshFileName.IsEmpty()) { + if (OPT::strMeshFileName.IsEmpty() && 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 @@ -281,21 +298,21 @@ int main(int argc, LPCTSTR* argv) #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) { // dump raw mesh - scene.mesh.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T("_raw")+OPT::strExportType); + scene.mesh.Save(baseFileName+_T("_raw")+OPT::strExportType); } #endif - } else { + } else if (!OPT::strMeshFileName.IsEmpty()) { // load existing mesh to clean scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName)); } // clean the mesh - scene.mesh.Clean(OPT::fDecimateMesh, OPT::fRemoveSpurious, OPT::bRemoveSpikes, OPT::nCloseHoles, OPT::nSmoothMesh, false); + const float fDecimate(OPT::nTargetFaceNum ? static_cast(OPT::nTargetFaceNum) / scene.mesh.faces.size() : OPT::fDecimateMesh); + scene.mesh.Clean(fDecimate, OPT::fRemoveSpurious, OPT::bRemoveSpikes, OPT::nCloseHoles, OPT::nSmoothMesh, false); scene.mesh.Clean(1.f, 0.f, OPT::bRemoveSpikes, OPT::nCloseHoles, 0, false); // extra cleaning trying to close more holes scene.mesh.Clean(1.f, 0.f, false, 0, 0, true); // extra cleaning to remove non-manifold problems created by closing holes // 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 diff --git a/apps/RefineMesh/CMakeLists.txt b/apps/RefineMesh/CMakeLists.txt index eaffec244..26a6f84ed 100644 --- a/apps/RefineMesh/CMakeLists.txt +++ b/apps/RefineMesh/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(RefineMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(RefineMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS RefineMesh diff --git a/apps/RefineMesh/RefineMesh.cpp b/apps/RefineMesh/RefineMesh.cpp index 1cbd6e227..fdeecc99f 100644 --- a/apps/RefineMesh/RefineMesh.cpp +++ b/apps/RefineMesh/RefineMesh.cpp @@ -43,6 +43,8 @@ using namespace MVS; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; String strOutputFileName; @@ -87,11 +89,11 @@ 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(2), "project archive type: 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)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 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 - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -106,23 +108,23 @@ bool Initialize(size_t argc, LPCTSTR* argv) config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") ("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") - ("max-views", boost::program_options::value(&OPT::nMaxViews)->default_value(8), "maximum number of neighbor images used to refine the mesh") - ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(0.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)") - ("ensure-edge-size", boost::program_options::value(&OPT::nEnsureEdgeSize)->default_value(1), "ensure edge size and improve vertex valence of the input surface (0 - disabled, 1 - auto, 2 - force)") - ("max-face-area", boost::program_options::value(&OPT::nMaxFaceArea)->default_value(64), "maximum face area projected in any pair of images that is not subdivided (0 - disabled)") - ("scales", boost::program_options::value(&OPT::nScales)->default_value(3), "how many iterations to run mesh optimization on multi-scale images") - ("scale-step", boost::program_options::value(&OPT::fScaleStep)->default_value(0.5f), "image scale factor used at each mesh optimization step") - ("reduce-memory", boost::program_options::value(&OPT::nReduceMemory)->default_value(1), "recompute some data in order to reduce memory requirements") - ("alternate-pair", boost::program_options::value(&OPT::nAlternatePair)->default_value(0), "refine mesh using an image pair alternatively as reference (0 - both, 1 - alternate, 2 - only left, 3 - only right)") - ("regularity-weight", boost::program_options::value(&OPT::fRegularityWeight)->default_value(0.2f), "scalar regularity weight to balance between photo-consistency and regularization terms during mesh optimization") - ("rigidity-elasticity-ratio", boost::program_options::value(&OPT::fRatioRigidityElasticity)->default_value(0.9f), "scalar ratio used to compute the regularity gradient as a combination of rigidity and elasticity") - ("gradient-step", boost::program_options::value(&OPT::fGradientStep)->default_value(45.05f), "gradient step to be used instead (0 - auto)") - ("planar-vertex-ratio", boost::program_options::value(&OPT::fPlanarVertexRatio)->default_value(0.f), "threshold used to remove vertices on planar patches (0 - disabled)") + ("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") + ("max-views", boost::program_options::value(&OPT::nMaxViews)->default_value(8), "maximum number of neighbor images used to refine the mesh") + ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(0.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)") + ("ensure-edge-size", boost::program_options::value(&OPT::nEnsureEdgeSize)->default_value(1), "ensure edge size and improve vertex valence of the input surface (0 - disabled, 1 - auto, 2 - force)") + ("max-face-area", boost::program_options::value(&OPT::nMaxFaceArea)->default_value(32), "maximum face area projected in any pair of images that is not subdivided (0 - disabled)") + ("scales", boost::program_options::value(&OPT::nScales)->default_value(3), "how many iterations to run mesh optimization on multi-scale images") + ("scale-step", boost::program_options::value(&OPT::fScaleStep)->default_value(0.5f), "image scale factor used at each mesh optimization step") + ("reduce-memory", boost::program_options::value(&OPT::nReduceMemory)->default_value(1), "recompute some data in order to reduce memory requirements") + ("alternate-pair", boost::program_options::value(&OPT::nAlternatePair)->default_value(0), "refine mesh using an image pair alternatively as reference (0 - both, 1 - alternate, 2 - only left, 3 - only right)") + ("regularity-weight", boost::program_options::value(&OPT::fRegularityWeight)->default_value(0.2f), "scalar regularity weight to balance between photo-consistency and regularization terms during mesh optimization") + ("rigidity-elasticity-ratio", boost::program_options::value(&OPT::fRatioRigidityElasticity)->default_value(0.9f), "scalar ratio used to compute the regularity gradient as a combination of rigidity and elasticity") + ("gradient-step", boost::program_options::value(&OPT::fGradientStep)->default_value(45.05f), "gradient step to be used instead (0 - auto)") + ("planar-vertex-ratio", boost::program_options::value(&OPT::fPlanarVertexRatio)->default_value(0.f), "threshold used to remove vertices on planar patches (0 - disabled)") #ifdef _USE_CUDA - ("use-cuda", boost::program_options::value(&OPT::bUseCUDA)->default_value(true), "refine mesh using CUDA") + ("use-cuda", boost::program_options::value(&OPT::bUseCUDA)->default_value(false), "refine mesh using CUDA") #endif ; @@ -213,6 +215,8 @@ void Finalize() CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO diff --git a/apps/TextureMesh/CMakeLists.txt b/apps/TextureMesh/CMakeLists.txt index b53e2f560..bef488eeb 100644 --- a/apps/TextureMesh/CMakeLists.txt +++ b/apps/TextureMesh/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(TextureMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(TextureMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS TextureMesh diff --git a/apps/TextureMesh/TextureMesh.cpp b/apps/TextureMesh/TextureMesh.cpp index 23f1421d1..6783a82fc 100644 --- a/apps/TextureMesh/TextureMesh.cpp +++ b/apps/TextureMesh/TextureMesh.cpp @@ -43,6 +43,8 @@ using namespace MVS; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; String strOutputFileName; @@ -80,12 +82,12 @@ 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") - ("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(2), "project archive type: 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)") + ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("glb")), "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") + ("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 - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -100,18 +102,18 @@ bool Initialize(size_t argc, LPCTSTR* argv) config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") ("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)") - ("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") - ("outlier-threshold", boost::program_options::value(&OPT::fOutlierThreshold)->default_value(6e-2f), "threshold used to find and remove outlier face textures (0 - disabled)") - ("cost-smoothness-ratio", boost::program_options::value(&OPT::fRatioDataSmoothness)->default_value(0.1f), "ratio used to adjust the preference for more compact patches (1 - best quality/worst compactness, ~0 - worst quality/best compactness)") - ("global-seam-leveling", boost::program_options::value(&OPT::bGlobalSeamLeveling)->default_value(true), "generate uniform texture patches using global seam leveling") - ("local-seam-leveling", boost::program_options::value(&OPT::bLocalSeamLeveling)->default_value(true), "generate uniform texture patch borders using local seam leveling") - ("texture-size-multiple", boost::program_options::value(&OPT::nTextureSizeMultiple)->default_value(0), "texture size should be a multiple of this value (0 - power of two)") - ("patch-packing-heuristic", boost::program_options::value(&OPT::nRectPackingHeuristic)->default_value(3), "specify the heuristic used when deciding where to place a new patch (0 - best fit, 3 - good speed, 100 - best speed)") - ("empty-color", boost::program_options::value(&OPT::nColEmpty)->default_value(0x00FF7F27), "color used for faces not covered by any image") - ("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)") + ("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)") + ("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") + ("outlier-threshold", boost::program_options::value(&OPT::fOutlierThreshold)->default_value(6e-2f), "threshold used to find and remove outlier face textures (0 - disabled)") + ("cost-smoothness-ratio", boost::program_options::value(&OPT::fRatioDataSmoothness)->default_value(0.1f), "ratio used to adjust the preference for more compact patches (1 - best quality/worst compactness, ~0 - worst quality/best compactness)") + ("global-seam-leveling", boost::program_options::value(&OPT::bGlobalSeamLeveling)->default_value(true), "generate uniform texture patches using global seam leveling") + ("local-seam-leveling", boost::program_options::value(&OPT::bLocalSeamLeveling)->default_value(true), "generate uniform texture patch borders using local seam leveling") + ("texture-size-multiple", boost::program_options::value(&OPT::nTextureSizeMultiple)->default_value(0), "texture size should be a multiple of this value (0 - power of two)") + ("patch-packing-heuristic", boost::program_options::value(&OPT::nRectPackingHeuristic)->default_value(3), "specify the heuristic used when deciding where to place a new patch (0 - best fit, 3 - good speed, 100 - best speed)") + ("empty-color", boost::program_options::value(&OPT::nColEmpty)->default_value(0x00FF7F27), "color used for faces not covered by any image") + ("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)") ; // hidden options, allowed both on command line and @@ -164,7 +166,17 @@ bool Initialize(size_t argc, LPCTSTR* argv) } if (OPT::strInputFileName.IsEmpty()) return false; - OPT::strExportType = OPT::strExportType.ToLower() == _T("obj") ? _T(".obj") : _T(".ply"); + OPT::strExportType = OPT::strExportType.ToLower(); + if (OPT::strExportType == _T("obj")) + OPT::strExportType = _T(".obj"); + else + if (OPT::strExportType == _T("glb")) + OPT::strExportType = _T(".glb"); + else + if (OPT::strExportType == _T("gltf")) + OPT::strExportType = _T(".gltf"); + else + OPT::strExportType = _T(".ply"); // initialize optional options Util::ensureValidPath(OPT::strOutputFileName); @@ -201,6 +213,8 @@ void Finalize() CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO diff --git a/apps/Viewer/CMakeLists.txt b/apps/Viewer/CMakeLists.txt index c519040d1..fe0920242 100644 --- a/apps/Viewer/CMakeLists.txt +++ b/apps/Viewer/CMakeLists.txt @@ -16,32 +16,17 @@ else() MESSAGE("-- Can't find GLEW. Continuing without it.") RETURN() endif() -if(CMAKE_COMPILER_IS_GNUCXX) - FIND_PACKAGE(PkgConfig QUIET) - pkg_search_module(GLFW QUIET glfw3) - if(GLFW_FOUND) - INCLUDE_DIRECTORIES(${GLFW_INCLUDE_DIRS}) - ADD_DEFINITIONS(${GLFW_DEFINITIONS}) - MESSAGE(STATUS "GLFW3 ${GLFW_VERSION} found (include: ${GLFW_INCLUDE_DIRS})") - else() - MESSAGE("-- Can't find GLFW3. Continuing without it.") - RETURN() - endif() +FIND_PACKAGE(glfw3 QUIET) +if(glfw3_FOUND) + INCLUDE_DIRECTORIES(${glfw3_INCLUDE_DIRS}) + ADD_DEFINITIONS(${glfw3_DEFINITIONS}) + MESSAGE(STATUS "GLFW3 ${glfw3_VERSION} found (include: ${glfw3_INCLUDE_DIRS})") else() - FIND_PACKAGE(glfw3 QUIET) - if(glfw3_FOUND) - INCLUDE_DIRECTORIES(${glfw3_INCLUDE_DIRS}) - ADD_DEFINITIONS(${glfw3_DEFINITIONS}) - MESSAGE(STATUS "GLFW3 ${glfw3_VERSION} found (include: ${glfw3_INCLUDE_DIRS})") - else() - MESSAGE("-- Can't find GLFW3. Continuing without it.") - RETURN() - endif() + MESSAGE("-- Can't find GLFW3. Continuing without it.") + RETURN() endif() # List sources files -FILE(GLOB PCH_C "Common.cpp") - if(MSVC) FILE(GLOB LIBRARY_FILES_C "*.cpp" "*.rc") else() @@ -49,15 +34,12 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -# Place Common.cpp as the first file in the list -# needed by cotire when setting PCH manually -LIST(REMOVE_ITEM LIBRARY_FILES_C ${PCH_C}) -SET(LIBRARY_FILES_C "${PCH_C};${LIBRARY_FILES_C}") - -cxx_executable_with_flags_no_pch(${VIEWER_NAME} "Apps" "${cxx_default}" "MVS;${OPENGL_LIBRARIES};${GLEW_LIBRARY};${GLFW_STATIC_LIBRARIES};GLEW::GLEW;${glfw3_LIBRARY};${GLFW3_LIBRARY};glfw;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(${VIEWER_NAME} "Apps" "${cxx_default}" "MVS;${OPENGL_LIBRARIES};${GLEW_LIBRARY};${GLFW_STATIC_LIBRARIES};GLEW::GLEW;${glfw3_LIBRARY};${GLFW3_LIBRARY};glfw;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Manually set Common.h as the precompiled header -set_target_pch(${VIEWER_NAME} Common.h) +IF(CMAKE_VERSION VERSION_GREATER_EQUAL 3.16.0) + TARGET_PRECOMPILE_HEADERS(${VIEWER_NAME} PRIVATE "Common.h") +endif() # Install INSTALL(TARGETS ${VIEWER_NAME} diff --git a/apps/Viewer/Camera.cpp b/apps/Viewer/Camera.cpp index b217c2d6b..e6cb75044 100644 --- a/apps/Viewer/Camera.cpp +++ b/apps/Viewer/Camera.cpp @@ -40,122 +40,136 @@ using namespace VIEWER; // S T R U C T S /////////////////////////////////////////////////// -Camera::Camera(const AABB3d& _box, double _fov) +Camera::Camera(const AABB3d& _box, const Point3d& _center, float _scaleF, float _fov) : - box(_box), - width(0), height(0), + boxScene(_box), + centerScene(_center), rotation(Eigen::Quaterniond::Identity()), center(Eigen::Vector3d::Zero()), - dist(0), radius(100), fov(_fov), - scaleF(1.f), + dist(0), radius(100), + fovDef(_fov), scaleFDef(_scaleF), prevCamID(NO_ID), currentCamID(NO_ID), maxCamID(0) { Reset(); } -void Camera::CopyOf(const Camera& rhs) -{ - rotation = rhs.rotation; - center = rhs.center; - dist = rhs.dist; - radius = rhs.radius; - fov = rhs.fov; -} - - -void Camera::Init(const AABB3d& _box) -{ - box = _box; - Reset(); -} - void Camera::Reset() { - center = box.GetCenter(); - radius = box.GetSize().norm()*0.5; + if (boxScene.IsEmpty()) { + center = Point3d::ZERO; + radius = 1; + } else { + center = centerScene; + radius = boxScene.GetSize().norm()*0.5; + } rotation = Eigen::Quaterniond::Identity(); - scaleF = 1.f; + scaleF = scaleFDef; prevCamID = currentCamID = NO_ID; - fov = 40; - dist = radius * 0.5 / SIN(D2R(fov)); - Resize(width, height); + fov = fovDef; + dist = radius*0.5 / SIN(D2R((double)fov)); + if (size.area()) + Resize(size); } -void Camera::Resize(int _width, int _height) +void Camera::Resize(const cv::Size& _size) { + ASSERT(MINF(_size.width, _size.height) > 0); + size = _size; glMatrixMode(GL_PROJECTION); glLoadIdentity(); - const GLfloat zNear = 1e-2f; - const GLfloat zFar = 1e5f; - width = _width; height = _height; - GLfloat aspect = float(width)/float(height); - GLfloat fH = TAN(FD2R((float)fov)) * zNear; - GLfloat fW = fH * aspect; - glFrustum(-fW, fW, -fH, fH, zNear, zFar); + const GLfloat zNear = 1e-3f; + const GLfloat zFar = (float)boxScene.GetSize().norm()*10; + const GLfloat aspect = float(size.width)/float(size.height); + if (fov == 5.f) { + // orthographic projection + const GLfloat fH = (float)boxScene.GetSize().norm()*0.5f; + const GLfloat fW = fH * aspect; + glOrtho(-fW, fW, -fH, fH, zNear, zFar); + } else { + // perspective projection + const GLfloat fH = TAN(FD2R(fov)) * zNear; + const GLfloat fW = fH * aspect; + glFrustum(-fW, fW, -fH, fH, zNear, zFar); + } } -void Camera::SetFOV(double _fov) +void Camera::SetFOV(float _fov) { - fov = _fov; - Resize(width, height); + fov = MAXF(_fov, 5.f); + Resize(size); } Eigen::Vector3d Camera::GetPosition() const { - const Eigen::Matrix3d R(rotation.toRotationMatrix()); - const Eigen::Vector3d eye(0, 0, dist); - return R * eye + center; + const Eigen::Matrix3d R(GetRotation()); + return center + R.col(2) * dist; +} + +Eigen::Matrix3d Camera::GetRotation() const +{ + return rotation.toRotationMatrix(); } Eigen::Matrix4d Camera::GetLookAt() const { - const Eigen::Matrix3d R(rotation.toRotationMatrix()); - const Eigen::Vector3d eye(R.col(2) * dist + center); + const Eigen::Matrix3d R(GetRotation()); + const Eigen::Vector3d eye(center + R.col(2) * dist); const Eigen::Vector3d up(R.col(1)); - + const Eigen::Vector3d n((center-eye).normalized()); const Eigen::Vector3d s(n.cross(up)); const Eigen::Vector3d v(s.cross(n)); - - Eigen::Matrix4d m; - m << - s(0), s(1), s(2), -eye.dot(s), - v(0), v(1), v(2), -eye.dot(v), - -n(0), -n(1), -n(2), eye.dot(n), - 0.0, 0.0, 0.0, 1.0; + + Eigen::Matrix4d m; m << + s(0), s(1), s(2), -eye.dot(s), + v(0), v(1), v(2), -eye.dot(v), + -n(0), -n(1), -n(2), eye.dot(n), + 0.0, 0.0, 0.0, 1.0; return m; } void Camera::GetLookAt(Eigen::Vector3d& _eye, Eigen::Vector3d& _center, Eigen::Vector3d& _up) const { - const Eigen::Matrix3d R(rotation.toRotationMatrix()); - const Eigen::Vector3d eye(0, 0, dist); - const Eigen::Vector3d up(0, 1, 0); - - _eye = R * eye + center; + const Eigen::Matrix3d R(GetRotation()); + _eye = center + R.col(2) * dist; _center = center; - _up = R * up; + _up = R.col(1); } + void Camera::Rotate(const Eigen::Vector2d& pos, const Eigen::Vector2d& prevPos) { - if (pos.isApprox(prevPos, ZERO_TOLERANCE)) + if (pos.isApprox(prevPos, ZEROTOLERANCE())) return; Eigen::Vector3d oldp(prevPos.x(), prevPos.y(), 0); Eigen::Vector3d newp(pos.x(), pos.y(), 0); - const double radius_virtual_sphere(0.9); - Project2Sphere(radius_virtual_sphere, oldp); - Project2Sphere(radius_virtual_sphere, newp); - Eigen::Quaterniond dr; - dr.setFromTwoVectors(newp, oldp); - rotation *= dr; + const double radiusSphere(0.9); + ProjectOnSphere(radiusSphere, oldp); + ProjectOnSphere(radiusSphere, newp); + rotation *= Eigen::Quaterniond().setFromTwoVectors(newp, oldp); + + // disable camera view mode + prevCamID = currentCamID; +} + +void Camera::Translate(const Eigen::Vector2d& pos, const Eigen::Vector2d& prevPos) +{ + if (pos.isApprox(prevPos, ZEROTOLERANCE())) + return; + + Eigen::Matrix P, V; + glGetDoublev(GL_MODELVIEW_MATRIX, V.data()); + glGetDoublev(GL_PROJECTION_MATRIX, P.data()); + Eigen::Vector3d centerScreen((P*V*center.homogeneous().eval()).hnormalized()); + centerScreen.head<2>() += prevPos - pos; + center = (V.inverse()*P.inverse()*centerScreen.homogeneous().eval()).hnormalized(); // disable camera view mode prevCamID = currentCamID; } -void Camera::Project2Sphere(double radius, Eigen::Vector3d& p) const +void Camera::ProjectOnSphere(double radius, Eigen::Vector3d& p) const { p.z() = 0; const double d = p.x()* p.x()+ p.y() * p.y(); diff --git a/apps/Viewer/Camera.h b/apps/Viewer/Camera.h index 37d982386..d83880a3e 100644 --- a/apps/Viewer/Camera.h +++ b/apps/Viewer/Camera.h @@ -48,34 +48,38 @@ class Camera public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW - AABB3d box; - int width, height; + cv::Size size; + AABB3d boxScene; + Eigen::Vector3d centerScene; Eigen::Quaterniond rotation; Eigen::Vector3d center; - double dist; - double radius; - double fov; - float scaleF; + double dist, radius; + float fov, fovDef; + float scaleF, scaleFDef; MVS::IIndex prevCamID, currentCamID, maxCamID; public: - explicit Camera(const AABB3d& _box=AABB3d(true), double _fov=40); - void CopyOf(const Camera&); + Camera(const AABB3d& _box=AABB3d(true), const Point3d& _center=Point3d::ZERO, float _scaleF=1, float _fov=40); - void Init(const AABB3d&); void Reset(); - void Resize(int _width, int _height); - void SetFOV(double _fov); + void Resize(const cv::Size&); + void SetFOV(float _fov); + + const cv::Size& GetSize() const { return size; } Eigen::Vector3d GetPosition() const; + Eigen::Matrix3d GetRotation() const; Eigen::Matrix4d GetLookAt() const; + void GetLookAt(Eigen::Vector3d& eye, Eigen::Vector3d& center, Eigen::Vector3d& up) const; void Rotate(const Eigen::Vector2d& pos, const Eigen::Vector2d& prevPos); + void Translate(const Eigen::Vector2d& pos, const Eigen::Vector2d& prevPos); + + bool IsCameraViewMode() const { return prevCamID != currentCamID && currentCamID != NO_ID; } protected: - void Project2Sphere(double radius, Eigen::Vector3d& p) const; + void ProjectOnSphere(double radius, Eigen::Vector3d& p) const; }; -typedef CSharedPtr CameraPtr; /*----------------------------------------------------------------*/ } // namespace VIEWER diff --git a/apps/Viewer/Common.h b/apps/Viewer/Common.h index d1f1912b5..f3387834e 100644 --- a/apps/Viewer/Common.h +++ b/apps/Viewer/Common.h @@ -59,16 +59,19 @@ using namespace SEACAVE; namespace VIEWER { // the conversion matrix from OpenGL default coordinate system -// to the camera coordinate system: +// to the camera coordinate system (NADIR orientation): // [ 1 0 0 0] * [ x ] = [ x ] // 0 -1 0 0 y -y // 0 0 -1 0 z -z // 0 0 0 1 1 1 -static const GLfloat gs_convert[4][4] = { - {1.f, 0.f, 0.f, 0.f}, - {0.f, -1.f, 0.f, 0.f}, - {0.f, 0.f, -1.f, 0.f}, - {0.f, 0.f, 0.f, 1.f}}; +static const Eigen::Matrix4d gs_convert = [] { + Eigen::Matrix4d tmp; tmp << + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1; + return tmp; +}(); /// given rotation matrix R and translation vector t, /// column-major matrix m is equal to: diff --git a/apps/Viewer/Scene.cpp b/apps/Viewer/Scene.cpp index b40cb1bf2..a7d7e314f 100644 --- a/apps/Viewer/Scene.cpp +++ b/apps/Viewer/Scene.cpp @@ -178,12 +178,16 @@ class EVTComputeOctree : public Event bool Run(void*) { MVS::Scene& scene = pScene->scene; if (!scene.mesh.IsEmpty()) { - Scene::OctreeMesh octMesh(scene.mesh.vertices); + Scene::OctreeMesh octMesh(scene.mesh.vertices, [](Scene::OctreeMesh::IDX_TYPE size, Scene::OctreeMesh::Type /*radius*/) { + return size > 256; + }); scene.mesh.ListIncidenteFaces(); pScene->octMesh.Swap(octMesh); } else if (!scene.pointcloud.IsEmpty()) { - Scene::OctreePoints octPoints(scene.pointcloud.points); + Scene::OctreePoints octPoints(scene.pointcloud.points, [](Scene::OctreePoints::IDX_TYPE size, Scene::OctreePoints::Type /*radius*/) { + return size > 512; + }); pScene->octPoints.Swap(octPoints); } return true; @@ -230,6 +234,12 @@ void Scene::Empty() { ReleasePointCloud(); ReleaseMesh(); + obbPoints.Release(); + if (window.IsValid()) { + window.ReleaseClbk(); + window.Reset(); + window.SetName(_T("(empty)")); + } textures.Release(); images.Release(); scene.Release(); @@ -237,6 +247,8 @@ void Scene::Empty() } void Scene::Release() { + if (window.IsValid()) + window.SetVisible(false); if (!thread.isRunning()) { events.AddEvent(new EVTClose()); thread.join(); @@ -260,23 +272,19 @@ void Scene::ReleaseMesh() } } -bool Scene::Init(int width, int height, LPCTSTR windowName, LPCTSTR fileName, LPCTSTR meshFileName) +bool Scene::Init(const cv::Size& size, LPCTSTR windowName, LPCTSTR fileName, LPCTSTR meshFileName) { ASSERT(scene.IsEmpty()); // init window if (glfwInit() == GL_FALSE) return false; - if (!window.Init(width, height, windowName)) + if (!window.Init(size, windowName)) return false; if (glewInit() != GLEW_OK) return false; name = windowName; window.clbkOpenScene = DELEGATEBINDCLASS(Window::ClbkOpenScene, &Scene::Open, this); - window.clbkExportScene = DELEGATEBINDCLASS(Window::ClbkExportScene, &Scene::Export, this); - window.clbkRayScene = DELEGATEBINDCLASS(Window::ClbkRayScene, &Scene::CastRay, this); - window.clbkCompilePointCloud = DELEGATEBINDCLASS(Window::ClbkCompilePointCloud, &Scene::CompilePointCloud, this); - window.clbkCompileMesh = DELEGATEBINDCLASS(Window::ClbkCompileMesh, &Scene::CompileMesh, this); // init OpenGL glPolygonMode(GL_FRONT, GL_FILL); @@ -301,9 +309,9 @@ bool Scene::Init(int width, int height, LPCTSTR windowName, LPCTSTR fileName, LP thread.start(ThreadWorker); // open scene or init empty scene - if (fileName == NULL || !Open(fileName, meshFileName)) - window.SetCamera(CameraPtr(new Camera())); - + window.SetCamera(Camera()); + if (fileName != NULL) + Open(fileName, meshFileName); window.SetVisible(true); return true; } @@ -333,14 +341,18 @@ bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) // init scene AABB3d bounds(true); + AABB3d imageBounds(true); + Point3d center(Point3d::INF); if (!scene.pointcloud.IsEmpty()) { bounds = scene.pointcloud.GetAABB(MINF(3u,scene.nCalibratedImages)); if (bounds.IsEmpty()) bounds = scene.pointcloud.GetAABB(); + center = scene.pointcloud.GetCenter(); } if (!scene.mesh.IsEmpty()) { scene.mesh.ComputeNormalFaces(); bounds.Insert(scene.mesh.GetAABB()); + center = scene.mesh.GetCenter(); } // init images @@ -349,7 +361,8 @@ bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) const MVS::Image& imageData = scene.images[idxImage]; if (!imageData.IsValid()) continue; - images.AddConstruct(idxImage); + images.emplace_back(idxImage); + imageBounds.InsertFull(imageData.camera.C); } // init and load texture @@ -373,12 +386,24 @@ bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) CompilePointCloud(); // compile mesh CompileMesh(); + // compile bounding-box + CompileBounds(); // init camera - window.SetCamera(CameraPtr(new Camera(bounds))); - window.camera->maxCamID = images.size(); + window.SetCamera(Camera(bounds, + center == Point3d::INF ? Point3d(bounds.GetCenter()) : center, + images.size()<2?1.f:(float)imageBounds.EnlargePercent(REAL(1)/images.size()).GetSize().norm())); + window.camera.maxCamID = images.size(); window.SetName(String::FormatString((name + _T(": %s")).c_str(), Util::getFileName(fileName).c_str())); - window.Reset(MINF(2u, images.size())); + window.clbkExportScene = DELEGATEBINDCLASS(Window::ClbkExportScene, &Scene::Export, this); + window.clbkCompilePointCloud = DELEGATEBINDCLASS(Window::ClbkCompilePointCloud, &Scene::CompilePointCloud, this); + window.clbkCompileMesh = DELEGATEBINDCLASS(Window::ClbkCompileMesh, &Scene::CompileMesh, this); + if (scene.IsBounded()) + window.clbkCompileBounds = DELEGATEBINDCLASS(Window::ClbkCompileBounds, &Scene::CompileBounds, this); + if (!bounds.IsEmpty()) + window.clbkRayScene = DELEGATEBINDCLASS(Window::ClbkRayScene, &Scene::CastRay, this); + window.Reset(!scene.pointcloud.IsEmpty()&&!scene.mesh.IsEmpty()?Window::SPR_NONE:Window::SPR_ALL, + MINF(2u,images.size())); return true; } @@ -459,13 +484,32 @@ void Scene::CompileMesh() glEndList(); } +void Scene::CompileBounds() +{ + if (!scene.IsBounded()) + return; + obbPoints.Release(); + window.bRenderBounds = !window.bRenderBounds; + if (window.bRenderBounds) { + static const uint8_t indices[12*2] = { + 0,2, 2,3, 3,1, 1,0, + 0,6, 2,4, 3,5, 1,7, + 6,4, 4,5, 5,7, 7,6 + }; + OBB3f::POINT corners[OBB3f::numCorners]; + scene.obb.GetCorners(corners); + for (int i=0; i<12; ++i) { + obbPoints.emplace_back(corners[indices[i*2+0]]); + obbPoints.emplace_back(corners[indices[i*2+1]]); + } + } +} + void Scene::Draw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPointSize(window.pointSize); - window.UpdateView(images, scene.images); - // render point-cloud if (listPointCloud) { glDisable(GL_TEXTURE_2D); @@ -489,6 +533,7 @@ void Scene::Draw() // render cameras if (window.bRenderCameras) { glDisable(GL_CULL_FACE); + const Point3* ptrPrevC(NULL); FOREACH(idx, images) { Image& image = images[idx]; const MVS::Image& imageData = scene.images[image.idx]; @@ -499,7 +544,7 @@ void Scene::Draw() glPointSize(window.pointSize+1.f); glDisable(GL_TEXTURE_2D); // draw camera position and image center - const double scaleFocal(window.camera->scaleF); + const double scaleFocal(window.camera.scaleF); glBegin(GL_POINTS); glColor3f(1,0,0); glVertex3f(0,0,0); // camera position glColor3f(0,1,0); glVertex3f(0,0,(float)scaleFocal); // image center @@ -516,7 +561,7 @@ void Scene::Draw() const Point3d ic3(px, py, scaleFocal); const Point3d ic4(px, cy, scaleFocal); // draw image thumbnail - const bool bSelectedImage(idx == window.camera->currentCamID); + const bool bSelectedImage(idx == window.camera.currentCamID); if (bSelectedImage) { if (image.IsValid()) { // render image @@ -561,8 +606,39 @@ void Scene::Draw() glEnd(); // restore coordinate system glPopMatrix(); + // render image visibility info + if (window.bRenderImageVisibility && idx != NO_ID && idx==window.camera.currentCamID) { + if (scene.pointcloud.IsValid()) { + const Image& image = images[idx]; + glPointSize(window.pointSize*1.1f); + glDisable(GL_DEPTH_TEST); + glBegin(GL_POINTS); + glColor3f(1.f,0.f,0.f); + FOREACH(i, scene.pointcloud.points) { + ASSERT(!scene.pointcloud.pointViews[i].empty()); + if (scene.pointcloud.pointViews[i].size() < window.minViews) + continue; + if (scene.pointcloud.pointViews[i].FindFirst(image.idx) == MVS::PointCloud::ViewArr::NO_INDEX) + continue; + glVertex3fv(scene.pointcloud.points[i].ptr()); + } + glEnd(); + glEnable(GL_DEPTH_TEST); + glPointSize(window.pointSize); + } + } + // render camera trajectory + if (window.bRenderCameraTrajectory && ptrPrevC) { + glBegin(GL_LINES); + glColor3f(1.f,0.5f,0.f); + glVertex3dv(ptrPrevC->ptr()); + glVertex3dv(camera.C.ptr()); + glEnd(); + } + ptrPrevC = &camera.C; } } + // render selection if (window.selectionType != Window::SEL_NA) { glPointSize(window.pointSize+4); glDisable(GL_DEPTH_TEST); @@ -573,25 +649,43 @@ void Scene::Draw() glColor3f(0,0,1); glVertex3fv(window.selectionPoints[2].ptr()); } glEnd(); + if (window.bRenderViews && window.selectionType == Window::SEL_POINT) { + if (!scene.pointcloud.pointViews.empty()) { + glBegin(GL_LINES); + const MVS::PointCloud::ViewArr& views = scene.pointcloud.pointViews[(MVS::PointCloud::Index)window.selectionIdx]; + ASSERT(!views.empty()); + for (MVS::PointCloud::View idxImage: views) { + const MVS::Image& imageData = scene.images[idxImage]; + glVertex3dv(imageData.camera.C.ptr()); + glVertex3fv(window.selectionPoints[0].ptr()); + } + glEnd(); + } + } glEnable(GL_DEPTH_TEST); glPointSize(window.pointSize); } + // render oriented-bounding-box + if (!obbPoints.empty()) { + glDepthMask(GL_FALSE); + glBegin(GL_LINES); + glColor3f(0.5f,0.1f,0.8f); + for (int i=0; iRotate(window.pos, window.prevPos); -} - void Scene::Loop() { while (!glfwWindowShouldClose(window.GetWindow())) { - ProcessEvents(); + window.UpdateView(images, scene.images); Draw(); + glfwWaitEvents(); } } @@ -616,8 +710,7 @@ void Scene::CastRay(const Ray3& ray, int action) if (window.selectionType != Window::SEL_NA && now-window.selectionTime < timeDblClick) { // this is a double click, center scene at the selected point - window.camera->center = Point3d(window.selectionPoints[3]); - window.camera->dist *= 0.7; + window.CenterCamera(window.selectionPoints[3]); window.selectionTime = now; } else if (!octMesh.IsEmpty()) { @@ -631,6 +724,7 @@ void Scene::CastRay(const Ray3& ray, int action) window.selectionPoints[3] = (ray.m_pOrig + ray.m_vDir*intRay.pick.dist).cast(); window.selectionType = Window::SEL_TRIANGLE; window.selectionTime = now; + window.selectionIdx = intRay.pick.idx; DEBUG("Face selected:\n\tindex: %u\n\tvertex 1: %u (%g %g %g)\n\tvertex 2: %u (%g %g %g)\n\tvertex 3: %u (%g %g %g)", intRay.pick.idx, face[0], window.selectionPoints[0].x, window.selectionPoints[0].y, window.selectionPoints[0].z, @@ -648,6 +742,7 @@ void Scene::CastRay(const Ray3& ray, int action) window.selectionPoints[0] = window.selectionPoints[3] = scene.pointcloud.points[intRay.pick.idx]; window.selectionType = Window::SEL_POINT; window.selectionTime = now; + window.selectionIdx = intRay.pick.idx; DEBUG("Point selected:\n\tindex: %u (%g %g %g)%s", intRay.pick.idx, window.selectionPoints[0].x, window.selectionPoints[0].y, window.selectionPoints[0].z, @@ -666,7 +761,7 @@ void Scene::CastRay(const Ray3& ray, int action) }().c_str() ); } else { - window.selectionType = Window::SEL_POINT; + window.selectionType = Window::SEL_NA; } } break; } diff --git a/apps/Viewer/Scene.h b/apps/Viewer/Scene.h index 0d31fa076..8704e24eb 100644 --- a/apps/Viewer/Scene.h +++ b/apps/Viewer/Scene.h @@ -48,8 +48,8 @@ namespace VIEWER { class Scene { public: - typedef TOctree OctreePoints; - typedef TOctree OctreeMesh; + typedef TOctree OctreePoints; + typedef TOctree OctreeMesh; public: String name; @@ -62,6 +62,7 @@ class Scene OctreePoints octPoints; OctreeMesh octMesh; + Point3fArr obbPoints; GLuint listPointCloud; GLuint listMesh; @@ -82,14 +83,14 @@ class Scene inline bool IsOpen() const { return IsValid() && !scene.IsEmpty(); } inline bool IsOctreeValid() const { return !octPoints.IsEmpty() || !octMesh.IsEmpty(); } - bool Init(int width, int height, LPCTSTR windowName, LPCTSTR fileName=NULL, LPCTSTR meshFileName=NULL); + bool Init(const cv::Size&, LPCTSTR windowName, LPCTSTR fileName=NULL, LPCTSTR meshFileName=NULL); bool Open(LPCTSTR fileName, LPCTSTR meshFileName=NULL); bool Export(LPCTSTR fileName, LPCTSTR exportType=NULL, bool losslessTexture=false) const; void CompilePointCloud(); void CompileMesh(); + void CompileBounds(); void Draw(); - void ProcessEvents(); void Loop(); void CastRay(const Ray3&, int); diff --git a/apps/Viewer/Viewer.cpp b/apps/Viewer/Viewer.cpp index 2470e3abf..a6fffebe9 100644 --- a/apps/Viewer/Viewer.cpp +++ b/apps/Viewer/Viewer.cpp @@ -44,6 +44,8 @@ using namespace VIEWER; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; String strOutputFileName; @@ -75,13 +77,13 @@ 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(2), "project archive type: 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)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 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)") #if TD_VERBOSE != TD_VERBOSE_OFF - ("log-file", boost::program_options::value(&OPT::bLogFile)->default_value(false), "dump log to a file") - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("log-file", boost::program_options::value(&OPT::bLogFile)->default_value(false), "dump log to a file") + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -96,7 +98,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input project filename containing camera poses and scene (point-cloud/mesh)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") - ("texture-lossless", boost::program_options::value(&OPT::bLosslessTexture)->default_value(false), "export texture using a lossless image format") + ("texture-lossless", boost::program_options::value(&OPT::bLosslessTexture)->default_value(false), "export texture using a lossless image format") ; // hidden options, allowed both on command line and @@ -152,10 +154,14 @@ bool Initialize(size_t argc, LPCTSTR* argv) "Keys:\n" "\tE: export scene\n" "\tR: reset scene\n" + "\tB: render bounds\n" "\tC: render cameras\n" + "\tC + Shift: render camera trajectory\n" "\tLeft/Right: select next camera to view the scene\n" - "\tW: render wire-frame mesh\n" "\tT: render mesh texture\n" + "\tW: render wire-frame mesh\n" + "\tV: render view rays to the selected point\n" + "\tV + Shift: render points seen by the current view\n" "\tUp/Down: adjust point size\n" "\tUp/Down + Shift: adjust minimum number of views accepted when displaying a point or line\n" "\t+/-: adjust camera thumbnail transparency\n" @@ -200,6 +206,8 @@ void Finalize() CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -212,7 +220,7 @@ int main(int argc, LPCTSTR* argv) // create viewer Scene viewer; - if (!viewer.Init(1280, 720, APPNAME, + 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())) return EXIT_FAILURE; diff --git a/apps/Viewer/Window.cpp b/apps/Viewer/Window.cpp index 4cb3eeef4..e03d6d289 100644 --- a/apps/Viewer/Window.cpp +++ b/apps/Viewer/Window.cpp @@ -57,39 +57,53 @@ Window::~Window() void Window::Release() { if (IsValid()) { + #ifdef _USE_NUKLEAR + nk_glfw3_shutdown(); + #endif glfwDestroyWindow(window); window = NULL; } clbkOpenScene.reset(); + ReleaseClbk(); +} + +void Window::ReleaseClbk() +{ clbkExportScene.reset(); clbkRayScene.reset(); clbkCompilePointCloud.reset(); clbkCompileMesh.reset(); + clbkCompileBounds.reset(); } -bool Window::Init(int width, int height, LPCTSTR name) +bool Window::Init(const cv::Size& _size, LPCTSTR name) { + sizeScale = 1; + size = _size; + glfwDefaultWindowHints(); glfwWindowHint(GLFW_VISIBLE, 0); - window = glfwCreateWindow(width, height, name, NULL, NULL); + window = glfwCreateWindow(size.width, size.height, name, NULL, NULL); if (!window) return false; glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, Window::Resize); glfwSetKeyCallback(window, Window::Key); glfwSetMouseButtonCallback(window, Window::MouseButton); + glfwSetCursorPosCallback(window, Window::MouseMove); glfwSetScrollCallback(window, Window::Scroll); glfwSetDropCallback(window, Window::Drop); g_mapWindows[window] = this; + Reset(); return true; } -void Window::SetCamera(CameraPtr cam) +void Window::SetCamera(const Camera& cam) { camera = cam; - int width, height; - glfwGetWindowSize(window, &width, &height); - Resize(width, height); + cv::Size _size; + glfwGetFramebufferSize(window, &_size.width, &_size.height); + Resize(_size); } void Window::SetName(LPCTSTR name) { @@ -102,18 +116,27 @@ void Window::SetVisible(bool v) else glfwHideWindow(window); } -void Window::Reset(uint32_t _minViews) +bool Window::IsVisible() const { - if (camera) - camera->Reset(); - sparseType = SPR_ALL; + return glfwGetWindowAttrib(window, GLFW_VISIBLE) != 0; +} +void Window::Reset(SPARSE _sparseType, unsigned _minViews) +{ + camera.Reset(); + inputType = INP_NA; + sparseType = _sparseType; minViews = _minViews; pointSize = 2.f; cameraBlend = 0.5f; bRenderCameras = true; + bRenderCameraTrajectory = true; + bRenderImageVisibility = false; + bRenderViews = true; bRenderSolid = true; bRenderTexture = true; + bRenderBounds = false; selectionType = SEL_NA; + selectionIdx = NO_IDX; if (clbkCompilePointCloud != NULL) clbkCompilePointCloud(); if (clbkCompileMesh != NULL) @@ -121,46 +144,76 @@ void Window::Reset(uint32_t _minViews) glfwPostEmptyEvent(); } -void Window::UpdateView(const ImageArr& images, const MVS::ImageArr& sceneImages) + +void Window::CenterCamera(const Point3& pos) { - glMatrixMode(GL_MODELVIEW); - if (camera->prevCamID != camera->currentCamID && camera->currentCamID != NO_ID) { - // enable camera view mode - // apply current camera transform - const Image& image = images[camera->currentCamID]; - const MVS::Image& imageData = sceneImages[image.idx]; - const MVS::Camera& camera = imageData.camera; - const Eigen::Matrix4d trans(TransW2L((const Matrix3x3::EMat)camera.R, camera.GetT())); - glLoadMatrixf((GLfloat*)gs_convert); - glMultMatrixd((GLdouble*)trans.data()); + camera.center = pos; + camera.dist *= 0.7; +} + + +void Window::UpdateView(const ImageArr& images, const MVS::ImageArr& sceneImagesMVS) +{ + if (camera.IsCameraViewMode()) { + // enable camera view mode and apply current camera transform + const Image& image = images[camera.currentCamID]; + const MVS::Camera& camera = sceneImagesMVS[image.idx].camera; + UpdateView((const Matrix3x3::EMat)camera.R, camera.GetT()); } else { // apply view point transform - const Eigen::Matrix4d trans(camera->GetLookAt()); + glMatrixMode(GL_MODELVIEW); + const Eigen::Matrix4d trans(camera.GetLookAt()); glLoadMatrixd((GLdouble*)trans.data()); } } -void Window::UpdateMousePosition() +void Window::UpdateView(const Eigen::Matrix3d& R, const Eigen::Vector3d& t) +{ + glMatrixMode(GL_MODELVIEW); + transform = gs_convert * TransW2L(R, t); + glLoadMatrixd((GLdouble*)transform.data()); +} + +void Window::UpdateMousePosition(double xpos, double ypos) { prevPos = pos; - // get current position - glfwGetCursorPos(window, &pos.x(), &pos.y()); + pos.x() = xpos; + pos.y() = ypos; // normalize position to [-1:1] range - const int w(camera->width); - const int h(camera->height); + const int w(camera.size.width); + const int h(camera.size.height); pos.x() = (2.0 * pos.x() - w) / w; pos.y() = (h - 2.0 * pos.y()) / h; } -void Window::Resize(int width, int height) + +void Window::GetFrame(Image8U3& image) const +{ + image.create(GetSize()); + glReadPixels(0, 0, image.width(), image.height(), GL_BGR_EXT, GL_UNSIGNED_BYTE, image.ptr()); + cv::flip(image, image, 0); +} + + +cv::Size Window::GetSize() const { + cv::Size _size; + glfwGetWindowSize(window, &_size.width, &_size.height); + return _size; +} +void Window::Resize(const cv::Size& _size) +{ + // detect scaled window + sizeScale = (double)GetSize().width/_size.width; + size = _size; + // update resolution glfwMakeContextCurrent(window); - glViewport(0, 0, (GLint)width, (GLint)height); - camera->Resize(width, height); + glViewport(0, 0, size.width, size.height); + camera.Resize(cv::Size(ROUND2INT(size.width*sizeScale), ROUND2INT(size.height*sizeScale))); } void Window::Resize(GLFWwindow* window, int width, int height) { - g_mapWindows[window]->Resize(width, height); + g_mapWindows[window]->Resize(cv::Size(width, height)); } void Window::Key(int k, int /*scancode*/, int action, int mod) @@ -196,31 +249,66 @@ void Window::Key(int k, int /*scancode*/, int action, int mod) break; case GLFW_KEY_LEFT: if (action != GLFW_RELEASE) { - camera->prevCamID = camera->currentCamID; - camera->currentCamID--; - if (camera->currentCamID < NO_ID && camera->currentCamID >= camera->maxCamID) - camera->currentCamID = camera->maxCamID-1; + camera.prevCamID = camera.currentCamID; + camera.currentCamID--; + if (camera.currentCamID < NO_ID && camera.currentCamID >= camera.maxCamID) + camera.currentCamID = camera.maxCamID-1; } break; case GLFW_KEY_RIGHT: if (action != GLFW_RELEASE) { - camera->prevCamID = camera->currentCamID; - camera->currentCamID++; - if (camera->currentCamID >= camera->maxCamID) - camera->currentCamID = NO_ID; + camera.prevCamID = camera.currentCamID; + camera.currentCamID++; + if (camera.currentCamID >= camera.maxCamID) + camera.currentCamID = NO_ID; + } + break; + case GLFW_KEY_B: + if (action == GLFW_RELEASE) + if (clbkCompileBounds != NULL) + clbkCompileBounds(); + break; + case GLFW_KEY_C: + if (action == GLFW_RELEASE) { + if (mod & GLFW_MOD_SHIFT) { + bRenderCameraTrajectory = !bRenderCameraTrajectory; + } else { + bRenderCameras = !bRenderCameras; + } } break; case GLFW_KEY_E: if (action == GLFW_RELEASE && clbkExportScene != NULL) clbkExportScene(NULL, NULL, false); break; + case GLFW_KEY_P: + switch (sparseType) { + case SPR_POINTS: sparseType = SPR_LINES; break; + case SPR_LINES: sparseType = SPR_ALL; break; + case SPR_ALL: sparseType = SPR_POINTS; break; + } + if (clbkCompilePointCloud != NULL) + clbkCompilePointCloud(); + break; case GLFW_KEY_R: if (action == GLFW_RELEASE) Reset(); break; - case GLFW_KEY_C: - if (action == GLFW_RELEASE) - bRenderCameras = !bRenderCameras; + case GLFW_KEY_T: + if (action == GLFW_RELEASE) { + bRenderTexture = !bRenderTexture; + if (clbkCompileMesh != NULL) + clbkCompileMesh(); + } + break; + case GLFW_KEY_V: + if (action == GLFW_RELEASE) { + if (mod & GLFW_MOD_SHIFT) { + bRenderImageVisibility = !bRenderImageVisibility; + } else { + bRenderViews = !bRenderViews; + } + } break; case GLFW_KEY_W: if (action == GLFW_RELEASE) { @@ -233,28 +321,12 @@ void Window::Key(int k, int /*scancode*/, int action, int mod) } } break; - case GLFW_KEY_T: - if (action == GLFW_RELEASE) { - bRenderTexture = !bRenderTexture; - if (clbkCompileMesh != NULL) - clbkCompileMesh(); - } - break; - case GLFW_KEY_P: - switch (sparseType) { - case SPR_POINTS: sparseType = SPR_LINES; break; - case SPR_LINES: sparseType = SPR_ALL; break; - case SPR_ALL: sparseType = SPR_POINTS; break; - } - if (clbkCompilePointCloud != NULL) - clbkCompilePointCloud(); - break; case GLFW_KEY_KP_SUBTRACT: if (action == GLFW_RELEASE) { if (mod & GLFW_MOD_CONTROL) - camera->SetFOV(MAXF(camera->fov-5, 5.0)); + camera.SetFOV(camera.fov-5.f); else if (mod & GLFW_MOD_SHIFT) - camera->scaleF *= 0.9f; + camera.scaleF *= 0.9f; else cameraBlend = MAXF(cameraBlend-0.1f, 0.f); } @@ -262,9 +334,9 @@ void Window::Key(int k, int /*scancode*/, int action, int mod) case GLFW_KEY_KP_ADD: if (action == GLFW_RELEASE) { if (mod & GLFW_MOD_CONTROL) - camera->SetFOV(camera->fov+5); + camera.SetFOV(camera.fov+5.f); else if (mod & GLFW_MOD_SHIFT) - camera->scaleF *= 1.11f; + camera.scaleF *= 1.1111f; else cameraBlend = MINF(cameraBlend+0.1f, 1.f); } @@ -278,24 +350,53 @@ void Window::Key(GLFWwindow* window, int k, int scancode, int action, int mod) void Window::MouseButton(int button, int action, int /*mods*/) { - if (clbkRayScene != NULL && button == GLFW_MOUSE_BUTTON_LEFT) { - typedef Eigen::Matrix Mat4; - Mat4 P, V; - glGetDoublev(GL_MODELVIEW_MATRIX, V.data()); - glGetDoublev(GL_PROJECTION_MATRIX, P.data()); - // 4d Homogeneous Clip Coordinates - const Eigen::Vector4d ray_clip(pos.x(), pos.y(), -1.0, 1.0); - // 4d Eye (Camera) Coordinates - Eigen::Vector4d ray_eye(P.inverse()*ray_clip); - ray_eye.z() = -1.0; - ray_eye.w() = 0.0; - // 4d World Coordinates - const Mat4 invV(V.inverse()); - ASSERT(ISEQUAL(invV(3,3),1.0)); - const Eigen::Vector3d start(invV.topRightCorner<3,1>()); - const Eigen::Vector4d ray_wor(invV*ray_eye); - const Eigen::Vector3d dir(ray_wor.topRows<3>().normalized()); - clbkRayScene(Ray3d(start, dir), action); + switch (button) { + case GLFW_MOUSE_BUTTON_LEFT: { + if (action == GLFW_PRESS) { + inputType.set(INP_MOUSE_LEFT); + } else + if (action == GLFW_RELEASE) { + inputType.unset(INP_MOUSE_LEFT); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + if (clbkRayScene != NULL) { + typedef Eigen::Matrix Mat4; + Mat4 P, V; + glGetDoublev(GL_MODELVIEW_MATRIX, V.data()); + glGetDoublev(GL_PROJECTION_MATRIX, P.data()); + // 4d Homogeneous Clip Coordinates + const Eigen::Vector4d ray_clip(pos.x(), pos.y(), -1.0, 1.0); + // 4d Eye (Camera) Coordinates + Eigen::Vector4d ray_eye(P.inverse()*ray_clip); + ray_eye.z() = -1.0; + ray_eye.w() = 0.0; + // 4d World Coordinates + const Mat4 invV(V.inverse()); + ASSERT(ISEQUAL(invV(3,3),1.0)); + const Eigen::Vector3d start(invV.topRightCorner<3,1>()); + const Eigen::Vector4d ray_wor(invV*ray_eye); + const Eigen::Vector3d dir(ray_wor.topRows<3>().normalized()); + clbkRayScene(Ray3d(start, dir), action); + } + } break; + case GLFW_MOUSE_BUTTON_MIDDLE: { + if (action == GLFW_PRESS) { + inputType.set(INP_MOUSE_MIDDLE); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + } else + if (action == GLFW_RELEASE) { + inputType.unset(INP_MOUSE_MIDDLE); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + } break; + case GLFW_MOUSE_BUTTON_RIGHT: { + if (action == GLFW_PRESS) { + inputType.set(INP_MOUSE_RIGHT); + } else + if (action == GLFW_RELEASE) { + inputType.unset(INP_MOUSE_RIGHT); + } + } } } void Window::MouseButton(GLFWwindow* window, int button, int action, int mods) @@ -303,9 +404,25 @@ void Window::MouseButton(GLFWwindow* window, int button, int action, int mods) g_mapWindows[window]->MouseButton(button, action, mods); } +void Window::MouseMove(double xpos, double ypos) +{ + UpdateMousePosition(xpos, ypos); + if (inputType.isSet(INP_MOUSE_LEFT)) { + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + camera.Rotate(pos, prevPos); + } else + if (inputType.isSet(INP_MOUSE_MIDDLE)) { + camera.Translate(pos, prevPos); + } +} +void Window::MouseMove(GLFWwindow* window, double xpos, double ypos) +{ + g_mapWindows[window]->MouseMove(xpos, ypos); +} + void Window::Scroll(double /*xoffset*/, double yoffset) { - camera->dist *= (yoffset>0 ? POW(1.11,yoffset) : POW(0.9,-yoffset)); + camera.dist *= (yoffset>0 ? POW(1.11,yoffset) : POW(0.9,-yoffset)); } void Window::Scroll(GLFWwindow* window, double xoffset, double yoffset) { diff --git a/apps/Viewer/Window.h b/apps/Viewer/Window.h index 0d9fe5034..364ad7e27 100644 --- a/apps/Viewer/Window.h +++ b/apps/Viewer/Window.h @@ -50,10 +50,22 @@ class Window { public: GLFWwindow* window; // window handle - CameraPtr camera; // current camera (always valid) + Camera camera; // current camera Eigen::Vector2d pos, prevPos; // current and previous mouse position (normalized) + Eigen::Matrix4d transform; // view matrix corresponding to the currently selected image + cv::Size size; // resolution in pixels, sometimes not equal to window resolution, ex. on Retina display + double sizeScale; // window/screen resolution scale + + enum INPUT : unsigned { + INP_NA = 0, + INP_MOUSE_LEFT = (1 << 0), + INP_MOUSE_MIDDLE = (1 << 1), + INP_MOUSE_RIGHT = (1 << 2), + }; + Flags inputType; enum SPARSE { + SPR_NONE = 0, SPR_POINTS = (1 << 0), SPR_LINES = (1 << 1), SPR_ALL = SPR_POINTS|SPR_LINES @@ -63,8 +75,12 @@ class Window float pointSize; float cameraBlend; bool bRenderCameras; + bool bRenderCameraTrajectory; + bool bRenderImageVisibility; + bool bRenderViews; bool bRenderSolid; bool bRenderTexture; + bool bRenderBounds; enum SELECTION { SEL_NA = 0, @@ -74,6 +90,7 @@ class Window SELECTION selectionType; Point3f selectionPoints[4]; double selectionTimeClick, selectionTime; + IDX selectionIdx; typedef DELEGATE ClbkOpenScene; ClbkOpenScene clbkOpenScene; @@ -85,6 +102,8 @@ class Window ClbkCompilePointCloud clbkCompilePointCloud; typedef DELEGATE ClbkCompileMesh; ClbkCompileMesh clbkCompileMesh; + typedef DELEGATE ClbkCompileBounds; + ClbkCompileBounds clbkCompileBounds; typedef std::unordered_map WindowsMap; static WindowsMap g_mapWindows; @@ -94,25 +113,35 @@ class Window ~Window(); void Release(); + void ReleaseClbk(); inline bool IsValid() const { return window != NULL; } - bool Init(int width, int height, LPCTSTR name); - void SetCamera(CameraPtr); + inline GLFWwindow* GetWindow() { return window; } + + bool Init(const cv::Size&, LPCTSTR name); + void SetCamera(const Camera&); void SetName(LPCTSTR); void SetVisible(bool); - void Reset(uint32_t minViews=2); + bool IsVisible() const; + void Reset(SPARSE sparseType=SPR_ALL, unsigned minViews=2); - inline GLFWwindow* GetWindow() { return window; } + void CenterCamera(const Point3&); void UpdateView(const ImageArr&, const MVS::ImageArr&); - void UpdateMousePosition(); + void UpdateView(const Eigen::Matrix3d& R, const Eigen::Vector3d& t); + void UpdateMousePosition(double xpos, double ypos); + + void GetFrame(Image8U3&) const; - void Resize(int width, int height); + cv::Size GetSize() const; + void Resize(const cv::Size&); static void Resize(GLFWwindow* window, int width, int height); void Key(int k, int scancode, int action, int mod); static void Key(GLFWwindow* window, int k, int scancode, int action, int mod); void MouseButton(int button, int action, int mods); static void MouseButton(GLFWwindow* window, int button, int action, int mods); + void MouseMove(double xpos, double ypos); + static void MouseMove(GLFWwindow* window, double xpos, double ypos); void Scroll(double xoffset, double yoffset); static void Scroll(GLFWwindow* window, double xoffset, double yoffset); void Drop(int count, const char** paths); diff --git a/build/Cotire.cmake b/build/Cotire.cmake deleted file mode 100644 index 97275d649..000000000 --- a/build/Cotire.cmake +++ /dev/null @@ -1,4190 +0,0 @@ -# - cotire (compile time reducer) -# -# See the cotire manual for usage hints. -# -#============================================================================= -# Copyright 2012-2018 Sascha Kratky -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation -# files (the "Software"), to deal in the Software without -# restriction, including without limitation the rights to use, -# copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following -# conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. -#============================================================================= - -if(__COTIRE_INCLUDED) - return() -endif() -set(__COTIRE_INCLUDED TRUE) - -# call cmake_minimum_required, but prevent modification of the CMake policy stack in include mode -# cmake_minimum_required also sets the policy version as a side effect, which we have to avoid -if (NOT CMAKE_SCRIPT_MODE_FILE) - cmake_policy(PUSH) -endif() -cmake_minimum_required(VERSION 2.8.12) -if (NOT CMAKE_SCRIPT_MODE_FILE) - cmake_policy(POP) -endif() - -set (COTIRE_CMAKE_MODULE_FILE "${CMAKE_CURRENT_LIST_FILE}") -set (COTIRE_CMAKE_MODULE_VERSION "1.8.0") - -# activate select policies -if (POLICY CMP0025) - # Compiler id for Apple Clang is now AppleClang - cmake_policy(SET CMP0025 NEW) -endif() - -if (POLICY CMP0026) - # disallow use of the LOCATION target property - cmake_policy(SET CMP0026 NEW) -endif() - -if (POLICY CMP0038) - # targets may not link directly to themselves - cmake_policy(SET CMP0038 NEW) -endif() - -if (POLICY CMP0039) - # utility targets may not have link dependencies - cmake_policy(SET CMP0039 NEW) -endif() - -if (POLICY CMP0040) - # target in the TARGET signature of add_custom_command() must exist - cmake_policy(SET CMP0040 NEW) -endif() - -if (POLICY CMP0045) - # error on non-existent target in get_target_property - cmake_policy(SET CMP0045 NEW) -endif() - -if (POLICY CMP0046) - # error on non-existent dependency in add_dependencies - cmake_policy(SET CMP0046 NEW) -endif() - -if (POLICY CMP0049) - # do not expand variables in target source entries - cmake_policy(SET CMP0049 NEW) -endif() - -if (POLICY CMP0050) - # disallow add_custom_command SOURCE signatures - cmake_policy(SET CMP0050 NEW) -endif() - -if (POLICY CMP0051) - # include TARGET_OBJECTS expressions in a target's SOURCES property - cmake_policy(SET CMP0051 NEW) -endif() - -if (POLICY CMP0053) - # simplify variable reference and escape sequence evaluation - cmake_policy(SET CMP0053 NEW) -endif() - -if (POLICY CMP0054) - # only interpret if() arguments as variables or keywords when unquoted - cmake_policy(SET CMP0054 NEW) -endif() - -if (POLICY CMP0055) - # strict checking for break() command - cmake_policy(SET CMP0055 NEW) -endif() - -include(CMakeParseArguments) -include(ProcessorCount) - -function (cotire_get_configuration_types _configsVar) - set (_configs "") - if (CMAKE_CONFIGURATION_TYPES) - list (APPEND _configs ${CMAKE_CONFIGURATION_TYPES}) - endif() - if (CMAKE_BUILD_TYPE) - list (APPEND _configs "${CMAKE_BUILD_TYPE}") - endif() - if (_configs) - list (REMOVE_DUPLICATES _configs) - set (${_configsVar} ${_configs} PARENT_SCOPE) - else() - set (${_configsVar} "None" PARENT_SCOPE) - endif() -endfunction() - -function (cotire_get_source_file_extension _sourceFile _extVar) - # get_filename_component returns extension from first occurrence of . in file name - # this function computes the extension from last occurrence of . in file name - string (FIND "${_sourceFile}" "." _index REVERSE) - if (_index GREATER -1) - math (EXPR _index "${_index} + 1") - string (SUBSTRING "${_sourceFile}" ${_index} -1 _sourceExt) - else() - set (_sourceExt "") - endif() - set (${_extVar} "${_sourceExt}" PARENT_SCOPE) -endfunction() - -macro (cotire_check_is_path_relative_to _path _isRelativeVar) - set (${_isRelativeVar} FALSE) - if (IS_ABSOLUTE "${_path}") - foreach (_dir ${ARGN}) - file (RELATIVE_PATH _relPath "${_dir}" "${_path}") - if (NOT _relPath OR (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\.")) - set (${_isRelativeVar} TRUE) - break() - endif() - endforeach() - endif() -endmacro() - -function (cotire_filter_language_source_files _language _target _sourceFilesVar _excludedSourceFilesVar _cotiredSourceFilesVar) - if (CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) - set (_languageExtensions "${CMAKE_${_language}_SOURCE_FILE_EXTENSIONS}") - else() - set (_languageExtensions "") - endif() - if (CMAKE_${_language}_IGNORE_EXTENSIONS) - set (_ignoreExtensions "${CMAKE_${_language}_IGNORE_EXTENSIONS}") - else() - set (_ignoreExtensions "") - endif() - if (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS) - set (_excludeExtensions "${COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS}") - else() - set (_excludeExtensions "") - endif() - if (COTIRE_DEBUG AND _languageExtensions) - message (STATUS "${_language} source file extensions: ${_languageExtensions}") - endif() - if (COTIRE_DEBUG AND _ignoreExtensions) - message (STATUS "${_language} ignore extensions: ${_ignoreExtensions}") - endif() - if (COTIRE_DEBUG AND _excludeExtensions) - message (STATUS "${_language} exclude extensions: ${_excludeExtensions}") - endif() - if (CMAKE_VERSION VERSION_LESS "3.1.0") - set (_allSourceFiles ${ARGN}) - else() - # as of CMake 3.1 target sources may contain generator expressions - # since we cannot obtain required property information about source files added - # through generator expressions at configure time, we filter them out - string (GENEX_STRIP "${ARGN}" _allSourceFiles) - endif() - set (_filteredSourceFiles "") - set (_excludedSourceFiles "") - foreach (_sourceFile ${_allSourceFiles}) - get_source_file_property(_sourceIsHeaderOnly "${_sourceFile}" HEADER_FILE_ONLY) - get_source_file_property(_sourceIsExternal "${_sourceFile}" EXTERNAL_OBJECT) - get_source_file_property(_sourceIsSymbolic "${_sourceFile}" SYMBOLIC) - if (NOT _sourceIsHeaderOnly AND NOT _sourceIsExternal AND NOT _sourceIsSymbolic) - cotire_get_source_file_extension("${_sourceFile}" _sourceExt) - if (_sourceExt) - list (FIND _ignoreExtensions "${_sourceExt}" _ignoreIndex) - if (_ignoreIndex LESS 0) - list (FIND _excludeExtensions "${_sourceExt}" _excludeIndex) - if (_excludeIndex GREATER -1) - list (APPEND _excludedSourceFiles "${_sourceFile}") - else() - list (FIND _languageExtensions "${_sourceExt}" _sourceIndex) - if (_sourceIndex GREATER -1) - # consider source file unless it is excluded explicitly - get_source_file_property(_sourceIsExcluded "${_sourceFile}" COTIRE_EXCLUDED) - if (_sourceIsExcluded) - list (APPEND _excludedSourceFiles "${_sourceFile}") - else() - list (APPEND _filteredSourceFiles "${_sourceFile}") - endif() - else() - get_source_file_property(_sourceLanguage "${_sourceFile}" LANGUAGE) - if ("${_sourceLanguage}" STREQUAL "${_language}") - # add to excluded sources, if file is not ignored and has correct language without having the correct extension - list (APPEND _excludedSourceFiles "${_sourceFile}") - endif() - endif() - endif() - endif() - endif() - endif() - endforeach() - # separate filtered source files from already cotired ones - # the COTIRE_TARGET property of a source file may be set while a target is being processed by cotire - set (_sourceFiles "") - set (_cotiredSourceFiles "") - foreach (_sourceFile ${_filteredSourceFiles}) - get_source_file_property(_sourceIsCotired "${_sourceFile}" COTIRE_TARGET) - if (_sourceIsCotired) - list (APPEND _cotiredSourceFiles "${_sourceFile}") - else() - get_source_file_property(_sourceCompileFlags "${_sourceFile}" COMPILE_FLAGS) - if (_sourceCompileFlags) - # add to excluded sources, if file has custom compile flags - list (APPEND _excludedSourceFiles "${_sourceFile}") - else() - get_source_file_property(_sourceCompileOptions "${_sourceFile}" COMPILE_OPTIONS) - if (_sourceCompileOptions) - # add to excluded sources, if file has list of custom compile options - list (APPEND _excludedSourceFiles "${_sourceFile}") - else() - list (APPEND _sourceFiles "${_sourceFile}") - endif() - endif() - endif() - endforeach() - if (COTIRE_DEBUG) - if (_sourceFiles) - message (STATUS "Filtered ${_target} ${_language} sources: ${_sourceFiles}") - endif() - if (_excludedSourceFiles) - message (STATUS "Excluded ${_target} ${_language} sources: ${_excludedSourceFiles}") - endif() - if (_cotiredSourceFiles) - message (STATUS "Cotired ${_target} ${_language} sources: ${_cotiredSourceFiles}") - endif() - endif() - set (${_sourceFilesVar} ${_sourceFiles} PARENT_SCOPE) - set (${_excludedSourceFilesVar} ${_excludedSourceFiles} PARENT_SCOPE) - set (${_cotiredSourceFilesVar} ${_cotiredSourceFiles} PARENT_SCOPE) -endfunction() - -function (cotire_get_objects_with_property_on _filteredObjectsVar _property _type) - set (_filteredObjects "") - foreach (_object ${ARGN}) - get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET) - if (_isSet) - get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) - if (_propertyValue) - list (APPEND _filteredObjects "${_object}") - endif() - endif() - endforeach() - set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE) -endfunction() - -function (cotire_get_objects_with_property_off _filteredObjectsVar _property _type) - set (_filteredObjects "") - foreach (_object ${ARGN}) - get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET) - if (_isSet) - get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) - if (NOT _propertyValue) - list (APPEND _filteredObjects "${_object}") - endif() - endif() - endforeach() - set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_file_property_values _valuesVar _property) - set (_values "") - foreach (_sourceFile ${ARGN}) - get_source_file_property(_propertyValue "${_sourceFile}" ${_property}) - if (_propertyValue) - list (APPEND _values "${_propertyValue}") - endif() - endforeach() - set (${_valuesVar} ${_values} PARENT_SCOPE) -endfunction() - -function (cotire_resolve_config_properties _configurations _propertiesVar) - set (_properties "") - foreach (_property ${ARGN}) - if ("${_property}" MATCHES "") - foreach (_config ${_configurations}) - string (TOUPPER "${_config}" _upperConfig) - string (REPLACE "" "${_upperConfig}" _configProperty "${_property}") - list (APPEND _properties ${_configProperty}) - endforeach() - else() - list (APPEND _properties ${_property}) - endif() - endforeach() - set (${_propertiesVar} ${_properties} PARENT_SCOPE) -endfunction() - -function (cotire_copy_set_properties _configurations _type _source _target) - cotire_resolve_config_properties("${_configurations}" _properties ${ARGN}) - foreach (_property ${_properties}) - get_property(_isSet ${_type} ${_source} PROPERTY ${_property} SET) - if (_isSet) - get_property(_propertyValue ${_type} ${_source} PROPERTY ${_property}) - set_property(${_type} ${_target} PROPERTY ${_property} "${_propertyValue}") - endif() - endforeach() -endfunction() - -function (cotire_get_target_usage_requirements _target _config _targetRequirementsVar) - set (_targetRequirements "") - get_target_property(_librariesToProcess ${_target} LINK_LIBRARIES) - while (_librariesToProcess) - # remove from head - list (GET _librariesToProcess 0 _library) - list (REMOVE_AT _librariesToProcess 0) - if (_library MATCHES "^\\$<\\$:([A-Za-z0-9_:-]+)>$") - set (_library "${CMAKE_MATCH_1}") - elseif (_config STREQUAL "None" AND _library MATCHES "^\\$<\\$:([A-Za-z0-9_:-]+)>$") - set (_library "${CMAKE_MATCH_1}") - endif() - if (TARGET ${_library}) - list (FIND _targetRequirements ${_library} _index) - if (_index LESS 0) - list (APPEND _targetRequirements ${_library}) - # BFS traversal of transitive libraries - get_target_property(_libraries ${_library} INTERFACE_LINK_LIBRARIES) - if (_libraries) - list (APPEND _librariesToProcess ${_libraries}) - list (REMOVE_DUPLICATES _librariesToProcess) - endif() - endif() - endif() - endwhile() - set (${_targetRequirementsVar} ${_targetRequirements} PARENT_SCOPE) -endfunction() - -function (cotire_filter_compile_flags _language _flagFilter _matchedOptionsVar _unmatchedOptionsVar) - if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - set (_flagPrefix "[/-]") - else() - set (_flagPrefix "--?") - endif() - set (_optionFlag "") - set (_matchedOptions "") - set (_unmatchedOptions "") - foreach (_compileFlag ${ARGN}) - if (_compileFlag) - if (_optionFlag AND NOT "${_compileFlag}" MATCHES "^${_flagPrefix}") - # option with separate argument - list (APPEND _matchedOptions "${_compileFlag}") - set (_optionFlag "") - elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})$") - # remember option - set (_optionFlag "${CMAKE_MATCH_2}") - elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})(.+)$") - # option with joined argument - list (APPEND _matchedOptions "${CMAKE_MATCH_3}") - set (_optionFlag "") - else() - # flush remembered option - if (_optionFlag) - list (APPEND _matchedOptions "${_optionFlag}") - set (_optionFlag "") - endif() - # add to unfiltered options - list (APPEND _unmatchedOptions "${_compileFlag}") - endif() - endif() - endforeach() - if (_optionFlag) - list (APPEND _matchedOptions "${_optionFlag}") - endif() - if (COTIRE_DEBUG AND _matchedOptions) - message (STATUS "Filter ${_flagFilter} matched: ${_matchedOptions}") - endif() - if (COTIRE_DEBUG AND _unmatchedOptions) - message (STATUS "Filter ${_flagFilter} unmatched: ${_unmatchedOptions}") - endif() - set (${_matchedOptionsVar} ${_matchedOptions} PARENT_SCOPE) - set (${_unmatchedOptionsVar} ${_unmatchedOptions} PARENT_SCOPE) -endfunction() - -function (cotire_is_target_supported _target _isSupportedVar) - if (NOT TARGET "${_target}") - set (${_isSupportedVar} FALSE PARENT_SCOPE) - return() - endif() - get_target_property(_imported ${_target} IMPORTED) - if (_imported) - set (${_isSupportedVar} FALSE PARENT_SCOPE) - return() - endif() - get_target_property(_targetType ${_target} TYPE) - if (NOT _targetType MATCHES "EXECUTABLE|(STATIC|SHARED|MODULE|OBJECT)_LIBRARY") - set (${_isSupportedVar} FALSE PARENT_SCOPE) - return() - endif() - set (${_isSupportedVar} TRUE PARENT_SCOPE) -endfunction() - -function (cotire_get_target_compile_flags _config _language _target _flagsVar) - string (TOUPPER "${_config}" _upperConfig) - # collect options from CMake language variables - set (_compileFlags "") - if (CMAKE_${_language}_FLAGS) - set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS}") - endif() - if (CMAKE_${_language}_FLAGS_${_upperConfig}) - set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS_${_upperConfig}}") - endif() - if (_target) - # add target compile flags - get_target_property(_targetflags ${_target} COMPILE_FLAGS) - if (_targetflags) - set (_compileFlags "${_compileFlags} ${_targetflags}") - endif() - endif() - if (UNIX) - separate_arguments(_compileFlags UNIX_COMMAND "${_compileFlags}") - elseif(WIN32) - separate_arguments(_compileFlags WINDOWS_COMMAND "${_compileFlags}") - else() - separate_arguments(_compileFlags) - endif() - # target compile options - if (_target) - get_target_property(_targetOptions ${_target} COMPILE_OPTIONS) - if (_targetOptions) - list (APPEND _compileFlags ${_targetOptions}) - endif() - endif() - # interface compile options from linked library targets - if (_target) - set (_linkedTargets "") - cotire_get_target_usage_requirements(${_target} ${_config} _linkedTargets) - foreach (_linkedTarget ${_linkedTargets}) - get_target_property(_targetOptions ${_linkedTarget} INTERFACE_COMPILE_OPTIONS) - if (_targetOptions) - list (APPEND _compileFlags ${_targetOptions}) - endif() - endforeach() - endif() - # handle language standard properties - if (CMAKE_${_language}_STANDARD_DEFAULT) - # used compiler supports language standard levels - if (_target) - get_target_property(_targetLanguageStandard ${_target} ${_language}_STANDARD) - if (_targetLanguageStandard) - set (_type "EXTENSION") - get_property(_isSet TARGET ${_target} PROPERTY ${_language}_EXTENSIONS SET) - if (_isSet) - get_target_property(_targetUseLanguageExtensions ${_target} ${_language}_EXTENSIONS) - if (NOT _targetUseLanguageExtensions) - set (_type "STANDARD") - endif() - endif() - if (CMAKE_${_language}${_targetLanguageStandard}_${_type}_COMPILE_OPTION) - list (APPEND _compileFlags "${CMAKE_${_language}${_targetLanguageStandard}_${_type}_COMPILE_OPTION}") - endif() - endif() - endif() - endif() - # handle the POSITION_INDEPENDENT_CODE target property - if (_target) - get_target_property(_targetPIC ${_target} POSITION_INDEPENDENT_CODE) - if (_targetPIC) - get_target_property(_targetType ${_target} TYPE) - if (_targetType STREQUAL "EXECUTABLE" AND CMAKE_${_language}_COMPILE_OPTIONS_PIE) - list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIE}") - elseif (CMAKE_${_language}_COMPILE_OPTIONS_PIC) - list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIC}") - endif() - endif() - endif() - # handle visibility target properties - if (_target) - get_target_property(_targetVisibility ${_target} ${_language}_VISIBILITY_PRESET) - if (_targetVisibility AND CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY) - list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY}${_targetVisibility}") - endif() - get_target_property(_targetVisibilityInlines ${_target} VISIBILITY_INLINES_HIDDEN) - if (_targetVisibilityInlines AND CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN) - list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN}") - endif() - endif() - # platform specific flags - if (APPLE) - get_target_property(_architectures ${_target} OSX_ARCHITECTURES_${_upperConfig}) - if (NOT _architectures) - get_target_property(_architectures ${_target} OSX_ARCHITECTURES) - endif() - if (_architectures) - foreach (_arch ${_architectures}) - list (APPEND _compileFlags "-arch" "${_arch}") - endforeach() - endif() - if (CMAKE_OSX_SYSROOT) - if (CMAKE_${_language}_SYSROOT_FLAG) - list (APPEND _compileFlags "${CMAKE_${_language}_SYSROOT_FLAG}" "${CMAKE_OSX_SYSROOT}") - else() - list (APPEND _compileFlags "-isysroot" "${CMAKE_OSX_SYSROOT}") - endif() - endif() - if (CMAKE_OSX_DEPLOYMENT_TARGET) - if (CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG) - list (APPEND _compileFlags "${CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG}${CMAKE_OSX_DEPLOYMENT_TARGET}") - else() - list (APPEND _compileFlags "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") - endif() - endif() - endif() - if (COTIRE_DEBUG AND _compileFlags) - message (STATUS "Target ${_target} compile flags: ${_compileFlags}") - endif() - set (${_flagsVar} ${_compileFlags} PARENT_SCOPE) -endfunction() - -function (cotire_get_target_include_directories _config _language _target _includeDirsVar _systemIncludeDirsVar) - set (_includeDirs "") - set (_systemIncludeDirs "") - # default include dirs - if (CMAKE_INCLUDE_CURRENT_DIR) - list (APPEND _includeDirs "${CMAKE_CURRENT_BINARY_DIR}") - list (APPEND _includeDirs "${CMAKE_CURRENT_SOURCE_DIR}") - endif() - set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) - # parse additional include directories from target compile flags - if (CMAKE_INCLUDE_FLAG_${_language}) - string (STRIP "${CMAKE_INCLUDE_FLAG_${_language}}" _includeFlag) - string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") - if (_includeFlag) - set (_dirs "") - cotire_filter_compile_flags("${_language}" "${_includeFlag}" _dirs _ignore ${_targetFlags}) - if (_dirs) - list (APPEND _includeDirs ${_dirs}) - endif() - endif() - endif() - # parse additional system include directories from target compile flags - if (CMAKE_INCLUDE_SYSTEM_FLAG_${_language}) - string (STRIP "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" _includeFlag) - string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") - if (_includeFlag) - set (_dirs "") - cotire_filter_compile_flags("${_language}" "${_includeFlag}" _dirs _ignore ${_targetFlags}) - if (_dirs) - list (APPEND _systemIncludeDirs ${_dirs}) - endif() - endif() - endif() - # target include directories - get_directory_property(_dirs DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" INCLUDE_DIRECTORIES) - if (_target) - get_target_property(_targetDirs ${_target} INCLUDE_DIRECTORIES) - if (_targetDirs) - list (APPEND _dirs ${_targetDirs}) - endif() - get_target_property(_targetDirs ${_target} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) - if (_targetDirs) - list (APPEND _systemIncludeDirs ${_targetDirs}) - endif() - endif() - # interface include directories from linked library targets - if (_target) - set (_linkedTargets "") - cotire_get_target_usage_requirements(${_target} ${_config} _linkedTargets) - foreach (_linkedTarget ${_linkedTargets}) - get_target_property(_linkedTargetType ${_linkedTarget} TYPE) - if (CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE AND NOT CMAKE_VERSION VERSION_LESS "3.4.0" AND - _linkedTargetType MATCHES "(STATIC|SHARED|MODULE|OBJECT)_LIBRARY") - # CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE refers to CMAKE_CURRENT_BINARY_DIR and CMAKE_CURRENT_SOURCE_DIR - # at the time, when the target was created. These correspond to the target properties BINARY_DIR and SOURCE_DIR - # which are only available with CMake 3.4 or later. - get_target_property(_targetDirs ${_linkedTarget} BINARY_DIR) - if (_targetDirs) - list (APPEND _dirs ${_targetDirs}) - endif() - get_target_property(_targetDirs ${_linkedTarget} SOURCE_DIR) - if (_targetDirs) - list (APPEND _dirs ${_targetDirs}) - endif() - endif() - get_target_property(_targetDirs ${_linkedTarget} INTERFACE_INCLUDE_DIRECTORIES) - if (_targetDirs) - list (APPEND _dirs ${_targetDirs}) - endif() - get_target_property(_targetDirs ${_linkedTarget} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) - if (_targetDirs) - list (APPEND _systemIncludeDirs ${_targetDirs}) - endif() - endforeach() - endif() - if (dirs) - list (REMOVE_DUPLICATES _dirs) - endif() - list (LENGTH _includeDirs _projectInsertIndex) - foreach (_dir ${_dirs}) - if (CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE) - cotire_check_is_path_relative_to("${_dir}" _isRelative "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") - if (_isRelative) - list (LENGTH _includeDirs _len) - if (_len EQUAL _projectInsertIndex) - list (APPEND _includeDirs "${_dir}") - else() - list (INSERT _includeDirs _projectInsertIndex "${_dir}") - endif() - math (EXPR _projectInsertIndex "${_projectInsertIndex} + 1") - else() - list (APPEND _includeDirs "${_dir}") - endif() - else() - list (APPEND _includeDirs "${_dir}") - endif() - endforeach() - list (REMOVE_DUPLICATES _includeDirs) - list (REMOVE_DUPLICATES _systemIncludeDirs) - if (CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES) - list (REMOVE_ITEM _includeDirs ${CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() - if (WIN32 AND NOT MINGW) - # convert Windows paths in include directories to CMake paths - if (_includeDirs) - set (_paths "") - foreach (_dir ${_includeDirs}) - file (TO_CMAKE_PATH "${_dir}" _path) - list (APPEND _paths "${_path}") - endforeach() - set (_includeDirs ${_paths}) - endif() - if (_systemIncludeDirs) - set (_paths "") - foreach (_dir ${_systemIncludeDirs}) - file (TO_CMAKE_PATH "${_dir}" _path) - list (APPEND _paths "${_path}") - endforeach() - set (_systemIncludeDirs ${_paths}) - endif() - endif() - if (COTIRE_DEBUG AND _includeDirs) - message (STATUS "Target ${_target} include dirs: ${_includeDirs}") - endif() - set (${_includeDirsVar} ${_includeDirs} PARENT_SCOPE) - if (COTIRE_DEBUG AND _systemIncludeDirs) - message (STATUS "Target ${_target} system include dirs: ${_systemIncludeDirs}") - endif() - set (${_systemIncludeDirsVar} ${_systemIncludeDirs} PARENT_SCOPE) -endfunction() - -function (cotire_get_target_export_symbol _target _exportSymbolVar) - set (_exportSymbol "") - get_target_property(_targetType ${_target} TYPE) - get_target_property(_enableExports ${_target} ENABLE_EXPORTS) - if (_targetType MATCHES "(SHARED|MODULE)_LIBRARY" OR - (_targetType STREQUAL "EXECUTABLE" AND _enableExports)) - get_target_property(_exportSymbol ${_target} DEFINE_SYMBOL) - if (NOT _exportSymbol) - set (_exportSymbol "${_target}_EXPORTS") - endif() - string (MAKE_C_IDENTIFIER "${_exportSymbol}" _exportSymbol) - endif() - set (${_exportSymbolVar} ${_exportSymbol} PARENT_SCOPE) -endfunction() - -function (cotire_get_target_compile_definitions _config _language _target _definitionsVar) - string (TOUPPER "${_config}" _upperConfig) - set (_configDefinitions "") - # CMAKE_INTDIR for multi-configuration build systems - if (NOT "${CMAKE_CFG_INTDIR}" STREQUAL ".") - list (APPEND _configDefinitions "CMAKE_INTDIR=\"${_config}\"") - endif() - # target export define symbol - cotire_get_target_export_symbol("${_target}" _defineSymbol) - if (_defineSymbol) - list (APPEND _configDefinitions "${_defineSymbol}") - endif() - # directory compile definitions - get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS_${_upperConfig}) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - # target compile definitions - get_target_property(_definitions ${_target} COMPILE_DEFINITIONS) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - get_target_property(_definitions ${_target} COMPILE_DEFINITIONS_${_upperConfig}) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - # interface compile definitions from linked library targets - set (_linkedTargets "") - cotire_get_target_usage_requirements(${_target} ${_config} _linkedTargets) - foreach (_linkedTarget ${_linkedTargets}) - get_target_property(_definitions ${_linkedTarget} INTERFACE_COMPILE_DEFINITIONS) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - endforeach() - # parse additional compile definitions from target compile flags - # and do not look at directory compile definitions, which we already handled - set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) - cotire_filter_compile_flags("${_language}" "D" _definitions _ignore ${_targetFlags}) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - list (REMOVE_DUPLICATES _configDefinitions) - if (COTIRE_DEBUG AND _configDefinitions) - message (STATUS "Target ${_target} compile definitions: ${_configDefinitions}") - endif() - set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE) -endfunction() - -function (cotire_get_target_compiler_flags _config _language _target _compilerFlagsVar) - # parse target compile flags omitting compile definitions and include directives - set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) - set (_flagFilter "D") - if (CMAKE_INCLUDE_FLAG_${_language}) - string (STRIP "${CMAKE_INCLUDE_FLAG_${_language}}" _includeFlag) - string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") - if (_includeFlag) - set (_flagFilter "${_flagFilter}|${_includeFlag}") - endif() - endif() - if (CMAKE_INCLUDE_SYSTEM_FLAG_${_language}) - string (STRIP "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" _includeFlag) - string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") - if (_includeFlag) - set (_flagFilter "${_flagFilter}|${_includeFlag}") - endif() - endif() - set (_compilerFlags "") - cotire_filter_compile_flags("${_language}" "${_flagFilter}" _ignore _compilerFlags ${_targetFlags}) - if (COTIRE_DEBUG AND _compilerFlags) - message (STATUS "Target ${_target} compiler flags: ${_compilerFlags}") - endif() - set (${_compilerFlagsVar} ${_compilerFlags} PARENT_SCOPE) -endfunction() - -function (cotire_add_sys_root_paths _pathsVar) - if (APPLE) - if (CMAKE_OSX_SYSROOT AND CMAKE_${_language}_HAS_ISYSROOT) - foreach (_path IN LISTS ${_pathsVar}) - if (IS_ABSOLUTE "${_path}") - get_filename_component(_path "${CMAKE_OSX_SYSROOT}/${_path}" ABSOLUTE) - if (EXISTS "${_path}") - list (APPEND ${_pathsVar} "${_path}") - endif() - endif() - endforeach() - endif() - endif() - set (${_pathsVar} ${${_pathsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_extra_properties _sourceFile _pattern _resultVar) - set (_extraProperties ${ARGN}) - set (_result "") - if (_extraProperties) - list (FIND _extraProperties "${_sourceFile}" _index) - if (_index GREATER -1) - math (EXPR _index "${_index} + 1") - list (LENGTH _extraProperties _len) - math (EXPR _len "${_len} - 1") - foreach (_index RANGE ${_index} ${_len}) - list (GET _extraProperties ${_index} _value) - if (_value MATCHES "${_pattern}") - list (APPEND _result "${_value}") - else() - break() - endif() - endforeach() - endif() - endif() - set (${_resultVar} ${_result} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_compile_definitions _config _language _sourceFile _definitionsVar) - set (_compileDefinitions "") - if (NOT CMAKE_SCRIPT_MODE_FILE) - string (TOUPPER "${_config}" _upperConfig) - get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS) - if (_definitions) - list (APPEND _compileDefinitions ${_definitions}) - endif() - get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS_${_upperConfig}) - if (_definitions) - list (APPEND _compileDefinitions ${_definitions}) - endif() - endif() - cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+(=.*)?$" _definitions ${ARGN}) - if (_definitions) - list (APPEND _compileDefinitions ${_definitions}) - endif() - if (COTIRE_DEBUG AND _compileDefinitions) - message (STATUS "Source ${_sourceFile} compile definitions: ${_compileDefinitions}") - endif() - set (${_definitionsVar} ${_compileDefinitions} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_files_compile_definitions _config _language _definitionsVar) - set (_configDefinitions "") - foreach (_sourceFile ${ARGN}) - cotire_get_source_compile_definitions("${_config}" "${_language}" "${_sourceFile}" _sourceDefinitions) - if (_sourceDefinitions) - list (APPEND _configDefinitions "${_sourceFile}" ${_sourceDefinitions} "-") - endif() - endforeach() - set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_undefs _sourceFile _property _sourceUndefsVar) - set (_sourceUndefs "") - if (NOT CMAKE_SCRIPT_MODE_FILE) - get_source_file_property(_undefs "${_sourceFile}" ${_property}) - if (_undefs) - list (APPEND _sourceUndefs ${_undefs}) - endif() - endif() - cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+$" _undefs ${ARGN}) - if (_undefs) - list (APPEND _sourceUndefs ${_undefs}) - endif() - if (COTIRE_DEBUG AND _sourceUndefs) - message (STATUS "Source ${_sourceFile} ${_property} undefs: ${_sourceUndefs}") - endif() - set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_files_undefs _property _sourceUndefsVar) - set (_sourceUndefs "") - foreach (_sourceFile ${ARGN}) - cotire_get_source_undefs("${_sourceFile}" ${_property} _undefs) - if (_undefs) - list (APPEND _sourceUndefs "${_sourceFile}" ${_undefs} "-") - endif() - endforeach() - set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE) -endfunction() - -macro (cotire_set_cmd_to_prologue _cmdVar) - set (${_cmdVar} "${CMAKE_COMMAND}") - if (COTIRE_DEBUG) - list (APPEND ${_cmdVar} "--warn-uninitialized") - endif() - list (APPEND ${_cmdVar} "-DCOTIRE_BUILD_TYPE:STRING=$") - if (XCODE) - list (APPEND ${_cmdVar} "-DXCODE:BOOL=TRUE") - endif() - if (COTIRE_VERBOSE) - list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=ON") - elseif("${CMAKE_GENERATOR}" MATCHES "Makefiles") - list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=$(VERBOSE)") - endif() -endmacro() - -function (cotire_init_compile_cmd _cmdVar _language _compilerLauncher _compilerExe _compilerArg1) - if (NOT _compilerLauncher) - set (_compilerLauncher ${CMAKE_${_language}_COMPILER_LAUNCHER}) - endif() - if (NOT _compilerExe) - set (_compilerExe "${CMAKE_${_language}_COMPILER}") - endif() - if (NOT _compilerArg1) - set (_compilerArg1 ${CMAKE_${_language}_COMPILER_ARG1}) - endif() - if (WIN32) - file (TO_NATIVE_PATH "${_compilerExe}" _compilerExe) - endif() - string (STRIP "${_compilerArg1}" _compilerArg1) - if ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - # compiler launcher is only supported for Makefile and Ninja - set (${_cmdVar} ${_compilerLauncher} "${_compilerExe}" ${_compilerArg1} PARENT_SCOPE) - else() - set (${_cmdVar} "${_compilerExe}" ${_compilerArg1} PARENT_SCOPE) - endif() -endfunction() - -macro (cotire_add_definitions_to_cmd _cmdVar _language) - foreach (_definition ${ARGN}) - if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - list (APPEND ${_cmdVar} "/D${_definition}") - else() - list (APPEND ${_cmdVar} "-D${_definition}") - endif() - endforeach() -endmacro() - -function (cotire_add_includes_to_cmd _cmdVar _language _includesVar _systemIncludesVar) - set (_includeDirs ${${_includesVar}} ${${_systemIncludesVar}}) - if (_includeDirs) - list (REMOVE_DUPLICATES _includeDirs) - foreach (_include ${_includeDirs}) - if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - file (TO_NATIVE_PATH "${_include}" _include) - list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_SEP_${_language}}${_include}") - else() - set (_index -1) - if ("${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" MATCHES ".+") - list (FIND ${_systemIncludesVar} "${_include}" _index) - endif() - if (_index GREATER -1) - list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_SEP_${_language}}${_include}") - else() - list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_SEP_${_language}}${_include}") - endif() - endif() - endforeach() - endif() - set (${_cmdVar} ${${_cmdVar}} PARENT_SCOPE) -endfunction() - -function (cotire_add_frameworks_to_cmd _cmdVar _language _includesVar _systemIncludesVar) - if (APPLE) - set (_frameworkDirs "") - foreach (_include ${${_includesVar}}) - if (IS_ABSOLUTE "${_include}" AND _include MATCHES "\\.framework$") - get_filename_component(_frameworkDir "${_include}" DIRECTORY) - list (APPEND _frameworkDirs "${_frameworkDir}") - endif() - endforeach() - set (_systemFrameworkDirs "") - foreach (_include ${${_systemIncludesVar}}) - if (IS_ABSOLUTE "${_include}" AND _include MATCHES "\\.framework$") - get_filename_component(_frameworkDir "${_include}" DIRECTORY) - list (APPEND _systemFrameworkDirs "${_frameworkDir}") - endif() - endforeach() - if (_systemFrameworkDirs) - list (APPEND _frameworkDirs ${_systemFrameworkDirs}) - endif() - if (_frameworkDirs) - list (REMOVE_DUPLICATES _frameworkDirs) - foreach (_frameworkDir ${_frameworkDirs}) - set (_index -1) - if ("${CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG}" MATCHES ".+") - list (FIND _systemFrameworkDirs "${_frameworkDir}" _index) - endif() - if (_index GREATER -1) - list (APPEND ${_cmdVar} "${CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG}${_frameworkDir}") - else() - list (APPEND ${_cmdVar} "${CMAKE_${_language}_FRAMEWORK_SEARCH_FLAG}${_frameworkDir}") - endif() - endforeach() - endif() - endif() - set (${_cmdVar} ${${_cmdVar}} PARENT_SCOPE) -endfunction() - -macro (cotire_add_compile_flags_to_cmd _cmdVar) - foreach (_flag ${ARGN}) - list (APPEND ${_cmdVar} "${_flag}") - endforeach() -endmacro() - -function (cotire_check_file_up_to_date _fileIsUpToDateVar _file) - if (EXISTS "${_file}") - set (_triggerFile "") - foreach (_dependencyFile ${ARGN}) - if (EXISTS "${_dependencyFile}") - # IS_NEWER_THAN returns TRUE if both files have the same timestamp - # thus we do the comparison in both directions to exclude ties - if ("${_dependencyFile}" IS_NEWER_THAN "${_file}" AND - NOT "${_file}" IS_NEWER_THAN "${_dependencyFile}") - set (_triggerFile "${_dependencyFile}") - break() - endif() - endif() - endforeach() - if (_triggerFile) - if (COTIRE_VERBOSE) - get_filename_component(_fileName "${_file}" NAME) - message (STATUS "${_fileName} update triggered by ${_triggerFile} change.") - endif() - set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) - else() - if (COTIRE_VERBOSE) - get_filename_component(_fileName "${_file}" NAME) - message (STATUS "${_fileName} is up-to-date.") - endif() - set (${_fileIsUpToDateVar} TRUE PARENT_SCOPE) - endif() - else() - if (COTIRE_VERBOSE) - get_filename_component(_fileName "${_file}" NAME) - message (STATUS "${_fileName} does not exist yet.") - endif() - set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) - endif() -endfunction() - -macro (cotire_find_closest_relative_path _headerFile _includeDirs _relPathVar) - set (${_relPathVar} "") - foreach (_includeDir ${_includeDirs}) - if (IS_DIRECTORY "${_includeDir}") - file (RELATIVE_PATH _relPath "${_includeDir}" "${_headerFile}") - if (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\.") - string (LENGTH "${${_relPathVar}}" _closestLen) - string (LENGTH "${_relPath}" _relLen) - if (_closestLen EQUAL 0 OR _relLen LESS _closestLen) - set (${_relPathVar} "${_relPath}") - endif() - endif() - elseif ("${_includeDir}" STREQUAL "${_headerFile}") - # if path matches exactly, return short non-empty string - set (${_relPathVar} "1") - break() - endif() - endforeach() -endmacro() - -macro (cotire_check_header_file_location _headerFile _insideIncludeDirs _outsideIncludeDirs _headerIsInside) - # check header path against ignored and honored include directories - cotire_find_closest_relative_path("${_headerFile}" "${_insideIncludeDirs}" _insideRelPath) - if (_insideRelPath) - # header is inside, but could be become outside if there is a shorter outside match - cotire_find_closest_relative_path("${_headerFile}" "${_outsideIncludeDirs}" _outsideRelPath) - if (_outsideRelPath) - string (LENGTH "${_insideRelPath}" _insideRelPathLen) - string (LENGTH "${_outsideRelPath}" _outsideRelPathLen) - if (_outsideRelPathLen LESS _insideRelPathLen) - set (${_headerIsInside} FALSE) - else() - set (${_headerIsInside} TRUE) - endif() - else() - set (${_headerIsInside} TRUE) - endif() - else() - # header is outside - set (${_headerIsInside} FALSE) - endif() -endmacro() - -macro (cotire_check_ignore_header_file_path _headerFile _headerIsIgnoredVar) - if (NOT EXISTS "${_headerFile}") - set (${_headerIsIgnoredVar} TRUE) - elseif (IS_DIRECTORY "${_headerFile}") - set (${_headerIsIgnoredVar} TRUE) - elseif ("${_headerFile}" MATCHES "\\.\\.|[_-]fixed" AND "${_headerFile}" MATCHES "\\.h$") - # heuristic: ignore C headers with embedded parent directory references or "-fixed" or "_fixed" in path - # these often stem from using GCC #include_next tricks, which may break the precompiled header compilation - # with the error message "error: no include path in which to search for header.h" - set (${_headerIsIgnoredVar} TRUE) - else() - set (${_headerIsIgnoredVar} FALSE) - endif() -endmacro() - -macro (cotire_check_ignore_header_file_ext _headerFile _ignoreExtensionsVar _headerIsIgnoredVar) - # check header file extension - cotire_get_source_file_extension("${_headerFile}" _headerFileExt) - set (${_headerIsIgnoredVar} FALSE) - if (_headerFileExt) - list (FIND ${_ignoreExtensionsVar} "${_headerFileExt}" _index) - if (_index GREATER -1) - set (${_headerIsIgnoredVar} TRUE) - endif() - endif() -endmacro() - -macro (cotire_parse_line _line _headerFileVar _headerDepthVar) - if (MSVC) - # cl.exe /showIncludes produces different output, depending on the language pack used, e.g.: - # English: "Note: including file: C:\directory\file" - # German: "Hinweis: Einlesen der Datei: C:\directory\file" - # We use a very general regular expression, relying on the presence of the : characters - if (_line MATCHES "( +)([a-zA-Z]:[^:]+)$") - string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar}) - get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" ABSOLUTE) - else() - set (${_headerFileVar} "") - set (${_headerDepthVar} 0) - endif() - else() - if (_line MATCHES "^(\\.+) (.*)$") - # GCC like output - string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar}) - if (IS_ABSOLUTE "${CMAKE_MATCH_2}") - set (${_headerFileVar} "${CMAKE_MATCH_2}") - else() - get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" REALPATH) - endif() - else() - set (${_headerFileVar} "") - set (${_headerDepthVar} 0) - endif() - endif() -endmacro() - -function (cotire_parse_includes _language _scanOutput _ignoredIncludeDirs _honoredIncludeDirs _ignoredExtensions _selectedIncludesVar _unparsedLinesVar) - if (WIN32) - # prevent CMake macro invocation errors due to backslash characters in Windows paths - string (REPLACE "\\" "/" _scanOutput "${_scanOutput}") - endif() - # canonize slashes - string (REPLACE "//" "/" _scanOutput "${_scanOutput}") - # prevent semicolon from being interpreted as a line separator - string (REPLACE ";" "\\;" _scanOutput "${_scanOutput}") - # then separate lines - string (REGEX REPLACE "\n" ";" _scanOutput "${_scanOutput}") - list (LENGTH _scanOutput _len) - # remove duplicate lines to speed up parsing - list (REMOVE_DUPLICATES _scanOutput) - list (LENGTH _scanOutput _uniqueLen) - if (COTIRE_VERBOSE OR COTIRE_DEBUG) - message (STATUS "Scanning ${_uniqueLen} unique lines of ${_len} for includes") - if (_ignoredExtensions) - message (STATUS "Ignored extensions: ${_ignoredExtensions}") - endif() - if (_ignoredIncludeDirs) - message (STATUS "Ignored paths: ${_ignoredIncludeDirs}") - endif() - if (_honoredIncludeDirs) - message (STATUS "Included paths: ${_honoredIncludeDirs}") - endif() - endif() - set (_sourceFiles ${ARGN}) - set (_selectedIncludes "") - set (_unparsedLines "") - # stack keeps track of inside/outside project status of processed header files - set (_headerIsInsideStack "") - foreach (_line IN LISTS _scanOutput) - if (_line) - cotire_parse_line("${_line}" _headerFile _headerDepth) - if (_headerFile) - cotire_check_header_file_location("${_headerFile}" "${_ignoredIncludeDirs}" "${_honoredIncludeDirs}" _headerIsInside) - if (COTIRE_DEBUG) - message (STATUS "${_headerDepth}: ${_headerFile} ${_headerIsInside}") - endif() - # update stack - list (LENGTH _headerIsInsideStack _stackLen) - if (_headerDepth GREATER _stackLen) - math (EXPR _stackLen "${_stackLen} + 1") - foreach (_index RANGE ${_stackLen} ${_headerDepth}) - list (APPEND _headerIsInsideStack ${_headerIsInside}) - endforeach() - else() - foreach (_index RANGE ${_headerDepth} ${_stackLen}) - list (REMOVE_AT _headerIsInsideStack -1) - endforeach() - list (APPEND _headerIsInsideStack ${_headerIsInside}) - endif() - if (COTIRE_DEBUG) - message (STATUS "${_headerIsInsideStack}") - endif() - # header is a candidate if it is outside project - if (NOT _headerIsInside) - # get parent header file's inside/outside status - if (_headerDepth GREATER 1) - math (EXPR _index "${_headerDepth} - 2") - list (GET _headerIsInsideStack ${_index} _parentHeaderIsInside) - else() - set (_parentHeaderIsInside TRUE) - endif() - # select header file if parent header file is inside project - # (e.g., a project header file that includes a standard header file) - if (_parentHeaderIsInside) - cotire_check_ignore_header_file_path("${_headerFile}" _headerIsIgnored) - if (NOT _headerIsIgnored) - cotire_check_ignore_header_file_ext("${_headerFile}" _ignoredExtensions _headerIsIgnored) - if (NOT _headerIsIgnored) - list (APPEND _selectedIncludes "${_headerFile}") - else() - # fix header's inside status on stack, it is ignored by extension now - list (REMOVE_AT _headerIsInsideStack -1) - list (APPEND _headerIsInsideStack TRUE) - endif() - endif() - if (COTIRE_DEBUG) - message (STATUS "${_headerFile} ${_ignoredExtensions} ${_headerIsIgnored}") - endif() - endif() - endif() - else() - if (MSVC) - # for cl.exe do not keep unparsed lines which solely consist of a source file name - string (FIND "${_sourceFiles}" "${_line}" _index) - if (_index LESS 0) - list (APPEND _unparsedLines "${_line}") - endif() - else() - list (APPEND _unparsedLines "${_line}") - endif() - endif() - endif() - endforeach() - list (REMOVE_DUPLICATES _selectedIncludes) - set (${_selectedIncludesVar} ${_selectedIncludes} PARENT_SCOPE) - set (${_unparsedLinesVar} ${_unparsedLines} PARENT_SCOPE) -endfunction() - -function (cotire_scan_includes _includesVar) - set(_options "") - set(_oneValueArgs COMPILER_ID COMPILER_EXECUTABLE COMPILER_ARG1 COMPILER_VERSION LANGUAGE UNPARSED_LINES SCAN_RESULT) - set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES - IGNORE_PATH INCLUDE_PATH IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH COMPILER_LAUNCHER) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) - if (NOT _option_LANGUAGE) - set (_option_LANGUAGE "CXX") - endif() - if (NOT _option_COMPILER_ID) - set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") - endif() - if (NOT _option_COMPILER_VERSION) - set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") - endif() - cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_LAUNCHER}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") - cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) - cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) - cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) - cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) - cotire_add_makedep_flags("${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" _cmd) - # only consider existing source files for scanning - set (_existingSourceFiles "") - foreach (_sourceFile ${_sourceFiles}) - if (EXISTS "${_sourceFile}") - list (APPEND _existingSourceFiles "${_sourceFile}") - endif() - endforeach() - if (NOT _existingSourceFiles) - set (${_includesVar} "" PARENT_SCOPE) - return() - endif() - # add source files to be scanned - if (WIN32) - foreach (_sourceFile ${_existingSourceFiles}) - file (TO_NATIVE_PATH "${_sourceFile}" _sourceFileNative) - list (APPEND _cmd "${_sourceFileNative}") - endforeach() - else() - list (APPEND _cmd ${_existingSourceFiles}) - endif() - if (COTIRE_VERBOSE) - message (STATUS "execute_process: ${_cmd}") - endif() - if (MSVC_IDE OR _option_COMPILER_ID MATCHES "MSVC") - # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared - unset (ENV{VS_UNICODE_OUTPUT}) - endif() - execute_process( - COMMAND ${_cmd} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE _result - OUTPUT_QUIET - ERROR_VARIABLE _output) - if (_result) - message (STATUS "Result ${_result} scanning includes of ${_existingSourceFiles}.") - endif() - cotire_parse_includes( - "${_option_LANGUAGE}" "${_output}" - "${_option_IGNORE_PATH}" "${_option_INCLUDE_PATH}" - "${_option_IGNORE_EXTENSIONS}" - _includes _unparsedLines - ${_sourceFiles}) - if (_option_INCLUDE_PRIORITY_PATH) - set (_sortedIncludes "") - foreach (_priorityPath ${_option_INCLUDE_PRIORITY_PATH}) - foreach (_include ${_includes}) - string (FIND ${_include} ${_priorityPath} _position) - if (_position GREATER -1) - list (APPEND _sortedIncludes ${_include}) - endif() - endforeach() - endforeach() - if (_sortedIncludes) - list (INSERT _includes 0 ${_sortedIncludes}) - list (REMOVE_DUPLICATES _includes) - endif() - endif() - set (${_includesVar} ${_includes} PARENT_SCOPE) - if (_option_UNPARSED_LINES) - set (${_option_UNPARSED_LINES} ${_unparsedLines} PARENT_SCOPE) - endif() - if (_option_SCAN_RESULT) - set (${_option_SCAN_RESULT} ${_result} PARENT_SCOPE) - endif() -endfunction() - -macro (cotire_append_undefs _contentsVar) - set (_undefs ${ARGN}) - if (_undefs) - list (REMOVE_DUPLICATES _undefs) - foreach (_definition ${_undefs}) - list (APPEND ${_contentsVar} "#undef ${_definition}") - endforeach() - endif() -endmacro() - -macro (cotire_comment_str _language _commentText _commentVar) - if ("${_language}" STREQUAL "CMAKE") - set (${_commentVar} "# ${_commentText}") - else() - set (${_commentVar} "/* ${_commentText} */") - endif() -endmacro() - -function (cotire_write_file _language _file _contents _force) - get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) - cotire_comment_str("${_language}" "${_moduleName} ${COTIRE_CMAKE_MODULE_VERSION} generated file" _header1) - cotire_comment_str("${_language}" "${_file}" _header2) - set (_contents "${_header1}\n${_header2}\n${_contents}") - if (COTIRE_DEBUG) - message (STATUS "${_contents}") - endif() - if (_force OR NOT EXISTS "${_file}") - file (WRITE "${_file}" "${_contents}") - else() - file (READ "${_file}" _oldContents) - if (NOT "${_oldContents}" STREQUAL "${_contents}") - file (WRITE "${_file}" "${_contents}") - else() - if (COTIRE_DEBUG) - message (STATUS "${_file} unchanged") - endif() - endif() - endif() -endfunction() - -function (cotire_generate_unity_source _unityFile) - set(_options "") - set(_oneValueArgs LANGUAGE) - set(_multiValueArgs - DEPENDS SOURCES_COMPILE_DEFINITIONS - PRE_UNDEFS SOURCES_PRE_UNDEFS POST_UNDEFS SOURCES_POST_UNDEFS PROLOGUE EPILOGUE) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if (_option_DEPENDS) - cotire_check_file_up_to_date(_unityFileIsUpToDate "${_unityFile}" ${_option_DEPENDS}) - if (_unityFileIsUpToDate) - return() - endif() - endif() - set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) - if (NOT _option_PRE_UNDEFS) - set (_option_PRE_UNDEFS "") - endif() - if (NOT _option_SOURCES_PRE_UNDEFS) - set (_option_SOURCES_PRE_UNDEFS "") - endif() - if (NOT _option_POST_UNDEFS) - set (_option_POST_UNDEFS "") - endif() - if (NOT _option_SOURCES_POST_UNDEFS) - set (_option_SOURCES_POST_UNDEFS "") - endif() - set (_contents "") - if (_option_PROLOGUE) - list (APPEND _contents ${_option_PROLOGUE}) - endif() - if (_option_LANGUAGE AND _sourceFiles) - if ("${_option_LANGUAGE}" STREQUAL "CXX") - list (APPEND _contents "#ifdef __cplusplus") - elseif ("${_option_LANGUAGE}" STREQUAL "C") - list (APPEND _contents "#ifndef __cplusplus") - endif() - endif() - set (_compileUndefinitions "") - foreach (_sourceFile ${_sourceFiles}) - cotire_get_source_compile_definitions( - "${_option_CONFIGURATION}" "${_option_LANGUAGE}" "${_sourceFile}" _compileDefinitions - ${_option_SOURCES_COMPILE_DEFINITIONS}) - cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_PRE_UNDEFS _sourcePreUndefs ${_option_SOURCES_PRE_UNDEFS}) - cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_POST_UNDEFS _sourcePostUndefs ${_option_SOURCES_POST_UNDEFS}) - if (_option_PRE_UNDEFS) - list (APPEND _compileUndefinitions ${_option_PRE_UNDEFS}) - endif() - if (_sourcePreUndefs) - list (APPEND _compileUndefinitions ${_sourcePreUndefs}) - endif() - if (_compileUndefinitions) - cotire_append_undefs(_contents ${_compileUndefinitions}) - set (_compileUndefinitions "") - endif() - if (_sourcePostUndefs) - list (APPEND _compileUndefinitions ${_sourcePostUndefs}) - endif() - if (_option_POST_UNDEFS) - list (APPEND _compileUndefinitions ${_option_POST_UNDEFS}) - endif() - foreach (_definition ${_compileDefinitions}) - if (_definition MATCHES "^([a-zA-Z0-9_]+)=(.+)$") - list (APPEND _contents "#define ${CMAKE_MATCH_1} ${CMAKE_MATCH_2}") - list (INSERT _compileUndefinitions 0 "${CMAKE_MATCH_1}") - else() - list (APPEND _contents "#define ${_definition}") - list (INSERT _compileUndefinitions 0 "${_definition}") - endif() - endforeach() - # use absolute path as source file location - get_filename_component(_sourceFileLocation "${_sourceFile}" ABSOLUTE) - if (WIN32) - file (TO_NATIVE_PATH "${_sourceFileLocation}" _sourceFileLocation) - endif() - list (APPEND _contents "#include \"${_sourceFileLocation}\"") - endforeach() - if (_compileUndefinitions) - cotire_append_undefs(_contents ${_compileUndefinitions}) - set (_compileUndefinitions "") - endif() - if (_option_LANGUAGE AND _sourceFiles) - list (APPEND _contents "#endif") - endif() - if (_option_EPILOGUE) - list (APPEND _contents ${_option_EPILOGUE}) - endif() - list (APPEND _contents "") - string (REPLACE ";" "\n" _contents "${_contents}") - if (COTIRE_VERBOSE) - message ("${_contents}") - endif() - cotire_write_file("${_option_LANGUAGE}" "${_unityFile}" "${_contents}" TRUE) -endfunction() - -function (cotire_generate_prefix_header _prefixFile) - set(_options "") - set(_oneValueArgs LANGUAGE COMPILER_EXECUTABLE COMPILER_ARG1 COMPILER_ID COMPILER_VERSION) - set(_multiValueArgs DEPENDS COMPILE_DEFINITIONS COMPILE_FLAGS - INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES IGNORE_PATH INCLUDE_PATH - IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH COMPILER_LAUNCHER) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if (NOT _option_COMPILER_ID) - set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") - endif() - if (NOT _option_COMPILER_VERSION) - set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") - endif() - if (_option_DEPENDS) - cotire_check_file_up_to_date(_prefixFileIsUpToDate "${_prefixFile}" ${_option_DEPENDS}) - if (_prefixFileIsUpToDate) - # create empty log file - set (_unparsedLinesFile "${_prefixFile}.log") - file (WRITE "${_unparsedLinesFile}" "") - return() - endif() - endif() - set (_prologue "") - set (_epilogue "") - if (_option_COMPILER_ID MATCHES "Clang") - set (_prologue "#pragma clang system_header") - elseif (_option_COMPILER_ID MATCHES "GNU") - set (_prologue "#pragma GCC system_header") - elseif (_option_COMPILER_ID MATCHES "MSVC") - set (_prologue "#pragma warning(push, 0)") - set (_epilogue "#pragma warning(pop)") - elseif (_option_COMPILER_ID MATCHES "Intel") - # Intel compiler requires hdrstop pragma to stop generating PCH file - set (_epilogue "#pragma hdrstop") - endif() - set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) - cotire_scan_includes(_selectedHeaders ${_sourceFiles} - LANGUAGE "${_option_LANGUAGE}" - COMPILER_LAUNCHER "${_option_COMPILER_LAUNCHER}" - COMPILER_EXECUTABLE "${_option_COMPILER_EXECUTABLE}" - COMPILER_ARG1 "${_option_COMPILER_ARG1}" - COMPILER_ID "${_option_COMPILER_ID}" - COMPILER_VERSION "${_option_COMPILER_VERSION}" - COMPILE_DEFINITIONS ${_option_COMPILE_DEFINITIONS} - COMPILE_FLAGS ${_option_COMPILE_FLAGS} - INCLUDE_DIRECTORIES ${_option_INCLUDE_DIRECTORIES} - SYSTEM_INCLUDE_DIRECTORIES ${_option_SYSTEM_INCLUDE_DIRECTORIES} - IGNORE_PATH ${_option_IGNORE_PATH} - INCLUDE_PATH ${_option_INCLUDE_PATH} - IGNORE_EXTENSIONS ${_option_IGNORE_EXTENSIONS} - INCLUDE_PRIORITY_PATH ${_option_INCLUDE_PRIORITY_PATH} - UNPARSED_LINES _unparsedLines - SCAN_RESULT _scanResult) - cotire_generate_unity_source("${_prefixFile}" - PROLOGUE ${_prologue} EPILOGUE ${_epilogue} LANGUAGE "${_option_LANGUAGE}" ${_selectedHeaders}) - set (_unparsedLinesFile "${_prefixFile}.log") - if (_unparsedLines) - if (COTIRE_VERBOSE OR _scanResult OR NOT _selectedHeaders) - list (LENGTH _unparsedLines _skippedLineCount) - if (WIN32) - file (TO_NATIVE_PATH "${_unparsedLinesFile}" _unparsedLinesLogPath) - else() - set (_unparsedLinesLogPath "${_unparsedLinesFile}") - endif() - message (STATUS "${_skippedLineCount} line(s) skipped, see ${_unparsedLinesLogPath}") - endif() - string (REPLACE ";" "\n" _unparsedLines "${_unparsedLines}") - endif() - file (WRITE "${_unparsedLinesFile}" "${_unparsedLines}\n") -endfunction() - -function (cotire_add_makedep_flags _language _compilerID _compilerVersion _flagsVar) - set (_flags ${${_flagsVar}}) - if (_compilerID MATCHES "MSVC") - # cl.exe options used - # /nologo suppresses display of sign-on banner - # /TC treat all files named on the command line as C source files - # /TP treat all files named on the command line as C++ source files - # /EP preprocess to stdout without #line directives - # /showIncludes list include files - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /showIncludes) - else() - # return as a flag string - set (_flags "${_sourceFileType${_language}} /EP /showIncludes") - endif() - elseif (_compilerID MATCHES "GNU") - # GCC options used - # -H print the name of each header file used - # -E invoke preprocessor - # -fdirectives-only do not expand macros, requires GCC >= 4.3 - if (_flags) - # append to list - list (APPEND _flags -H -E) - if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0") - list (APPEND _flags -fdirectives-only) - endif() - else() - # return as a flag string - set (_flags "-H -E") - if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0") - set (_flags "${_flags} -fdirectives-only") - endif() - endif() - elseif (_compilerID MATCHES "Clang") - if (UNIX) - # Clang options used - # -H print the name of each header file used - # -E invoke preprocessor - # -fno-color-diagnostics do not print diagnostics in color - # -Eonly just run preprocessor, no output - if (_flags) - # append to list - list (APPEND _flags -H -E -fno-color-diagnostics -Xclang -Eonly) - else() - # return as a flag string - set (_flags "-H -E -fno-color-diagnostics -Xclang -Eonly") - endif() - elseif (WIN32) - # Clang-cl.exe options used - # /TC treat all files named on the command line as C source files - # /TP treat all files named on the command line as C++ source files - # /EP preprocess to stdout without #line directives - # -H print the name of each header file used - # -fno-color-diagnostics do not print diagnostics in color - # -Eonly just run preprocessor, no output - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags "${_sourceFileType${_language}}" /EP -fno-color-diagnostics -Xclang -H -Xclang -Eonly) - else() - # return as a flag string - set (_flags "${_sourceFileType${_language}} /EP -fno-color-diagnostics -Xclang -H -Xclang -Eonly") - endif() - endif() - elseif (_compilerID MATCHES "Intel") - if (WIN32) - # Windows Intel options used - # /nologo do not display compiler version information - # /QH display the include file order - # /EP preprocess to stdout, omitting #line directives - # /TC process all source or unrecognized file types as C source files - # /TP process all source or unrecognized file types as C++ source files - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /QH) - else() - # return as a flag string - set (_flags "${_sourceFileType${_language}} /EP /QH") - endif() - else() - # Linux / Mac OS X Intel options used - # -H print the name of each header file used - # -EP preprocess to stdout, omitting #line directives - # -Kc++ process all source or unrecognized file types as C++ source files - if (_flags) - # append to list - if ("${_language}" STREQUAL "CXX") - list (APPEND _flags -Kc++) - endif() - list (APPEND _flags -H -EP) - else() - # return as a flag string - if ("${_language}" STREQUAL "CXX") - set (_flags "-Kc++ ") - endif() - set (_flags "${_flags}-H -EP") - endif() - endif() - else() - message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") - endif() - set (${_flagsVar} ${_flags} PARENT_SCOPE) -endfunction() - -function (cotire_add_pch_compilation_flags _language _compilerID _compilerVersion _prefixFile _pchFile _hostFile _flagsVar) - set (_flags ${${_flagsVar}}) - if (_compilerID MATCHES "MSVC") - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) - file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative) - # cl.exe options used - # /Yc creates a precompiled header file - # /Fp specifies precompiled header binary file name - # /FI forces inclusion of file - # /TC treat all files named on the command line as C source files - # /TP treat all files named on the command line as C++ source files - # /Zs syntax check only - # /Zm precompiled header memory allocation scaling factor - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags /nologo "${_sourceFileType${_language}}" - "/Yc${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}") - if (COTIRE_PCH_MEMORY_SCALING_FACTOR) - list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") - endif() - else() - # return as a flag string - set (_flags "/Yc\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") - if (COTIRE_PCH_MEMORY_SCALING_FACTOR) - set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") - endif() - endif() - elseif (_compilerID MATCHES "GNU") - # GCC options used - # -x specify the source language - # -c compile but do not link - # -o place output in file - # note that we cannot use -w to suppress all warnings upon pre-compiling, because turning off a warning may - # alter compile flags as a side effect (e.g., -Wwrite-string implies -fconst-strings) - set (_xLanguage_C "c-header") - set (_xLanguage_CXX "c++-header") - if (_flags) - # append to list - list (APPEND _flags -x "${_xLanguage_${_language}}" -c "${_prefixFile}" -o "${_pchFile}") - else() - # return as a flag string - set (_flags "-x ${_xLanguage_${_language}} -c \"${_prefixFile}\" -o \"${_pchFile}\"") - endif() - elseif (_compilerID MATCHES "Clang") - if (UNIX) - # Clang options used - # -x specify the source language - # -c compile but do not link - # -o place output in file - # -fno-pch-timestamp disable inclusion of timestamp in precompiled headers (clang 4.0.0+) - set (_xLanguage_C "c-header") - set (_xLanguage_CXX "c++-header") - if (_flags) - # append to list - list (APPEND _flags -x "${_xLanguage_${_language}}" -c "${_prefixFile}" -o "${_pchFile}") - if (NOT "${_compilerVersion}" VERSION_LESS "4.0.0") - list (APPEND _flags -Xclang -fno-pch-timestamp) - endif() - else() - # return as a flag string - set (_flags "-x ${_xLanguage_${_language}} -c \"${_prefixFile}\" -o \"${_pchFile}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "4.0.0") - set (_flags "${_flags} -Xclang -fno-pch-timestamp") - endif() - endif() - elseif (WIN32) - # Clang-cl.exe options used - # /Yc creates a precompiled header file - # /Fp specifies precompiled header binary file name - # /FI forces inclusion of file - # /Zs syntax check only - # /TC treat all files named on the command line as C source files - # /TP treat all files named on the command line as C++ source files - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags "${_sourceFileType${_language}}" - "/Yc${_prefixFile}" "/Fp${_pchFile}" "/FI${_prefixFile}" /Zs "${_hostFile}") - else() - # return as a flag string - set (_flags "/Yc\"${_prefixFile}\" /Fp\"${_pchFile}\" /FI\"${_prefixFile}\"") - endif() - endif() - elseif (_compilerID MATCHES "Intel") - if (WIN32) - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) - file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative) - # Windows Intel options used - # /nologo do not display compiler version information - # /Yc create a precompiled header (PCH) file - # /Fp specify a path or file name for precompiled header files - # /FI tells the preprocessor to include a specified file name as the header file - # /TC process all source or unrecognized file types as C source files - # /TP process all source or unrecognized file types as C++ source files - # /Zs syntax check only - # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags /nologo "${_sourceFileType${_language}}" - "/Yc" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - list (APPEND _flags "/Wpch-messages") - endif() - else() - # return as a flag string - set (_flags "/Yc /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - set (_flags "${_flags} /Wpch-messages") - endif() - endif() - else() - # Linux / Mac OS X Intel options used - # -pch-dir location for precompiled header files - # -pch-create name of the precompiled header (PCH) to create - # -Kc++ process all source or unrecognized file types as C++ source files - # -fsyntax-only check only for correct syntax - # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) - get_filename_component(_pchDir "${_pchFile}" DIRECTORY) - get_filename_component(_pchName "${_pchFile}" NAME) - set (_xLanguage_C "c-header") - set (_xLanguage_CXX "c++-header") - set (_pchSuppressMessages FALSE) - if ("${CMAKE_${_language}_FLAGS}" MATCHES ".*-Wno-pch-messages.*") - set(_pchSuppressMessages TRUE) - endif() - if (_flags) - # append to list - if ("${_language}" STREQUAL "CXX") - list (APPEND _flags -Kc++) - endif() - list (APPEND _flags -include "${_prefixFile}" -pch-dir "${_pchDir}" -pch-create "${_pchName}" -fsyntax-only "${_hostFile}") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - if (NOT _pchSuppressMessages) - list (APPEND _flags -Wpch-messages) - endif() - endif() - else() - # return as a flag string - set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-create \"${_pchName}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - if (NOT _pchSuppressMessages) - set (_flags "${_flags} -Wpch-messages") - endif() - endif() - endif() - endif() - else() - message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") - endif() - set (${_flagsVar} ${_flags} PARENT_SCOPE) -endfunction() - -function (cotire_add_prefix_pch_inclusion_flags _language _compilerID _compilerVersion _prefixFile _pchFile _flagsVar) - set (_flags ${${_flagsVar}}) - if (_compilerID MATCHES "MSVC") - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) - # cl.exe options used - # /Yu uses a precompiled header file during build - # /Fp specifies precompiled header binary file name - # /FI forces inclusion of file - # /Zm precompiled header memory allocation scaling factor - if (_pchFile) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) - if (_flags) - # append to list - list (APPEND _flags "/Yu${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}") - if (COTIRE_PCH_MEMORY_SCALING_FACTOR) - list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") - endif() - else() - # return as a flag string - set (_flags "/Yu\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") - if (COTIRE_PCH_MEMORY_SCALING_FACTOR) - set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") - endif() - endif() - else() - # no precompiled header, force inclusion of prefix header - if (_flags) - # append to list - list (APPEND _flags "/FI${_prefixFileNative}") - else() - # return as a flag string - set (_flags "/FI\"${_prefixFileNative}\"") - endif() - endif() - elseif (_compilerID MATCHES "GNU") - # GCC options used - # -include process include file as the first line of the primary source file - # -Winvalid-pch warns if precompiled header is found but cannot be used - # note: ccache requires the -include flag to be used in order to process precompiled header correctly - if (_flags) - # append to list - list (APPEND _flags -Winvalid-pch -include "${_prefixFile}") - else() - # return as a flag string - set (_flags "-Winvalid-pch -include \"${_prefixFile}\"") - endif() - elseif (_compilerID MATCHES "Clang") - if (UNIX) - # Clang options used - # -include process include file as the first line of the primary source file - # note: ccache requires the -include flag to be used in order to process precompiled header correctly - if (_flags) - # append to list - list (APPEND _flags -include "${_prefixFile}") - else() - # return as a flag string - set (_flags "-include \"${_prefixFile}\"") - endif() - elseif (WIN32) - # Clang-cl.exe options used - # /Yu uses a precompiled header file during build - # /Fp specifies precompiled header binary file name - # /FI forces inclusion of file - if (_pchFile) - if (_flags) - # append to list - list (APPEND _flags "/Yu${_prefixFile}" "/Fp${_pchFile}" "/FI${_prefixFile}") - else() - # return as a flag string - set (_flags "/Yu\"${_prefixFile}\" /Fp\"${_pchFile}\" /FI\"${_prefixFile}\"") - endif() - else() - # no precompiled header, force inclusion of prefix header - if (_flags) - # append to list - list (APPEND _flags "/FI${_prefixFile}") - else() - # return as a flag string - set (_flags "/FI\"${_prefixFile}\"") - endif() - endif() - endif() - elseif (_compilerID MATCHES "Intel") - if (WIN32) - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) - # Windows Intel options used - # /Yu use a precompiled header (PCH) file - # /Fp specify a path or file name for precompiled header files - # /FI tells the preprocessor to include a specified file name as the header file - # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) - if (_pchFile) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) - if (_flags) - # append to list - list (APPEND _flags "/Yu" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - list (APPEND _flags "/Wpch-messages") - endif() - else() - # return as a flag string - set (_flags "/Yu /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - set (_flags "${_flags} /Wpch-messages") - endif() - endif() - else() - # no precompiled header, force inclusion of prefix header - if (_flags) - # append to list - list (APPEND _flags "/FI${_prefixFileNative}") - else() - # return as a flag string - set (_flags "/FI\"${_prefixFileNative}\"") - endif() - endif() - else() - # Linux / Mac OS X Intel options used - # -pch-dir location for precompiled header files - # -pch-use name of the precompiled header (PCH) to use - # -include process include file as the first line of the primary source file - # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) - if (_pchFile) - get_filename_component(_pchDir "${_pchFile}" DIRECTORY) - get_filename_component(_pchName "${_pchFile}" NAME) - set (_pchSuppressMessages FALSE) - if ("${CMAKE_${_language}_FLAGS}" MATCHES ".*-Wno-pch-messages.*") - set(_pchSuppressMessages TRUE) - endif() - if (_flags) - # append to list - list (APPEND _flags -include "${_prefixFile}" -pch-dir "${_pchDir}" -pch-use "${_pchName}") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - if (NOT _pchSuppressMessages) - list (APPEND _flags -Wpch-messages) - endif() - endif() - else() - # return as a flag string - set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-use \"${_pchName}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - if (NOT _pchSuppressMessages) - set (_flags "${_flags} -Wpch-messages") - endif() - endif() - endif() - else() - # no precompiled header, force inclusion of prefix header - if (_flags) - # append to list - list (APPEND _flags -include "${_prefixFile}") - else() - # return as a flag string - set (_flags "-include \"${_prefixFile}\"") - endif() - endif() - endif() - else() - message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") - endif() - set (${_flagsVar} ${_flags} PARENT_SCOPE) -endfunction() - -function (cotire_precompile_prefix_header _prefixFile _pchFile _hostFile) - set(_options "") - set(_oneValueArgs COMPILER_EXECUTABLE COMPILER_ARG1 COMPILER_ID COMPILER_VERSION LANGUAGE) - set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES SYS COMPILER_LAUNCHER) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if (NOT _option_LANGUAGE) - set (_option_LANGUAGE "CXX") - endif() - if (NOT _option_COMPILER_ID) - set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") - endif() - if (NOT _option_COMPILER_VERSION) - set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") - endif() - cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_LAUNCHER}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") - cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) - cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) - cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) - cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) - cotire_add_pch_compilation_flags( - "${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" "${_hostFile}" _cmd) - if (COTIRE_VERBOSE) - message (STATUS "execute_process: ${_cmd}") - endif() - if (MSVC_IDE OR _option_COMPILER_ID MATCHES "MSVC") - # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared - unset (ENV{VS_UNICODE_OUTPUT}) - elseif (_option_COMPILER_ID MATCHES "Clang" AND _option_COMPILER_VERSION VERSION_LESS "4.0.0") - if (_option_COMPILER_LAUNCHER MATCHES "ccache" OR - _option_COMPILER_EXECUTABLE MATCHES "ccache") - # Newer versions of Clang embed a compilation timestamp into the precompiled header binary, - # which results in "file has been modified since the precompiled header was built" errors if ccache is used. - # We work around the problem by disabling ccache upon pre-compiling the prefix header. - set (ENV{CCACHE_DISABLE} "true") - endif() - endif() - execute_process( - COMMAND ${_cmd} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE _result) - if (_result) - message (FATAL_ERROR "cotire: error ${_result} precompiling ${_prefixFile}.") - endif() -endfunction() - -function (cotire_check_precompiled_header_support _language _target _msgVar) - set (_unsupportedCompiler - "Precompiled headers not supported for ${_language} compiler ${CMAKE_${_language}_COMPILER_ID}") - if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC") - # PCH supported since Visual Studio C++ 6.0 - # and CMake does not support an earlier version - set (${_msgVar} "" PARENT_SCOPE) - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU") - # GCC PCH support requires version >= 3.4 - if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "3.4.0") - set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) - else() - set (${_msgVar} "" PARENT_SCOPE) - endif() - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Clang") - if (UNIX) - # all Unix Clang versions have PCH support - set (${_msgVar} "" PARENT_SCOPE) - elseif (WIN32) - # only clang-cl is supported under Windows - get_filename_component(_compilerName "${CMAKE_${_language}_COMPILER}" NAME_WE) - if (NOT _compilerName MATCHES "cl$") - set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}. Use clang-cl instead." PARENT_SCOPE) - endif() - endif() - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel") - # Intel PCH support requires version >= 8.0.0 - if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "8.0.0") - set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) - else() - set (${_msgVar} "" PARENT_SCOPE) - endif() - else() - set (${_msgVar} "${_unsupportedCompiler}." PARENT_SCOPE) - endif() - # check if ccache is used as a compiler launcher - get_target_property(_launcher ${_target} ${_language}_COMPILER_LAUNCHER) - get_filename_component(_realCompilerExe "${CMAKE_${_language}_COMPILER}" REALPATH) - if (_realCompilerExe MATCHES "ccache" OR _launcher MATCHES "ccache") - # verify that ccache configuration is compatible with precompiled headers - # always check environment variable CCACHE_SLOPPINESS, because earlier versions of ccache - # do not report the "sloppiness" setting correctly upon printing ccache configuration - if (DEFINED ENV{CCACHE_SLOPPINESS}) - if (NOT "$ENV{CCACHE_SLOPPINESS}" MATCHES "pch_defines" OR - NOT "$ENV{CCACHE_SLOPPINESS}" MATCHES "time_macros") - set (${_msgVar} - "ccache requires the environment variable CCACHE_SLOPPINESS to be set to \"pch_defines,time_macros\"." - PARENT_SCOPE) - endif() - else() - if (_realCompilerExe MATCHES "ccache") - set (_ccacheExe "${_realCompilerExe}") - else() - set (_ccacheExe "${_launcher}") - endif() - execute_process( - COMMAND "${_ccacheExe}" "--print-config" - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _ccacheConfig OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET) - if (_result) - set (${_msgVar} "ccache configuration cannot be determined." PARENT_SCOPE) - elseif (NOT _ccacheConfig MATCHES "sloppiness.*=.*time_macros" OR - NOT _ccacheConfig MATCHES "sloppiness.*=.*pch_defines") - set (${_msgVar} - "ccache requires configuration setting \"sloppiness\" to be set to \"pch_defines,time_macros\"." - PARENT_SCOPE) - endif() - endif() - endif() - if (APPLE) - # PCH compilation not supported by GCC / Clang for multi-architecture builds (e.g., i386, x86_64) - cotire_get_configuration_types(_configs) - foreach (_config ${_configs}) - set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) - cotire_filter_compile_flags("${_language}" "arch" _architectures _ignore ${_targetFlags}) - list (LENGTH _architectures _numberOfArchitectures) - if (_numberOfArchitectures GREATER 1) - string (REPLACE ";" ", " _architectureStr "${_architectures}") - set (${_msgVar} - "Precompiled headers not supported on Darwin for multi-architecture builds (${_architectureStr})." - PARENT_SCOPE) - break() - endif() - endforeach() - endif() -endfunction() - -macro (cotire_get_intermediate_dir _cotireDir) - # ${CMAKE_CFG_INTDIR} may reference a build-time variable when using a generator which supports configuration types - get_filename_component(${_cotireDir} "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${COTIRE_INTDIR}" ABSOLUTE) -endmacro() - -macro (cotire_setup_file_extension_variables) - set (_unityFileExt_C ".c") - set (_unityFileExt_CXX ".cxx") - set (_prefixFileExt_C ".h") - set (_prefixFileExt_CXX ".hxx") - set (_prefixSourceFileExt_C ".c") - set (_prefixSourceFileExt_CXX ".cxx") -endmacro() - -function (cotire_make_single_unity_source_file_path _language _target _unityFileVar) - cotire_setup_file_extension_variables() - if (NOT DEFINED _unityFileExt_${_language}) - set (${_unityFileVar} "" PARENT_SCOPE) - return() - endif() - set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") - set (_unityFileName "${_unityFileBaseName}${_unityFileExt_${_language}}") - cotire_get_intermediate_dir(_baseDir) - set (_unityFile "${_baseDir}/${_unityFileName}") - set (${_unityFileVar} "${_unityFile}" PARENT_SCOPE) -endfunction() - -function (cotire_make_unity_source_file_paths _language _target _maxIncludes _unityFilesVar) - cotire_setup_file_extension_variables() - if (NOT DEFINED _unityFileExt_${_language}) - set (${_unityFileVar} "" PARENT_SCOPE) - return() - endif() - set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") - cotire_get_intermediate_dir(_baseDir) - set (_startIndex 0) - set (_index 0) - set (_unityFiles "") - set (_sourceFiles ${ARGN}) - foreach (_sourceFile ${_sourceFiles}) - get_source_file_property(_startNew "${_sourceFile}" COTIRE_START_NEW_UNITY_SOURCE) - math (EXPR _unityFileCount "${_index} - ${_startIndex}") - if (_startNew OR (_maxIncludes GREATER 0 AND NOT _unityFileCount LESS _maxIncludes)) - if (_index GREATER 0) - # start new unity file segment - math (EXPR _endIndex "${_index} - 1") - set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}") - list (APPEND _unityFiles "${_baseDir}/${_unityFileName}") - endif() - set (_startIndex ${_index}) - endif() - math (EXPR _index "${_index} + 1") - endforeach() - list (LENGTH _sourceFiles _numberOfSources) - if (_startIndex EQUAL 0) - # there is only a single unity file - cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFiles) - elseif (_startIndex LESS _numberOfSources) - # end with final unity file segment - math (EXPR _endIndex "${_index} - 1") - set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}") - list (APPEND _unityFiles "${_baseDir}/${_unityFileName}") - endif() - set (${_unityFilesVar} ${_unityFiles} PARENT_SCOPE) - if (COTIRE_DEBUG AND _unityFiles) - message (STATUS "unity files: ${_unityFiles}") - endif() -endfunction() - -function (cotire_unity_to_prefix_file_path _language _target _unityFile _prefixFileVar) - cotire_setup_file_extension_variables() - if (NOT DEFINED _unityFileExt_${_language}) - set (${_prefixFileVar} "" PARENT_SCOPE) - return() - endif() - set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") - set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") - string (REPLACE "${_unityFileBaseName}" "${_prefixFileBaseName}" _prefixFile "${_unityFile}") - string (REGEX REPLACE "${_unityFileExt_${_language}}$" "${_prefixFileExt_${_language}}" _prefixFile "${_prefixFile}") - set (${_prefixFileVar} "${_prefixFile}" PARENT_SCOPE) -endfunction() - -function (cotire_prefix_header_to_source_file_path _language _prefixHeaderFile _prefixSourceFileVar) - cotire_setup_file_extension_variables() - if (NOT DEFINED _prefixSourceFileExt_${_language}) - set (${_prefixSourceFileVar} "" PARENT_SCOPE) - return() - endif() - string (REGEX REPLACE "${_prefixFileExt_${_language}}$" "${_prefixSourceFileExt_${_language}}" _prefixSourceFile "${_prefixHeaderFile}") - set (${_prefixSourceFileVar} "${_prefixSourceFile}" PARENT_SCOPE) -endfunction() - -function (cotire_make_prefix_file_name _language _target _prefixFileBaseNameVar _prefixFileNameVar) - cotire_setup_file_extension_variables() - if (NOT _language) - set (_prefixFileBaseName "${_target}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") - set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_C}") - elseif (DEFINED _prefixFileExt_${_language}) - set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") - set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_${_language}}") - else() - set (_prefixFileBaseName "") - set (_prefixFileName "") - endif() - set (${_prefixFileBaseNameVar} "${_prefixFileBaseName}" PARENT_SCOPE) - set (${_prefixFileNameVar} "${_prefixFileName}" PARENT_SCOPE) -endfunction() - -function (cotire_make_prefix_file_path _language _target _prefixFileVar) - cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName) - set (${_prefixFileVar} "" PARENT_SCOPE) - if (_prefixFileName) - if (NOT _language) - set (_language "C") - endif() - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang|Intel|MSVC") - cotire_get_intermediate_dir(_baseDir) - set (${_prefixFileVar} "${_baseDir}/${_prefixFileName}" PARENT_SCOPE) - endif() - endif() -endfunction() - -function (cotire_make_pch_file_path _language _target _pchFileVar) - cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName) - set (${_pchFileVar} "" PARENT_SCOPE) - if (_prefixFileBaseName AND _prefixFileName) - cotire_check_precompiled_header_support("${_language}" "${_target}" _msg) - if (NOT _msg) - if (XCODE) - # For Xcode, we completely hand off the compilation of the prefix header to the IDE - return() - endif() - cotire_get_intermediate_dir(_baseDir) - if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC") - # MSVC uses the extension .pch added to the prefix header base name - set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pch" PARENT_SCOPE) - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Clang") - # Clang looks for a precompiled header corresponding to the prefix header with the extension .pch appended - set (${_pchFileVar} "${_baseDir}/${_prefixFileName}.pch" PARENT_SCOPE) - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU") - # GCC looks for a precompiled header corresponding to the prefix header with the extension .gch appended - set (${_pchFileVar} "${_baseDir}/${_prefixFileName}.gch" PARENT_SCOPE) - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel") - # Intel uses the extension .pchi added to the prefix header base name - set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pchi" PARENT_SCOPE) - endif() - endif() - endif() -endfunction() - -function (cotire_select_unity_source_files _unityFile _sourcesVar) - set (_sourceFiles ${ARGN}) - if (_sourceFiles AND "${_unityFile}" MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}_([0-9]+)_([0-9]+)") - set (_startIndex ${CMAKE_MATCH_1}) - set (_endIndex ${CMAKE_MATCH_2}) - list (LENGTH _sourceFiles _numberOfSources) - if (NOT _startIndex LESS _numberOfSources) - math (EXPR _startIndex "${_numberOfSources} - 1") - endif() - if (NOT _endIndex LESS _numberOfSources) - math (EXPR _endIndex "${_numberOfSources} - 1") - endif() - set (_files "") - foreach (_index RANGE ${_startIndex} ${_endIndex}) - list (GET _sourceFiles ${_index} _file) - list (APPEND _files "${_file}") - endforeach() - else() - set (_files ${_sourceFiles}) - endif() - set (${_sourcesVar} ${_files} PARENT_SCOPE) -endfunction() - -function (cotire_get_unity_source_dependencies _language _target _dependencySourcesVar) - set (_dependencySources "") - # depend on target's generated source files - get_target_property(_targetSourceFiles ${_target} SOURCES) - cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) - if (_generatedSources) - # but omit all generated source files that have the COTIRE_EXCLUDED property set to true - cotire_get_objects_with_property_on(_excludedGeneratedSources COTIRE_EXCLUDED SOURCE ${_generatedSources}) - if (_excludedGeneratedSources) - list (REMOVE_ITEM _generatedSources ${_excludedGeneratedSources}) - endif() - # and omit all generated source files that have the COTIRE_DEPENDENCY property set to false explicitly - cotire_get_objects_with_property_off(_excludedNonDependencySources COTIRE_DEPENDENCY SOURCE ${_generatedSources}) - if (_excludedNonDependencySources) - list (REMOVE_ITEM _generatedSources ${_excludedNonDependencySources}) - endif() - if (_generatedSources) - list (APPEND _dependencySources ${_generatedSources}) - endif() - endif() - if (COTIRE_DEBUG AND _dependencySources) - message (STATUS "${_language} ${_target} unity source dependencies: ${_dependencySources}") - endif() - set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) -endfunction() - -function (cotire_get_prefix_header_dependencies _language _target _dependencySourcesVar) - set (_dependencySources "") - # depend on target source files marked with custom COTIRE_DEPENDENCY property - get_target_property(_targetSourceFiles ${_target} SOURCES) - cotire_get_objects_with_property_on(_dependencySources COTIRE_DEPENDENCY SOURCE ${_targetSourceFiles}) - if (COTIRE_DEBUG AND _dependencySources) - message (STATUS "${_language} ${_target} prefix header dependencies: ${_dependencySources}") - endif() - set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) -endfunction() - -function (cotire_generate_target_script _language _configurations _target _targetScriptVar _targetConfigScriptVar) - set (_targetSources ${ARGN}) - cotire_get_prefix_header_dependencies(${_language} ${_target} COTIRE_TARGET_PREFIX_DEPENDS ${_targetSources}) - cotire_get_unity_source_dependencies(${_language} ${_target} COTIRE_TARGET_UNITY_DEPENDS ${_targetSources}) - # set up variables to be configured - set (COTIRE_TARGET_LANGUAGE "${_language}") - get_target_property(COTIRE_TARGET_IGNORE_PATH ${_target} COTIRE_PREFIX_HEADER_IGNORE_PATH) - cotire_add_sys_root_paths(COTIRE_TARGET_IGNORE_PATH) - get_target_property(COTIRE_TARGET_INCLUDE_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PATH) - cotire_add_sys_root_paths(COTIRE_TARGET_INCLUDE_PATH) - get_target_property(COTIRE_TARGET_PRE_UNDEFS ${_target} COTIRE_UNITY_SOURCE_PRE_UNDEFS) - get_target_property(COTIRE_TARGET_POST_UNDEFS ${_target} COTIRE_UNITY_SOURCE_POST_UNDEFS) - get_target_property(COTIRE_TARGET_MAXIMUM_NUMBER_OF_INCLUDES ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES) - get_target_property(COTIRE_TARGET_INCLUDE_PRIORITY_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH) - cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_PRE_UNDEFS COTIRE_TARGET_SOURCES_PRE_UNDEFS ${_targetSources}) - cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_POST_UNDEFS COTIRE_TARGET_SOURCES_POST_UNDEFS ${_targetSources}) - set (COTIRE_TARGET_CONFIGURATION_TYPES "${_configurations}") - foreach (_config ${_configurations}) - string (TOUPPER "${_config}" _upperConfig) - cotire_get_target_include_directories( - "${_config}" "${_language}" "${_target}" COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig} COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}) - cotire_get_target_compile_definitions( - "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}) - cotire_get_target_compiler_flags( - "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}) - cotire_get_source_files_compile_definitions( - "${_config}" "${_language}" COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig} ${_targetSources}) - endforeach() - get_target_property(COTIRE_TARGET_${_language}_COMPILER_LAUNCHER ${_target} ${_language}_COMPILER_LAUNCHER) - # set up COTIRE_TARGET_SOURCES - set (COTIRE_TARGET_SOURCES "") - foreach (_sourceFile ${_targetSources}) - get_source_file_property(_generated "${_sourceFile}" GENERATED) - if (_generated) - # use absolute paths for generated files only, retrieving the LOCATION property is an expensive operation - get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) - list (APPEND COTIRE_TARGET_SOURCES "${_sourceLocation}") - else() - list (APPEND COTIRE_TARGET_SOURCES "${_sourceFile}") - endif() - endforeach() - # copy variable definitions to cotire target script - get_cmake_property(_vars VARIABLES) - string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+" _matchVars "${_vars}") - # omit COTIRE_*_INIT variables - string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+_INIT" _initVars "${_matchVars}") - if (_initVars) - list (REMOVE_ITEM _matchVars ${_initVars}) - endif() - # omit COTIRE_VERBOSE which is passed as a CMake define on command line - list (REMOVE_ITEM _matchVars COTIRE_VERBOSE) - set (_contents "") - set (_contentsHasGeneratorExpressions FALSE) - foreach (_var IN LISTS _matchVars ITEMS - XCODE MSVC CMAKE_GENERATOR CMAKE_BUILD_TYPE CMAKE_CONFIGURATION_TYPES - CMAKE_${_language}_COMPILER_ID CMAKE_${_language}_COMPILER_VERSION - CMAKE_${_language}_COMPILER_LAUNCHER CMAKE_${_language}_COMPILER CMAKE_${_language}_COMPILER_ARG1 - CMAKE_INCLUDE_FLAG_${_language} CMAKE_INCLUDE_FLAG_SEP_${_language} - CMAKE_INCLUDE_SYSTEM_FLAG_${_language} - CMAKE_${_language}_FRAMEWORK_SEARCH_FLAG - CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG - CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) - if (DEFINED ${_var}) - string (REPLACE "\"" "\\\"" _value "${${_var}}") - set (_contents "${_contents}set (${_var} \"${_value}\")\n") - if (NOT _contentsHasGeneratorExpressions) - if ("${_value}" MATCHES "\\$<.*>") - set (_contentsHasGeneratorExpressions TRUE) - endif() - endif() - endif() - endforeach() - # generate target script file - get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) - set (_targetCotireScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_moduleName}") - cotire_write_file("CMAKE" "${_targetCotireScript}" "${_contents}" FALSE) - if (_contentsHasGeneratorExpressions) - # use file(GENERATE ...) to expand generator expressions in the target script at CMake generate-time - set (_configNameOrNoneGeneratorExpression "$<$:None>$<$>:$>") - set (_targetCotireConfigScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_configNameOrNoneGeneratorExpression}_${_moduleName}") - file (GENERATE OUTPUT "${_targetCotireConfigScript}" INPUT "${_targetCotireScript}") - else() - set (_targetCotireConfigScript "${_targetCotireScript}") - endif() - set (${_targetScriptVar} "${_targetCotireScript}" PARENT_SCOPE) - set (${_targetConfigScriptVar} "${_targetCotireConfigScript}" PARENT_SCOPE) -endfunction() - -function (cotire_setup_pch_file_compilation _language _target _targetScript _prefixFile _pchFile _hostFile) - set (_sourceFiles ${ARGN}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel" OR - (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "Clang")) - # for MSVC, Intel and Clang-cl, we attach the precompiled header compilation to the host file - # the remaining files include the precompiled header, see cotire_setup_pch_file_inclusion - if (_sourceFiles) - set (_flags "") - cotire_add_pch_compilation_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" "${_hostFile}" _flags) - set_property (SOURCE ${_hostFile} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") - set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_OUTPUTS "${_pchFile}") - # make object file generated from host file depend on prefix header - set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") - # mark host file as cotired to prevent it from being used in another cotired target - set_property (SOURCE ${_hostFile} PROPERTY COTIRE_TARGET "${_target}") - endif() - elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - # for makefile based generator, we add a custom command to precompile the prefix header - if (_targetScript) - cotire_set_cmd_to_prologue(_cmds) - list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "precompile" "${_targetScript}" "${_prefixFile}" "${_pchFile}" "${_hostFile}") - if (MSVC_IDE) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileLogPath) - else() - file (RELATIVE_PATH _pchFileLogPath "${CMAKE_BINARY_DIR}" "${_pchFile}") - endif() - # make precompiled header compilation depend on the actual compiler executable used to force - # re-compilation when the compiler executable is updated. This prevents "created by a different GCC executable" - # warnings when the precompiled header is included. - get_filename_component(_realCompilerExe "${CMAKE_${_language}_COMPILER}" ABSOLUTE) - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_pchFile} ${_cmds} DEPENDS ${_prefixFile} ${_realCompilerExe} IMPLICIT_DEPENDS ${_language} ${_prefixFile}") - endif() - set_property (SOURCE "${_pchFile}" PROPERTY GENERATED TRUE) - add_custom_command( - OUTPUT "${_pchFile}" - COMMAND ${_cmds} - DEPENDS "${_prefixFile}" "${_realCompilerExe}" - IMPLICIT_DEPENDS ${_language} "${_prefixFile}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "Building ${_language} precompiled header ${_pchFileLogPath}" - VERBATIM) - endif() - endif() -endfunction() - -function (cotire_setup_pch_file_inclusion _language _target _wholeTarget _prefixFile _pchFile _hostFile) - if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel" OR - (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "Clang")) - # for MSVC, Intel and clang-cl, we include the precompiled header in all but the host file - # the host file does the precompiled header compilation, see cotire_setup_pch_file_compilation - set (_sourceFiles ${ARGN}) - list (LENGTH _sourceFiles _numberOfSourceFiles) - if (_numberOfSourceFiles GREATER 0) - # mark sources as cotired to prevent them from being used in another cotired target - set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") - set (_flags "") - cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" _flags) - set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") - # make object files generated from source files depend on precompiled header - set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") - endif() - elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - set (_sourceFiles ${_hostFile} ${ARGN}) - if (NOT _wholeTarget) - # for makefile based generator, we force the inclusion of the prefix header for a subset - # of the source files, if this is a multi-language target or has excluded files - set (_flags "") - cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" _flags) - set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") - # mark sources as cotired to prevent them from being used in another cotired target - set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") - endif() - # make object files generated from source files depend on precompiled header - set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") - endif() -endfunction() - -function (cotire_setup_prefix_file_inclusion _language _target _prefixFile) - set (_sourceFiles ${ARGN}) - # force the inclusion of the prefix header for the given source files - set (_flags "") - set (_pchFile "") - cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" _flags) - set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") - # mark sources as cotired to prevent them from being used in another cotired target - set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") - # make object files generated from source files depend on prefix header - set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") -endfunction() - -function (cotire_get_first_set_property_value _propertyValueVar _type _object) - set (_properties ${ARGN}) - foreach (_property ${_properties}) - get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) - if (_propertyValue) - set (${_propertyValueVar} ${_propertyValue} PARENT_SCOPE) - return() - endif() - endforeach() - set (${_propertyValueVar} "" PARENT_SCOPE) -endfunction() - -function (cotire_setup_combine_command _language _targetScript _joinedFile _cmdsVar) - set (_files ${ARGN}) - set (_filesPaths "") - foreach (_file ${_files}) - get_filename_component(_filePath "${_file}" ABSOLUTE) - list (APPEND _filesPaths "${_filePath}") - endforeach() - cotire_set_cmd_to_prologue(_prefixCmd) - list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "combine") - if (_targetScript) - list (APPEND _prefixCmd "${_targetScript}") - endif() - list (APPEND _prefixCmd "${_joinedFile}" ${_filesPaths}) - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_joinedFile} COMMAND ${_prefixCmd} DEPENDS ${_files}") - endif() - set_property (SOURCE "${_joinedFile}" PROPERTY GENERATED TRUE) - if (MSVC_IDE) - file (TO_NATIVE_PATH "${_joinedFile}" _joinedFileLogPath) - else() - file (RELATIVE_PATH _joinedFileLogPath "${CMAKE_BINARY_DIR}" "${_joinedFile}") - endif() - get_filename_component(_joinedFileBaseName "${_joinedFile}" NAME_WE) - get_filename_component(_joinedFileExt "${_joinedFile}" EXT) - if (_language AND _joinedFileBaseName MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}$") - set (_comment "Generating ${_language} unity source ${_joinedFileLogPath}") - elseif (_language AND _joinedFileBaseName MATCHES "${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}$") - if (_joinedFileExt MATCHES "^\\.c") - set (_comment "Generating ${_language} prefix source ${_joinedFileLogPath}") - else() - set (_comment "Generating ${_language} prefix header ${_joinedFileLogPath}") - endif() - else() - set (_comment "Generating ${_joinedFileLogPath}") - endif() - add_custom_command( - OUTPUT "${_joinedFile}" - COMMAND ${_prefixCmd} - DEPENDS ${_files} - COMMENT "${_comment}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - VERBATIM) - list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_setup_target_pch_usage _languages _target _wholeTarget) - if (XCODE) - # for Xcode, we attach a pre-build action to generate the unity sources and prefix headers - set (_prefixFiles "") - foreach (_language ${_languages}) - get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) - if (_prefixFile) - list (APPEND _prefixFiles "${_prefixFile}") - endif() - endforeach() - set (_cmds ${ARGN}) - list (LENGTH _prefixFiles _numberOfPrefixFiles) - if (_numberOfPrefixFiles GREATER 1) - # we also generate a generic, single prefix header which includes all language specific prefix headers - set (_language "") - set (_targetScript "") - cotire_make_prefix_file_path("${_language}" ${_target} _prefixHeader) - cotire_setup_combine_command("${_language}" "${_targetScript}" "${_prefixHeader}" _cmds ${_prefixFiles}) - else() - set (_prefixHeader "${_prefixFiles}") - endif() - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: TARGET ${_target} PRE_BUILD ${_cmds}") - endif() - # because CMake PRE_BUILD command does not support dependencies, - # we check dependencies explicity in cotire script mode when the pre-build action is run - add_custom_command( - TARGET "${_target}" - PRE_BUILD ${_cmds} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "Updating target ${_target} prefix headers" - VERBATIM) - # make Xcode precompile the generated prefix header with ProcessPCH and ProcessPCH++ - set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES") - set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${_prefixHeader}") - elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - # for makefile based generator, we force inclusion of the prefix header for all target source files - # if this is a single-language target without any excluded files - if (_wholeTarget) - set (_language "${_languages}") - # for MSVC, Intel and clang-cl, precompiled header inclusion is always done on the source file level - # see cotire_setup_pch_file_inclusion - if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel" AND NOT - (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "Clang")) - get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) - if (_prefixFile) - get_property(_pchFile TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER) - set (_options COMPILE_OPTIONS) - cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" _options) - set_property(TARGET ${_target} APPEND PROPERTY ${_options}) - endif() - endif() - endif() - endif() -endfunction() - -function (cotire_setup_unity_generation_commands _language _target _targetScript _targetConfigScript _unityFiles _cmdsVar) - set (_dependencySources "") - cotire_get_unity_source_dependencies(${_language} ${_target} _dependencySources ${ARGN}) - foreach (_unityFile ${_unityFiles}) - set_property (SOURCE "${_unityFile}" PROPERTY GENERATED TRUE) - # set up compiled unity source dependencies via OBJECT_DEPENDS - # this ensures that missing source files are generated before the unity file is compiled - if (COTIRE_DEBUG AND _dependencySources) - message (STATUS "${_unityFile} OBJECT_DEPENDS ${_dependencySources}") - endif() - if (_dependencySources) - # the OBJECT_DEPENDS property requires a list of full paths - set (_objectDependsPaths "") - foreach (_sourceFile ${_dependencySources}) - get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) - list (APPEND _objectDependsPaths "${_sourceLocation}") - endforeach() - set_property (SOURCE "${_unityFile}" PROPERTY OBJECT_DEPENDS ${_objectDependsPaths}) - endif() - if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - # unity file compilation results in potentially huge object file, - # thus use /bigobj by default unter cl.exe and Windows Intel - set_property (SOURCE "${_unityFile}" APPEND_STRING PROPERTY COMPILE_FLAGS "/bigobj") - endif() - cotire_set_cmd_to_prologue(_unityCmd) - list (APPEND _unityCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "unity" "${_targetConfigScript}" "${_unityFile}") - if (CMAKE_VERSION VERSION_LESS "3.1.0") - set (_unityCmdDepends "${_targetScript}") - else() - # CMake 3.1.0 supports generator expressions in arguments to DEPENDS - set (_unityCmdDepends "${_targetConfigScript}") - endif() - if (MSVC_IDE) - file (TO_NATIVE_PATH "${_unityFile}" _unityFileLogPath) - else() - file (RELATIVE_PATH _unityFileLogPath "${CMAKE_BINARY_DIR}" "${_unityFile}") - endif() - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_unityFile} COMMAND ${_unityCmd} DEPENDS ${_unityCmdDepends}") - endif() - add_custom_command( - OUTPUT "${_unityFile}" - COMMAND ${_unityCmd} - DEPENDS ${_unityCmdDepends} - COMMENT "Generating ${_language} unity source ${_unityFileLogPath}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - VERBATIM) - list (APPEND ${_cmdsVar} COMMAND ${_unityCmd}) - endforeach() - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_setup_prefix_generation_command _language _target _targetScript _prefixFile _unityFiles _cmdsVar) - set (_sourceFiles ${ARGN}) - set (_dependencySources "") - cotire_get_prefix_header_dependencies(${_language} ${_target} _dependencySources ${_sourceFiles}) - cotire_set_cmd_to_prologue(_prefixCmd) - list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "prefix" "${_targetScript}" "${_prefixFile}" ${_unityFiles}) - set_property (SOURCE "${_prefixFile}" PROPERTY GENERATED TRUE) - # make prefix header generation depend on the actual compiler executable used to force - # re-generation when the compiler executable is updated. This prevents "file not found" - # errors for compiler version specific system header files. - get_filename_component(_realCompilerExe "${CMAKE_${_language}_COMPILER}" ABSOLUTE) - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_prefixFile} COMMAND ${_prefixCmd} DEPENDS ${_unityFile} ${_dependencySources} ${_realCompilerExe}") - endif() - if (MSVC_IDE) - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileLogPath) - else() - file (RELATIVE_PATH _prefixFileLogPath "${CMAKE_BINARY_DIR}" "${_prefixFile}") - endif() - get_filename_component(_prefixFileExt "${_prefixFile}" EXT) - if (_prefixFileExt MATCHES "^\\.c") - set (_comment "Generating ${_language} prefix source ${_prefixFileLogPath}") - else() - set (_comment "Generating ${_language} prefix header ${_prefixFileLogPath}") - endif() - # prevent pre-processing errors upon generating the prefix header when a target's generated include file does not yet exist - # we do not add a file-level dependency for the target's generated files though, because we only want to depend on their existence - # thus we make the prefix header generation depend on a custom helper target which triggers the generation of the files - set (_preTargetName "${_target}${COTIRE_PCH_TARGET_SUFFIX}_pre") - if (TARGET ${_preTargetName}) - # custom helper target has already been generated while processing a different language - list (APPEND _dependencySources ${_preTargetName}) - else() - get_target_property(_targetSourceFiles ${_target} SOURCES) - cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) - if (_generatedSources) - add_custom_target("${_preTargetName}" DEPENDS ${_generatedSources}) - cotire_init_target("${_preTargetName}") - list (APPEND _dependencySources ${_preTargetName}) - endif() - endif() - add_custom_command( - OUTPUT "${_prefixFile}" "${_prefixFile}.log" - COMMAND ${_prefixCmd} - DEPENDS ${_unityFiles} ${_dependencySources} "${_realCompilerExe}" - COMMENT "${_comment}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - VERBATIM) - list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_setup_prefix_generation_from_unity_command _language _target _targetScript _prefixFile _unityFiles _cmdsVar) - set (_sourceFiles ${ARGN}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") - # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma - cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) - else() - set (_prefixSourceFile "${_prefixFile}") - endif() - cotire_setup_prefix_generation_command( - ${_language} ${_target} "${_targetScript}" - "${_prefixSourceFile}" "${_unityFiles}" ${_cmdsVar} ${_sourceFiles}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") - # set up generation of a prefix source file which includes the prefix header - cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) - endif() - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_setup_prefix_generation_from_provided_command _language _target _targetScript _prefixFile _cmdsVar) - set (_prefixHeaderFiles ${ARGN}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") - # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma - cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) - else() - set (_prefixSourceFile "${_prefixFile}") - endif() - cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixSourceFile}" _cmds ${_prefixHeaderFiles}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") - # set up generation of a prefix source file which includes the prefix header - cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) - endif() - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_init_cotire_target_properties _target) - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER TRUE) - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD TRUE) - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN FALSE) - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_SOURCE_DIR}") - cotire_check_is_path_relative_to("${CMAKE_BINARY_DIR}" _isRelative "${CMAKE_SOURCE_DIR}") - if (NOT _isRelative) - set_property(TARGET ${_target} APPEND PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_BINARY_DIR}") - endif() - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH "") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH "") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS "") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS "") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT "COPY_UNITY") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES SET) - if (NOT _isSet) - if (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES}") - else() - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "") - endif() - endif() -endfunction() - -function (cotire_make_target_message _target _languages _disableMsg _targetMsgVar) - get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) - get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) - string (REPLACE ";" " " _languagesStr "${_languages}") - math (EXPR _numberOfExcludedFiles "${ARGC} - 4") - if (_numberOfExcludedFiles EQUAL 0) - set (_excludedStr "") - elseif (COTIRE_VERBOSE OR _numberOfExcludedFiles LESS 4) - string (REPLACE ";" ", " _excludedStr "excluding ${ARGN}") - else() - set (_excludedStr "excluding ${_numberOfExcludedFiles} files") - endif() - set (_targetMsg "") - if (NOT _languages) - set (_targetMsg "Target ${_target} cannot be cotired.") - if (_disableMsg) - set (_targetMsg "${_targetMsg} ${_disableMsg}") - endif() - elseif (NOT _targetUsePCH AND NOT _targetAddSCU) - set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build and precompiled header.") - if (_disableMsg) - set (_targetMsg "${_targetMsg} ${_disableMsg}") - endif() - elseif (NOT _targetUsePCH) - if (_excludedStr) - set (_targetMsg "${_languagesStr} target ${_target} cotired without precompiled header ${_excludedStr}.") - else() - set (_targetMsg "${_languagesStr} target ${_target} cotired without precompiled header.") - endif() - if (_disableMsg) - set (_targetMsg "${_targetMsg} ${_disableMsg}") - endif() - elseif (NOT _targetAddSCU) - if (_excludedStr) - set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build ${_excludedStr}.") - else() - set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build.") - endif() - if (_disableMsg) - set (_targetMsg "${_targetMsg} ${_disableMsg}") - endif() - else() - if (_excludedStr) - set (_targetMsg "${_languagesStr} target ${_target} cotired ${_excludedStr}.") - else() - set (_targetMsg "${_languagesStr} target ${_target} cotired.") - endif() - endif() - set (${_targetMsgVar} "${_targetMsg}" PARENT_SCOPE) -endfunction() - -function (cotire_choose_target_languages _target _targetLanguagesVar _wholeTargetVar) - set (_languages ${ARGN}) - set (_allSourceFiles "") - set (_allExcludedSourceFiles "") - set (_allCotiredSourceFiles "") - set (_targetLanguages "") - set (_pchEligibleTargetLanguages "") - get_target_property(_targetType ${_target} TYPE) - get_target_property(_targetSourceFiles ${_target} SOURCES) - get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) - get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) - set (_disableMsg "") - foreach (_language ${_languages}) - get_target_property(_prefixHeader ${_target} COTIRE_${_language}_PREFIX_HEADER) - get_target_property(_unityBuildFile ${_target} COTIRE_${_language}_UNITY_SOURCE) - if (_prefixHeader OR _unityBuildFile) - message (STATUS "cotire: target ${_target} has already been cotired.") - set (${_targetLanguagesVar} "" PARENT_SCOPE) - return() - endif() - if (_targetUsePCH AND "${_language}" MATCHES "^C|CXX$" AND DEFINED CMAKE_${_language}_COMPILER_ID) - if (CMAKE_${_language}_COMPILER_ID) - cotire_check_precompiled_header_support("${_language}" "${_target}" _disableMsg) - if (_disableMsg) - set (_targetUsePCH FALSE) - endif() - endif() - endif() - set (_sourceFiles "") - set (_excludedSources "") - set (_cotiredSources "") - cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) - if (_sourceFiles OR _excludedSources OR _cotiredSources) - list (APPEND _targetLanguages ${_language}) - endif() - if (_sourceFiles) - list (APPEND _allSourceFiles ${_sourceFiles}) - endif() - list (LENGTH _sourceFiles _numberOfSources) - if (NOT _numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) - list (APPEND _pchEligibleTargetLanguages ${_language}) - endif() - if (_excludedSources) - list (APPEND _allExcludedSourceFiles ${_excludedSources}) - endif() - if (_cotiredSources) - list (APPEND _allCotiredSourceFiles ${_cotiredSources}) - endif() - endforeach() - set (_targetMsgLevel STATUS) - if (NOT _targetLanguages) - string (REPLACE ";" " or " _languagesStr "${_languages}") - set (_disableMsg "No ${_languagesStr} source files.") - set (_targetUsePCH FALSE) - set (_targetAddSCU FALSE) - endif() - if (_targetUsePCH) - if (_allCotiredSourceFiles) - cotire_get_source_file_property_values(_cotireTargets COTIRE_TARGET ${_allCotiredSourceFiles}) - list (REMOVE_DUPLICATES _cotireTargets) - string (REPLACE ";" ", " _cotireTargetsStr "${_cotireTargets}") - set (_disableMsg "Target sources already include a precompiled header for target(s) ${_cotireTargets}.") - set (_disableMsg "${_disableMsg} Set target property COTIRE_ENABLE_PRECOMPILED_HEADER to FALSE for targets ${_target},") - set (_disableMsg "${_disableMsg} ${_cotireTargetsStr} to get a workable build system.") - set (_targetMsgLevel SEND_ERROR) - set (_targetUsePCH FALSE) - elseif (NOT _pchEligibleTargetLanguages) - set (_disableMsg "Too few applicable sources.") - set (_targetUsePCH FALSE) - elseif (XCODE AND _allExcludedSourceFiles) - # for Xcode, we cannot apply the precompiled header to individual sources, only to the whole target - set (_disableMsg "Exclusion of source files not supported for generator Xcode.") - set (_targetUsePCH FALSE) - elseif (XCODE AND "${_targetType}" STREQUAL "OBJECT_LIBRARY") - # for Xcode, we cannot apply the required PRE_BUILD action to generate the prefix header to an OBJECT_LIBRARY target - set (_disableMsg "Required PRE_BUILD action not supported for OBJECT_LIBRARY targets for generator Xcode.") - set (_targetUsePCH FALSE) - endif() - endif() - if (_targetAddSCU) - # disable unity builds if automatic Qt processing is used - get_target_property(_targetAutoMoc ${_target} AUTOMOC) - get_target_property(_targetAutoUic ${_target} AUTOUIC) - get_target_property(_targetAutoRcc ${_target} AUTORCC) - if (_targetAutoMoc OR _targetAutoUic OR _targetAutoRcc) - if (_disableMsg) - set (_disableMsg "${_disableMsg} Target uses automatic CMake Qt processing.") - else() - set (_disableMsg "Target uses automatic CMake Qt processing.") - endif() - set (_targetAddSCU FALSE) - endif() - endif() - set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER ${_targetUsePCH}) - set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD ${_targetAddSCU}) - cotire_make_target_message(${_target} "${_targetLanguages}" "${_disableMsg}" _targetMsg ${_allExcludedSourceFiles}) - if (_targetMsg) - if (NOT DEFINED COTIREMSG_${_target}) - set (COTIREMSG_${_target} "") - endif() - if (COTIRE_VERBOSE OR NOT "${_targetMsgLevel}" STREQUAL "STATUS" OR - NOT "${COTIREMSG_${_target}}" STREQUAL "${_targetMsg}") - # cache message to avoid redundant messages on re-configure - set (COTIREMSG_${_target} "${_targetMsg}" CACHE INTERNAL "${_target} cotire message.") - message (${_targetMsgLevel} "${_targetMsg}") - endif() - endif() - list (LENGTH _targetLanguages _numberOfLanguages) - if (_numberOfLanguages GREATER 1 OR _allExcludedSourceFiles) - set (${_wholeTargetVar} FALSE PARENT_SCOPE) - else() - set (${_wholeTargetVar} TRUE PARENT_SCOPE) - endif() - set (${_targetLanguagesVar} ${_targetLanguages} PARENT_SCOPE) -endfunction() - -function (cotire_compute_unity_max_number_of_includes _target _maxIncludesVar) - set (_sourceFiles ${ARGN}) - get_target_property(_maxIncludes ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES) - if (_maxIncludes MATCHES "(-j|--parallel|--jobs) ?([0-9]*)") - if (DEFINED CMAKE_MATCH_2) - set (_numberOfThreads "${CMAKE_MATCH_2}") - else() - set (_numberOfThreads "") - endif() - if (NOT _numberOfThreads) - # use all available cores - ProcessorCount(_numberOfThreads) - endif() - list (LENGTH _sourceFiles _numberOfSources) - math (EXPR _maxIncludes "(${_numberOfSources} + ${_numberOfThreads} - 1) / ${_numberOfThreads}") - elseif (NOT _maxIncludes MATCHES "[0-9]+") - set (_maxIncludes 0) - endif() - if (COTIRE_DEBUG) - message (STATUS "${_target} unity source max includes: ${_maxIncludes}") - endif() - set (${_maxIncludesVar} ${_maxIncludes} PARENT_SCOPE) -endfunction() - -function (cotire_process_target_language _language _configurations _target _wholeTarget _cmdsVar) - set (${_cmdsVar} "" PARENT_SCOPE) - get_target_property(_targetSourceFiles ${_target} SOURCES) - set (_sourceFiles "") - set (_excludedSources "") - set (_cotiredSources "") - cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) - if (NOT _sourceFiles AND NOT _cotiredSources) - return() - endif() - set (_cmds "") - # check for user provided unity source file list - get_property(_unitySourceFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE_INIT) - if (NOT _unitySourceFiles) - set (_unitySourceFiles ${_sourceFiles} ${_cotiredSources}) - endif() - cotire_generate_target_script( - ${_language} "${_configurations}" ${_target} _targetScript _targetConfigScript ${_unitySourceFiles}) - # set up unity files for parallel compilation - cotire_compute_unity_max_number_of_includes(${_target} _maxIncludes ${_unitySourceFiles}) - cotire_make_unity_source_file_paths(${_language} ${_target} ${_maxIncludes} _unityFiles ${_unitySourceFiles}) - list (LENGTH _unityFiles _numberOfUnityFiles) - if (_numberOfUnityFiles EQUAL 0) - return() - elseif (_numberOfUnityFiles GREATER 1) - cotire_setup_unity_generation_commands( - ${_language} ${_target} "${_targetScript}" "${_targetConfigScript}" "${_unityFiles}" _cmds ${_unitySourceFiles}) - endif() - # set up single unity file for prefix header generation - cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFile) - cotire_setup_unity_generation_commands( - ${_language} ${_target} "${_targetScript}" "${_targetConfigScript}" "${_unityFile}" _cmds ${_unitySourceFiles}) - cotire_make_prefix_file_path(${_language} ${_target} _prefixFile) - # set up prefix header - if (_prefixFile) - # check for user provided prefix header files - get_property(_prefixHeaderFiles TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT) - if (_prefixHeaderFiles) - cotire_setup_prefix_generation_from_provided_command( - ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" _cmds ${_prefixHeaderFiles}) - else() - cotire_setup_prefix_generation_from_unity_command( - ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_unityFile}" _cmds ${_unitySourceFiles}) - endif() - # check if selected language has enough sources at all - list (LENGTH _sourceFiles _numberOfSources) - if (_numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) - set (_targetUsePCH FALSE) - else() - get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) - endif() - if (_targetUsePCH) - cotire_make_pch_file_path(${_language} ${_target} _pchFile) - if (_pchFile) - # first file in _sourceFiles is passed as the host file - cotire_setup_pch_file_compilation( - ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) - cotire_setup_pch_file_inclusion( - ${_language} ${_target} ${_wholeTarget} "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) - endif() - elseif (_prefixHeaderFiles) - # user provided prefix header must be included unconditionally - cotire_setup_prefix_file_inclusion(${_language} ${_target} "${_prefixFile}" ${_sourceFiles}) - endif() - endif() - # mark target as cotired for language - set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE "${_unityFiles}") - if (_prefixFile) - set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER "${_prefixFile}") - if (_targetUsePCH AND _pchFile) - set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER "${_pchFile}") - endif() - endif() - set (${_cmdsVar} ${_cmds} PARENT_SCOPE) -endfunction() - -function (cotire_setup_clean_target _target) - set (_cleanTargetName "${_target}${COTIRE_CLEAN_TARGET_SUFFIX}") - if (NOT TARGET "${_cleanTargetName}") - cotire_set_cmd_to_prologue(_cmds) - get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}" ABSOLUTE) - list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${_outputDir}" "${COTIRE_INTDIR}" "${_target}") - add_custom_target(${_cleanTargetName} - COMMAND ${_cmds} - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - COMMENT "Cleaning up target ${_target} cotire generated files" - VERBATIM) - cotire_init_target("${_cleanTargetName}") - endif() -endfunction() - -function (cotire_setup_pch_target _languages _configurations _target) - if ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - # for makefile based generators, we add a custom target to trigger the generation of the cotire related files - set (_dependsFiles "") - foreach (_language ${_languages}) - set (_props COTIRE_${_language}_PREFIX_HEADER COTIRE_${_language}_UNITY_SOURCE) - if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel" AND NOT - (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "Clang")) - # MSVC, Intel and clang-cl only create precompiled header as a side effect - list (INSERT _props 0 COTIRE_${_language}_PRECOMPILED_HEADER) - endif() - cotire_get_first_set_property_value(_dependsFile TARGET ${_target} ${_props}) - if (_dependsFile) - list (APPEND _dependsFiles "${_dependsFile}") - endif() - endforeach() - if (_dependsFiles) - set (_pchTargetName "${_target}${COTIRE_PCH_TARGET_SUFFIX}") - add_custom_target("${_pchTargetName}" DEPENDS ${_dependsFiles}) - cotire_init_target("${_pchTargetName}") - cotire_add_to_pch_all_target(${_pchTargetName}) - endif() - else() - # for other generators, we add the "clean all" target to clean up the precompiled header - cotire_setup_clean_all_target() - endif() -endfunction() - -function (cotire_filter_object_libraries _target _objectLibrariesVar) - set (_objectLibraries "") - foreach (_source ${ARGN}) - if (_source MATCHES "^\\$$") - list (APPEND _objectLibraries "${_source}") - endif() - endforeach() - set (${_objectLibrariesVar} ${_objectLibraries} PARENT_SCOPE) -endfunction() - -function (cotire_collect_unity_target_sources _target _languages _unityTargetSourcesVar) - get_target_property(_targetSourceFiles ${_target} SOURCES) - set (_unityTargetSources ${_targetSourceFiles}) - foreach (_language ${_languages}) - get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) - if (_unityFiles) - # remove source files that are included in the unity source - set (_sourceFiles "") - set (_excludedSources "") - set (_cotiredSources "") - cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) - if (_sourceFiles OR _cotiredSources) - list (REMOVE_ITEM _unityTargetSources ${_sourceFiles} ${_cotiredSources}) - endif() - # add unity source files instead - list (APPEND _unityTargetSources ${_unityFiles}) - endif() - endforeach() - # handle object libraries which are part of the target's sources - get_target_property(_linkLibrariesStrategy ${_target} COTIRE_UNITY_LINK_LIBRARIES_INIT) - if ("${_linkLibrariesStrategy}" MATCHES "^COPY_UNITY$") - cotire_filter_object_libraries(${_target} _objectLibraries ${_targetSourceFiles}) - if (_objectLibraries) - cotire_map_libraries("${_linkLibrariesStrategy}" _unityObjectLibraries ${_objectLibraries}) - list (REMOVE_ITEM _unityTargetSources ${_objectLibraries}) - list (APPEND _unityTargetSources ${_unityObjectLibraries}) - endif() - endif() - set (${_unityTargetSourcesVar} ${_unityTargetSources} PARENT_SCOPE) -endfunction() - -function (cotire_setup_unity_target_pch_usage _languages _target) - foreach (_language ${_languages}) - get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) - if (_unityFiles) - get_property(_userPrefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT) - get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) - if (_userPrefixFile AND _prefixFile) - # user provided prefix header must be included unconditionally by unity sources - cotire_setup_prefix_file_inclusion(${_language} ${_target} "${_prefixFile}" ${_unityFiles}) - endif() - endif() - endforeach() -endfunction() - -function (cotire_setup_unity_build_target _languages _configurations _target) - get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) - if (NOT _unityTargetName) - set (_unityTargetName "${_target}${COTIRE_UNITY_BUILD_TARGET_SUFFIX}") - endif() - # determine unity target sub type - get_target_property(_targetType ${_target} TYPE) - if ("${_targetType}" STREQUAL "EXECUTABLE") - set (_unityTargetSubType "") - elseif (_targetType MATCHES "(STATIC|SHARED|MODULE|OBJECT)_LIBRARY") - set (_unityTargetSubType "${CMAKE_MATCH_1}") - else() - message (WARNING "cotire: target ${_target} has unknown target type ${_targetType}.") - return() - endif() - # determine unity target sources - set (_unityTargetSources "") - cotire_collect_unity_target_sources(${_target} "${_languages}" _unityTargetSources) - # prevent AUTOMOC, AUTOUIC and AUTORCC properties from being set when the unity target is created - set (CMAKE_AUTOMOC OFF) - set (CMAKE_AUTOUIC OFF) - set (CMAKE_AUTORCC OFF) - if (COTIRE_DEBUG) - message (STATUS "add target ${_targetType} ${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}") - endif() - # generate unity target - if ("${_targetType}" STREQUAL "EXECUTABLE") - add_executable(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}) - else() - add_library(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}) - endif() - # copy output location properties - set (_outputDirProperties - ARCHIVE_OUTPUT_DIRECTORY ARCHIVE_OUTPUT_DIRECTORY_ - LIBRARY_OUTPUT_DIRECTORY LIBRARY_OUTPUT_DIRECTORY_ - RUNTIME_OUTPUT_DIRECTORY RUNTIME_OUTPUT_DIRECTORY_) - if (COTIRE_UNITY_OUTPUT_DIRECTORY) - set (_setDefaultOutputDir TRUE) - if (IS_ABSOLUTE "${COTIRE_UNITY_OUTPUT_DIRECTORY}") - set (_outputDir "${COTIRE_UNITY_OUTPUT_DIRECTORY}") - else() - # append relative COTIRE_UNITY_OUTPUT_DIRECTORY to target's actual output directory - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} ${_outputDirProperties}) - cotire_resolve_config_properties("${_configurations}" _properties ${_outputDirProperties}) - foreach (_property ${_properties}) - get_property(_outputDir TARGET ${_target} PROPERTY ${_property}) - if (_outputDir) - get_filename_component(_outputDir "${_outputDir}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE) - set_property(TARGET ${_unityTargetName} PROPERTY ${_property} "${_outputDir}") - set (_setDefaultOutputDir FALSE) - endif() - endforeach() - if (_setDefaultOutputDir) - get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE) - endif() - endif() - if (_setDefaultOutputDir) - set_target_properties(${_unityTargetName} PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY "${_outputDir}" - LIBRARY_OUTPUT_DIRECTORY "${_outputDir}" - RUNTIME_OUTPUT_DIRECTORY "${_outputDir}") - endif() - else() - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - ${_outputDirProperties}) - endif() - # copy output name - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - ARCHIVE_OUTPUT_NAME ARCHIVE_OUTPUT_NAME_ - LIBRARY_OUTPUT_NAME LIBRARY_OUTPUT_NAME_ - OUTPUT_NAME OUTPUT_NAME_ - RUNTIME_OUTPUT_NAME RUNTIME_OUTPUT_NAME_ - PREFIX _POSTFIX SUFFIX - IMPORT_PREFIX IMPORT_SUFFIX) - # copy compile stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - COMPILE_DEFINITIONS COMPILE_DEFINITIONS_ - COMPILE_FLAGS COMPILE_OPTIONS - Fortran_FORMAT Fortran_MODULE_DIRECTORY - INCLUDE_DIRECTORIES - INTERPROCEDURAL_OPTIMIZATION INTERPROCEDURAL_OPTIMIZATION_ - POSITION_INDEPENDENT_CODE - C_COMPILER_LAUNCHER CXX_COMPILER_LAUNCHER - C_INCLUDE_WHAT_YOU_USE CXX_INCLUDE_WHAT_YOU_USE - C_VISIBILITY_PRESET CXX_VISIBILITY_PRESET VISIBILITY_INLINES_HIDDEN - C_CLANG_TIDY CXX_CLANG_TIDY) - # copy compile features - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - C_EXTENSIONS C_STANDARD C_STANDARD_REQUIRED - CXX_EXTENSIONS CXX_STANDARD CXX_STANDARD_REQUIRED - COMPILE_FEATURES) - # copy interface stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - COMPATIBLE_INTERFACE_BOOL COMPATIBLE_INTERFACE_NUMBER_MAX COMPATIBLE_INTERFACE_NUMBER_MIN - COMPATIBLE_INTERFACE_STRING - INTERFACE_COMPILE_DEFINITIONS INTERFACE_COMPILE_FEATURES INTERFACE_COMPILE_OPTIONS - INTERFACE_INCLUDE_DIRECTORIES INTERFACE_SOURCES - INTERFACE_POSITION_INDEPENDENT_CODE INTERFACE_SYSTEM_INCLUDE_DIRECTORIES - INTERFACE_AUTOUIC_OPTIONS NO_SYSTEM_FROM_IMPORTED) - # copy link stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - BUILD_WITH_INSTALL_RPATH BUILD_WITH_INSTALL_NAME_DIR - INSTALL_RPATH INSTALL_RPATH_USE_LINK_PATH SKIP_BUILD_RPATH - LINKER_LANGUAGE LINK_DEPENDS LINK_DEPENDS_NO_SHARED - LINK_FLAGS LINK_FLAGS_ - LINK_INTERFACE_LIBRARIES LINK_INTERFACE_LIBRARIES_ - LINK_INTERFACE_MULTIPLICITY LINK_INTERFACE_MULTIPLICITY_ - LINK_SEARCH_START_STATIC LINK_SEARCH_END_STATIC - STATIC_LIBRARY_FLAGS STATIC_LIBRARY_FLAGS_ - NO_SONAME SOVERSION VERSION - LINK_WHAT_YOU_USE BUILD_RPATH) - # copy cmake stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - IMPLICIT_DEPENDS_INCLUDE_TRANSFORM RULE_LAUNCH_COMPILE RULE_LAUNCH_CUSTOM RULE_LAUNCH_LINK) - # copy Apple platform specific stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - BUNDLE BUNDLE_EXTENSION FRAMEWORK FRAMEWORK_VERSION INSTALL_NAME_DIR - MACOSX_BUNDLE MACOSX_BUNDLE_INFO_PLIST MACOSX_FRAMEWORK_INFO_PLIST MACOSX_RPATH - OSX_ARCHITECTURES OSX_ARCHITECTURES_ PRIVATE_HEADER PUBLIC_HEADER RESOURCE XCTEST - IOS_INSTALL_COMBINED XCODE_EXPLICIT_FILE_TYPE XCODE_PRODUCT_TYPE) - # copy Windows platform specific stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - GNUtoMS - COMPILE_PDB_NAME COMPILE_PDB_NAME_ - COMPILE_PDB_OUTPUT_DIRECTORY COMPILE_PDB_OUTPUT_DIRECTORY_ - PDB_NAME PDB_NAME_ PDB_OUTPUT_DIRECTORY PDB_OUTPUT_DIRECTORY_ - VS_DESKTOP_EXTENSIONS_VERSION VS_DOTNET_REFERENCES VS_DOTNET_TARGET_FRAMEWORK_VERSION - VS_GLOBAL_KEYWORD VS_GLOBAL_PROJECT_TYPES VS_GLOBAL_ROOTNAMESPACE - VS_IOT_EXTENSIONS_VERSION VS_IOT_STARTUP_TASK - VS_KEYWORD VS_MOBILE_EXTENSIONS_VERSION - VS_SCC_AUXPATH VS_SCC_LOCALPATH VS_SCC_PROJECTNAME VS_SCC_PROVIDER - VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION - VS_WINRT_COMPONENT VS_WINRT_EXTENSIONS VS_WINRT_REFERENCES - WIN32_EXECUTABLE WINDOWS_EXPORT_ALL_SYMBOLS - DEPLOYMENT_REMOTE_DIRECTORY VS_CONFIGURATION_TYPE - VS_SDK_REFERENCES VS_USER_PROPS VS_DEBUGGER_WORKING_DIRECTORY) - # copy Android platform specific stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - ANDROID_API ANDROID_API_MIN ANDROID_GUI - ANDROID_ANT_ADDITIONAL_OPTIONS ANDROID_ARCH ANDROID_ASSETS_DIRECTORIES - ANDROID_JAR_DEPENDENCIES ANDROID_JAR_DIRECTORIES ANDROID_JAVA_SOURCE_DIR - ANDROID_NATIVE_LIB_DEPENDENCIES ANDROID_NATIVE_LIB_DIRECTORIES - ANDROID_PROCESS_MAX ANDROID_PROGUARD ANDROID_PROGUARD_CONFIG_PATH - ANDROID_SECURE_PROPS_PATH ANDROID_SKIP_ANT_STEP ANDROID_STL_TYPE) - # copy CUDA platform specific stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - CUDA_PTX_COMPILATION CUDA_SEPARABLE_COMPILATION CUDA_RESOLVE_DEVICE_SYMBOLS - CUDA_EXTENSIONS CUDA_STANDARD CUDA_STANDARD_REQUIRED) - # use output name from original target - get_target_property(_targetOutputName ${_unityTargetName} OUTPUT_NAME) - if (NOT _targetOutputName) - set_property(TARGET ${_unityTargetName} PROPERTY OUTPUT_NAME "${_target}") - endif() - # use export symbol from original target - cotire_get_target_export_symbol("${_target}" _defineSymbol) - if (_defineSymbol) - set_property(TARGET ${_unityTargetName} PROPERTY DEFINE_SYMBOL "${_defineSymbol}") - if ("${_targetType}" STREQUAL "EXECUTABLE") - set_property(TARGET ${_unityTargetName} PROPERTY ENABLE_EXPORTS TRUE) - endif() - endif() - # enable parallel compilation for MSVC - if (MSVC AND "${CMAKE_GENERATOR}" MATCHES "Visual Studio") - list (LENGTH _unityTargetSources _numberOfUnityTargetSources) - if (_numberOfUnityTargetSources GREATER 1) - set_property(TARGET ${_unityTargetName} APPEND PROPERTY COMPILE_OPTIONS "/MP") - endif() - endif() - cotire_init_target(${_unityTargetName}) - cotire_add_to_unity_all_target(${_unityTargetName}) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_TARGET_NAME "${_unityTargetName}") -endfunction(cotire_setup_unity_build_target) - -function (cotire_target _target) - set(_options "") - set(_oneValueArgs "") - set(_multiValueArgs LANGUAGES CONFIGURATIONS) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if (NOT _option_LANGUAGES) - get_property (_option_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) - endif() - if (NOT _option_CONFIGURATIONS) - cotire_get_configuration_types(_option_CONFIGURATIONS) - endif() - # check if cotire can be applied to target at all - cotire_is_target_supported(${_target} _isSupported) - if (NOT _isSupported) - get_target_property(_imported ${_target} IMPORTED) - get_target_property(_targetType ${_target} TYPE) - if (_imported) - message (WARNING "cotire: imported ${_targetType} target ${_target} cannot be cotired.") - else() - message (STATUS "cotire: ${_targetType} target ${_target} cannot be cotired.") - endif() - return() - endif() - # resolve alias - get_target_property(_aliasName ${_target} ALIASED_TARGET) - if (_aliasName) - if (COTIRE_DEBUG) - message (STATUS "${_target} is an alias. Applying cotire to aliased target ${_aliasName} instead.") - endif() - set (_target ${_aliasName}) - endif() - # check if target needs to be cotired for build type - # when using configuration types, the test is performed at build time - cotire_init_cotire_target_properties(${_target}) - if (NOT CMAKE_CONFIGURATION_TYPES) - if (CMAKE_BUILD_TYPE) - list (FIND _option_CONFIGURATIONS "${CMAKE_BUILD_TYPE}" _index) - else() - list (FIND _option_CONFIGURATIONS "None" _index) - endif() - if (_index EQUAL -1) - if (COTIRE_DEBUG) - message (STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} not cotired (${_option_CONFIGURATIONS})") - endif() - return() - endif() - endif() - # when not using configuration types, immediately create cotire intermediate dir - if (NOT CMAKE_CONFIGURATION_TYPES) - cotire_get_intermediate_dir(_baseDir) - file (MAKE_DIRECTORY "${_baseDir}") - endif() - # choose languages that apply to the target - cotire_choose_target_languages("${_target}" _targetLanguages _wholeTarget ${_option_LANGUAGES}) - if (NOT _targetLanguages) - return() - endif() - set (_cmds "") - foreach (_language ${_targetLanguages}) - cotire_process_target_language("${_language}" "${_option_CONFIGURATIONS}" ${_target} ${_wholeTarget} _cmd) - if (_cmd) - list (APPEND _cmds ${_cmd}) - endif() - endforeach() - get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) - if (_targetAddSCU) - cotire_setup_unity_build_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) - endif() - get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) - if (_targetUsePCH) - cotire_setup_target_pch_usage("${_targetLanguages}" ${_target} ${_wholeTarget} ${_cmds}) - cotire_setup_pch_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) - if (_targetAddSCU) - cotire_setup_unity_target_pch_usage("${_targetLanguages}" ${_target}) - endif() - endif() - get_target_property(_targetAddCleanTarget ${_target} COTIRE_ADD_CLEAN) - if (_targetAddCleanTarget) - cotire_setup_clean_target(${_target}) - endif() -endfunction(cotire_target) - -function (cotire_map_libraries _strategy _mappedLibrariesVar) - set (_mappedLibraries "") - foreach (_library ${ARGN}) - if (_library MATCHES "^\\$$") - set (_libraryName "${CMAKE_MATCH_1}") - set (_linkOnly TRUE) - set (_objectLibrary FALSE) - elseif (_library MATCHES "^\\$$") - set (_libraryName "${CMAKE_MATCH_1}") - set (_linkOnly FALSE) - set (_objectLibrary TRUE) - else() - set (_libraryName "${_library}") - set (_linkOnly FALSE) - set (_objectLibrary FALSE) - endif() - if ("${_strategy}" MATCHES "COPY_UNITY") - cotire_is_target_supported(${_libraryName} _isSupported) - if (_isSupported) - # use target's corresponding unity target, if available - get_target_property(_libraryUnityTargetName ${_libraryName} COTIRE_UNITY_TARGET_NAME) - if (TARGET "${_libraryUnityTargetName}") - if (_linkOnly) - list (APPEND _mappedLibraries "$") - elseif (_objectLibrary) - list (APPEND _mappedLibraries "$") - else() - list (APPEND _mappedLibraries "${_libraryUnityTargetName}") - endif() - else() - list (APPEND _mappedLibraries "${_library}") - endif() - else() - list (APPEND _mappedLibraries "${_library}") - endif() - else() - list (APPEND _mappedLibraries "${_library}") - endif() - endforeach() - list (REMOVE_DUPLICATES _mappedLibraries) - set (${_mappedLibrariesVar} ${_mappedLibraries} PARENT_SCOPE) -endfunction() - -function (cotire_target_link_libraries _target) - cotire_is_target_supported(${_target} _isSupported) - if (NOT _isSupported) - return() - endif() - get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) - if (TARGET "${_unityTargetName}") - get_target_property(_linkLibrariesStrategy ${_target} COTIRE_UNITY_LINK_LIBRARIES_INIT) - if (COTIRE_DEBUG) - message (STATUS "unity target ${_unityTargetName} link strategy: ${_linkLibrariesStrategy}") - endif() - if ("${_linkLibrariesStrategy}" MATCHES "^(COPY|COPY_UNITY)$") - get_target_property(_linkLibraries ${_target} LINK_LIBRARIES) - if (_linkLibraries) - cotire_map_libraries("${_linkLibrariesStrategy}" _unityLinkLibraries ${_linkLibraries}) - set_target_properties(${_unityTargetName} PROPERTIES LINK_LIBRARIES "${_unityLinkLibraries}") - if (COTIRE_DEBUG) - message (STATUS "unity target ${_unityTargetName} link libraries: ${_unityLinkLibraries}") - endif() - endif() - get_target_property(_interfaceLinkLibraries ${_target} INTERFACE_LINK_LIBRARIES) - if (_interfaceLinkLibraries) - cotire_map_libraries("${_linkLibrariesStrategy}" _unityLinkInterfaceLibraries ${_interfaceLinkLibraries}) - set_target_properties(${_unityTargetName} PROPERTIES INTERFACE_LINK_LIBRARIES "${_unityLinkInterfaceLibraries}") - if (COTIRE_DEBUG) - message (STATUS "unity target ${_unityTargetName} interface link libraries: ${_unityLinkInterfaceLibraries}") - endif() - endif() - get_target_property(_manualDependencies ${_target} MANUALLY_ADDED_DEPENDENCIES) - if (_manualDependencies) - cotire_map_libraries("${_linkLibrariesStrategy}" _unityManualDependencies ${_manualDependencies}) - if (_unityManualDependencies) - add_dependencies("${_unityTargetName}" ${_unityManualDependencies}) - endif() - endif() - endif() - endif() -endfunction(cotire_target_link_libraries) - -function (cotire_cleanup _binaryDir _cotireIntermediateDirName _targetName) - if (_targetName) - file (GLOB_RECURSE _cotireFiles "${_binaryDir}/${_targetName}*.*") - else() - file (GLOB_RECURSE _cotireFiles "${_binaryDir}/*.*") - endif() - # filter files in intermediate directory - set (_filesToRemove "") - foreach (_file ${_cotireFiles}) - get_filename_component(_dir "${_file}" DIRECTORY) - get_filename_component(_dirName "${_dir}" NAME) - if ("${_dirName}" STREQUAL "${_cotireIntermediateDirName}") - list (APPEND _filesToRemove "${_file}") - endif() - endforeach() - if (_filesToRemove) - if (COTIRE_VERBOSE) - message (STATUS "cleaning up ${_filesToRemove}") - endif() - file (REMOVE ${_filesToRemove}) - endif() -endfunction() - -function (cotire_init_target _targetName) - if (COTIRE_TARGETS_FOLDER) - set_target_properties(${_targetName} PROPERTIES FOLDER "${COTIRE_TARGETS_FOLDER}") - endif() - set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_ALL TRUE) - if (MSVC_IDE) - set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE) - endif() -endfunction() - -function (cotire_add_to_pch_all_target _pchTargetName) - set (_targetName "${COTIRE_PCH_ALL_TARGET_NAME}") - if (NOT TARGET "${_targetName}") - add_custom_target("${_targetName}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - VERBATIM) - cotire_init_target("${_targetName}") - endif() - cotire_setup_clean_all_target() - add_dependencies(${_targetName} ${_pchTargetName}) -endfunction() - -function (cotire_add_to_unity_all_target _unityTargetName) - set (_targetName "${COTIRE_UNITY_BUILD_ALL_TARGET_NAME}") - if (NOT TARGET "${_targetName}") - add_custom_target("${_targetName}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - VERBATIM) - cotire_init_target("${_targetName}") - endif() - cotire_setup_clean_all_target() - add_dependencies(${_targetName} ${_unityTargetName}) -endfunction() - -function (cotire_setup_clean_all_target) - set (_targetName "${COTIRE_CLEAN_ALL_TARGET_NAME}") - if (NOT TARGET "${_targetName}") - cotire_set_cmd_to_prologue(_cmds) - list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${CMAKE_BINARY_DIR}" "${COTIRE_INTDIR}") - add_custom_target(${_targetName} - COMMAND ${_cmds} - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - COMMENT "Cleaning up all cotire generated files" - VERBATIM) - cotire_init_target("${_targetName}") - endif() -endfunction() - -function (cotire) - set(_options "") - set(_oneValueArgs "") - set(_multiValueArgs LANGUAGES CONFIGURATIONS) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - set (_targets ${_option_UNPARSED_ARGUMENTS}) - foreach (_target ${_targets}) - if (TARGET ${_target}) - cotire_target(${_target} LANGUAGES ${_option_LANGUAGES} CONFIGURATIONS ${_option_CONFIGURATIONS}) - else() - message (WARNING "cotire: ${_target} is not a target.") - endif() - endforeach() - foreach (_target ${_targets}) - if (TARGET ${_target}) - cotire_target_link_libraries(${_target}) - endif() - endforeach() -endfunction() - -if (CMAKE_SCRIPT_MODE_FILE) - - # cotire is being run in script mode - # locate -P on command args - set (COTIRE_ARGC -1) - foreach (_index RANGE ${CMAKE_ARGC}) - if (COTIRE_ARGC GREATER -1) - set (COTIRE_ARGV${COTIRE_ARGC} "${CMAKE_ARGV${_index}}") - math (EXPR COTIRE_ARGC "${COTIRE_ARGC} + 1") - elseif ("${CMAKE_ARGV${_index}}" STREQUAL "-P") - set (COTIRE_ARGC 0) - endif() - endforeach() - - # include target script if available - if ("${COTIRE_ARGV2}" MATCHES "\\.cmake$") - # the included target scripts sets up additional variables relating to the target (e.g., COTIRE_TARGET_SOURCES) - include("${COTIRE_ARGV2}") - endif() - - if (COTIRE_DEBUG) - message (STATUS "${COTIRE_ARGV0} ${COTIRE_ARGV1} ${COTIRE_ARGV2} ${COTIRE_ARGV3} ${COTIRE_ARGV4} ${COTIRE_ARGV5}") - endif() - - if (NOT COTIRE_BUILD_TYPE) - set (COTIRE_BUILD_TYPE "None") - endif() - string (TOUPPER "${COTIRE_BUILD_TYPE}" _upperConfig) - set (_includeDirs ${COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig}}) - set (_systemIncludeDirs ${COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}}) - set (_compileDefinitions ${COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}}) - set (_compileFlags ${COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}}) - # check if target has been cotired for actual build type COTIRE_BUILD_TYPE - list (FIND COTIRE_TARGET_CONFIGURATION_TYPES "${COTIRE_BUILD_TYPE}" _index) - if (_index GREATER -1) - set (_sources ${COTIRE_TARGET_SOURCES}) - set (_sourcesDefinitions ${COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig}}) - else() - if (COTIRE_DEBUG) - message (STATUS "COTIRE_BUILD_TYPE=${COTIRE_BUILD_TYPE} not cotired (${COTIRE_TARGET_CONFIGURATION_TYPES})") - endif() - set (_sources "") - set (_sourcesDefinitions "") - endif() - set (_targetPreUndefs ${COTIRE_TARGET_PRE_UNDEFS}) - set (_targetPostUndefs ${COTIRE_TARGET_POST_UNDEFS}) - set (_sourcesPreUndefs ${COTIRE_TARGET_SOURCES_PRE_UNDEFS}) - set (_sourcesPostUndefs ${COTIRE_TARGET_SOURCES_POST_UNDEFS}) - - if ("${COTIRE_ARGV1}" STREQUAL "unity") - - if (XCODE) - # executing pre-build action under Xcode, check dependency on target script - set (_dependsOption DEPENDS "${COTIRE_ARGV2}") - else() - # executing custom command, no need to re-check for dependencies - set (_dependsOption "") - endif() - - cotire_select_unity_source_files("${COTIRE_ARGV3}" _sources ${_sources}) - - cotire_generate_unity_source( - "${COTIRE_ARGV3}" ${_sources} - LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - SOURCES_COMPILE_DEFINITIONS ${_sourcesDefinitions} - PRE_UNDEFS ${_targetPreUndefs} - POST_UNDEFS ${_targetPostUndefs} - SOURCES_PRE_UNDEFS ${_sourcesPreUndefs} - SOURCES_POST_UNDEFS ${_sourcesPostUndefs} - ${_dependsOption}) - - elseif ("${COTIRE_ARGV1}" STREQUAL "prefix") - - if (XCODE) - # executing pre-build action under Xcode, check dependency on unity file and prefix dependencies - set (_dependsOption DEPENDS "${COTIRE_ARGV4}" ${COTIRE_TARGET_PREFIX_DEPENDS}) - else() - # executing custom command, no need to re-check for dependencies - set (_dependsOption "") - endif() - - set (_files "") - foreach (_index RANGE 4 ${COTIRE_ARGC}) - if (COTIRE_ARGV${_index}) - list (APPEND _files "${COTIRE_ARGV${_index}}") - endif() - endforeach() - - cotire_generate_prefix_header( - "${COTIRE_ARGV3}" ${_files} - COMPILER_LAUNCHER "${COTIRE_TARGET_${COTIRE_TARGET_LANGUAGE}_COMPILER_LAUNCHER}" - COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" - COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} - COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" - COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" - LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - IGNORE_PATH "${COTIRE_TARGET_IGNORE_PATH};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH}" - INCLUDE_PATH ${COTIRE_TARGET_INCLUDE_PATH} - IGNORE_EXTENSIONS "${CMAKE_${COTIRE_TARGET_LANGUAGE}_SOURCE_FILE_EXTENSIONS};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS}" - INCLUDE_PRIORITY_PATH ${COTIRE_TARGET_INCLUDE_PRIORITY_PATH} - INCLUDE_DIRECTORIES ${_includeDirs} - SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} - COMPILE_DEFINITIONS ${_compileDefinitions} - COMPILE_FLAGS ${_compileFlags} - ${_dependsOption}) - - elseif ("${COTIRE_ARGV1}" STREQUAL "precompile") - - set (_files "") - foreach (_index RANGE 5 ${COTIRE_ARGC}) - if (COTIRE_ARGV${_index}) - list (APPEND _files "${COTIRE_ARGV${_index}}") - endif() - endforeach() - - cotire_precompile_prefix_header( - "${COTIRE_ARGV3}" "${COTIRE_ARGV4}" "${COTIRE_ARGV5}" - COMPILER_LAUNCHER "${COTIRE_TARGET_${COTIRE_TARGET_LANGUAGE}_COMPILER_LAUNCHER}" - COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" - COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} - COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" - COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" - LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - INCLUDE_DIRECTORIES ${_includeDirs} - SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} - COMPILE_DEFINITIONS ${_compileDefinitions} - COMPILE_FLAGS ${_compileFlags}) - - elseif ("${COTIRE_ARGV1}" STREQUAL "combine") - - if (COTIRE_TARGET_LANGUAGE) - set (_combinedFile "${COTIRE_ARGV3}") - set (_startIndex 4) - else() - set (_combinedFile "${COTIRE_ARGV2}") - set (_startIndex 3) - endif() - set (_files "") - foreach (_index RANGE ${_startIndex} ${COTIRE_ARGC}) - if (COTIRE_ARGV${_index}) - list (APPEND _files "${COTIRE_ARGV${_index}}") - endif() - endforeach() - - if (XCODE) - # executing pre-build action under Xcode, check dependency on files to be combined - set (_dependsOption DEPENDS ${_files}) - else() - # executing custom command, no need to re-check for dependencies - set (_dependsOption "") - endif() - - if (COTIRE_TARGET_LANGUAGE) - cotire_generate_unity_source( - "${_combinedFile}" ${_files} - LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - ${_dependsOption}) - else() - cotire_generate_unity_source("${_combinedFile}" ${_files} ${_dependsOption}) - endif() - - elseif ("${COTIRE_ARGV1}" STREQUAL "cleanup") - - cotire_cleanup("${COTIRE_ARGV2}" "${COTIRE_ARGV3}" "${COTIRE_ARGV4}") - - else() - message (FATAL_ERROR "cotire: unknown command \"${COTIRE_ARGV1}\".") - endif() - -else() - - # cotire is being run in include mode - # set up all variable and property definitions - - if (NOT DEFINED COTIRE_DEBUG_INIT) - if (DEFINED COTIRE_DEBUG) - set (COTIRE_DEBUG_INIT ${COTIRE_DEBUG}) - else() - set (COTIRE_DEBUG_INIT FALSE) - endif() - endif() - option (COTIRE_DEBUG "Enable cotire debugging output?" ${COTIRE_DEBUG_INIT}) - - if (NOT DEFINED COTIRE_VERBOSE_INIT) - if (DEFINED COTIRE_VERBOSE) - set (COTIRE_VERBOSE_INIT ${COTIRE_VERBOSE}) - else() - set (COTIRE_VERBOSE_INIT FALSE) - endif() - endif() - option (COTIRE_VERBOSE "Enable cotire verbose output?" ${COTIRE_VERBOSE_INIT}) - - set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS "inc;inl;ipp" CACHE STRING - "Ignore headers with the listed file extensions from the generated prefix header.") - - set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH "" CACHE STRING - "Ignore headers from these directories when generating the prefix header.") - - set (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS "m;mm" CACHE STRING - "Ignore sources with the listed file extensions from the generated unity source.") - - set (COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES "2" CACHE STRING - "Minimum number of sources in target required to enable use of precompiled header.") - - if (NOT DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT) - if (DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES) - set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT ${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES}) - elseif ("${CMAKE_GENERATOR}" MATCHES "JOM|Ninja|Visual Studio") - # enable parallelization for generators that run multiple jobs by default - set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "-j") - else() - set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "0") - endif() - endif() - set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT}" CACHE STRING - "Maximum number of source files to include in a single unity source file.") - - if (NOT COTIRE_PREFIX_HEADER_FILENAME_SUFFIX) - set (COTIRE_PREFIX_HEADER_FILENAME_SUFFIX "_prefix") - endif() - if (NOT COTIRE_UNITY_SOURCE_FILENAME_SUFFIX) - set (COTIRE_UNITY_SOURCE_FILENAME_SUFFIX "_unity") - endif() - if (NOT COTIRE_INTDIR) - set (COTIRE_INTDIR "cotire") - endif() - if (NOT COTIRE_PCH_ALL_TARGET_NAME) - set (COTIRE_PCH_ALL_TARGET_NAME "all_pch") - endif() - if (NOT COTIRE_UNITY_BUILD_ALL_TARGET_NAME) - set (COTIRE_UNITY_BUILD_ALL_TARGET_NAME "all_unity") - endif() - if (NOT COTIRE_CLEAN_ALL_TARGET_NAME) - set (COTIRE_CLEAN_ALL_TARGET_NAME "clean_cotire") - endif() - if (NOT COTIRE_CLEAN_TARGET_SUFFIX) - set (COTIRE_CLEAN_TARGET_SUFFIX "_clean_cotire") - endif() - if (NOT COTIRE_PCH_TARGET_SUFFIX) - set (COTIRE_PCH_TARGET_SUFFIX "_pch") - endif() - if (MSVC) - # MSVC default PCH memory scaling factor of 100 percent (75 MB) is too small for template heavy C++ code - # use a bigger default factor of 170 percent (128 MB) - if (NOT DEFINED COTIRE_PCH_MEMORY_SCALING_FACTOR) - set (COTIRE_PCH_MEMORY_SCALING_FACTOR "170") - endif() - endif() - if (NOT COTIRE_UNITY_BUILD_TARGET_SUFFIX) - set (COTIRE_UNITY_BUILD_TARGET_SUFFIX "_unity") - endif() - if (NOT DEFINED COTIRE_TARGETS_FOLDER) - set (COTIRE_TARGETS_FOLDER "cotire") - endif() - if (NOT DEFINED COTIRE_UNITY_OUTPUT_DIRECTORY) - if ("${CMAKE_GENERATOR}" MATCHES "Ninja") - # generated Ninja build files do not work if the unity target produces the same output file as the cotired target - set (COTIRE_UNITY_OUTPUT_DIRECTORY "unity") - else() - set (COTIRE_UNITY_OUTPUT_DIRECTORY "") - endif() - endif() - - # define cotire cache variables - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH" - BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." - FULL_DOCS - "The variable can be set to a semicolon separated list of include directories." - "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header." - "If not defined, defaults to empty list." - ) - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS" - BRIEF_DOCS "Ignore includes with the listed file extensions from the generated prefix header." - FULL_DOCS - "The variable can be set to a semicolon separated list of file extensions." - "If a header file extension matches one in the list, it will be excluded from the generated prefix header." - "Includes with an extension in CMAKE__SOURCE_FILE_EXTENSIONS are always ignored." - "If not defined, defaults to inc;inl;ipp." - ) - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS" - BRIEF_DOCS "Exclude sources with the listed file extensions from the generated unity source." - FULL_DOCS - "The variable can be set to a semicolon separated list of file extensions." - "If a source file extension matches one in the list, it will be excluded from the generated unity source file." - "Source files with an extension in CMAKE__IGNORE_EXTENSIONS are always excluded." - "If not defined, defaults to m;mm." - ) - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES" - BRIEF_DOCS "Minimum number of sources in target required to enable use of precompiled header." - FULL_DOCS - "The variable can be set to an integer > 0." - "If a target contains less than that number of source files, cotire will not enable the use of the precompiled header for the target." - "If not defined, defaults to 2." - ) - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES" - BRIEF_DOCS "Maximum number of source files to include in a single unity source file." - FULL_DOCS - "This may be set to an integer >= 0." - "If 0, cotire will only create a single unity source file." - "If a target contains more than that number of source files, cotire will create multiple unity source files for it." - "Can be set to \"-j\" to optimize the count of unity source files for the number of available processor cores." - "Can be set to \"-j jobs\" to optimize the number of unity source files for the given number of simultaneous jobs." - "Is used to initialize the target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES." - "Defaults to \"-j\" for the generators Visual Studio, JOM or Ninja. Defaults to 0 otherwise." - ) - - # define cotire directory properties - - define_property( - DIRECTORY PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER" - BRIEF_DOCS "Modify build command of cotired targets added in this directory to make use of the generated precompiled header." - FULL_DOCS - "See target property COTIRE_ENABLE_PRECOMPILED_HEADER." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_ADD_UNITY_BUILD" - BRIEF_DOCS "Add a new target that performs a unity build for cotired targets added in this directory." - FULL_DOCS - "See target property COTIRE_ADD_UNITY_BUILD." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_ADD_CLEAN" - BRIEF_DOCS "Add a new target that cleans all cotire generated files for cotired targets added in this directory." - FULL_DOCS - "See target property COTIRE_ADD_CLEAN." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH" - BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." - FULL_DOCS - "See target property COTIRE_PREFIX_HEADER_IGNORE_PATH." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH" - BRIEF_DOCS "Honor headers from these directories when generating the prefix header." - FULL_DOCS - "See target property COTIRE_PREFIX_HEADER_INCLUDE_PATH." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" - BRIEF_DOCS "Header paths matching one of these directories are put at the top of the prefix header." - FULL_DOCS - "See target property COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each source file." - FULL_DOCS - "See target property COTIRE_UNITY_SOURCE_PRE_UNDEFS." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each source file." - FULL_DOCS - "See target property COTIRE_UNITY_SOURCE_POST_UNDEFS." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES" - BRIEF_DOCS "Maximum number of source files to include in a single unity source file." - FULL_DOCS - "See target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_UNITY_LINK_LIBRARIES_INIT" - BRIEF_DOCS "Define strategy for setting up the unity target's link libraries." - FULL_DOCS - "See target property COTIRE_UNITY_LINK_LIBRARIES_INIT." - ) - - # define cotire target properties - - define_property( - TARGET PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER" INHERITED - BRIEF_DOCS "Modify this target's build command to make use of the generated precompiled header." - FULL_DOCS - "If this property is set to TRUE, cotire will modify the build command to make use of the generated precompiled header." - "Irrespective of the value of this property, cotire will setup custom commands to generate the unity source and prefix header for the target." - "For makefile based generators cotire will also set up a custom target to manually invoke the generation of the precompiled header." - "The target name will be set to this target's name with the suffix _pch appended." - "Inherited from directory." - "Defaults to TRUE." - ) - - define_property( - TARGET PROPERTY "COTIRE_ADD_UNITY_BUILD" INHERITED - BRIEF_DOCS "Add a new target that performs a unity build for this target." - FULL_DOCS - "If this property is set to TRUE, cotire creates a new target of the same type that uses the generated unity source file instead of the target sources." - "Most of the relevant target properties will be copied from this target to the new unity build target." - "Target dependencies and linked libraries have to be manually set up for the new unity build target." - "The unity target name will be set to this target's name with the suffix _unity appended." - "Inherited from directory." - "Defaults to TRUE." - ) - - define_property( - TARGET PROPERTY "COTIRE_ADD_CLEAN" INHERITED - BRIEF_DOCS "Add a new target that cleans all cotire generated files for this target." - FULL_DOCS - "If this property is set to TRUE, cotire creates a new target that clean all files (unity source, prefix header, precompiled header)." - "The clean target name will be set to this target's name with the suffix _clean_cotire appended." - "Inherited from directory." - "Defaults to FALSE." - ) - - define_property( - TARGET PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH" INHERITED - BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." - FULL_DOCS - "The property can be set to a list of directories." - "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header." - "Inherited from directory." - "If not set, this property is initialized to \${CMAKE_SOURCE_DIR};\${CMAKE_BINARY_DIR}." - ) - - define_property( - TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH" INHERITED - BRIEF_DOCS "Honor headers from these directories when generating the prefix header." - FULL_DOCS - "The property can be set to a list of directories." - "If a header file is found in one of these directories or sub-directories, it will be included in the generated prefix header." - "If a header file is both selected by COTIRE_PREFIX_HEADER_IGNORE_PATH and COTIRE_PREFIX_HEADER_INCLUDE_PATH," - "the option which yields the closer relative path match wins." - "Inherited from directory." - "If not set, this property is initialized to the empty list." - ) - - define_property( - TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" INHERITED - BRIEF_DOCS "Header paths matching one of these directories are put at the top of prefix header." - FULL_DOCS - "The property can be set to a list of directories." - "Header file paths matching one of these directories will be inserted at the beginning of the generated prefix header." - "Header files are sorted according to the order of the directories in the property." - "If not set, this property is initialized to the empty list." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" INHERITED - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each target source file." - FULL_DOCS - "This may be set to a semicolon-separated list of preprocessor symbols." - "cotire will add corresponding #undef directives to the generated unit source file before each target source file." - "Inherited from directory." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" INHERITED - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each target source file." - FULL_DOCS - "This may be set to a semicolon-separated list of preprocessor symbols." - "cotire will add corresponding #undef directives to the generated unit source file after each target source file." - "Inherited from directory." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES" INHERITED - BRIEF_DOCS "Maximum number of source files to include in a single unity source file." - FULL_DOCS - "This may be set to an integer > 0." - "If a target contains more than that number of source files, cotire will create multiple unity build files for it." - "If not set, cotire will only create a single unity source file." - "Inherited from directory." - "Defaults to empty." - ) - - define_property( - TARGET PROPERTY "COTIRE__UNITY_SOURCE_INIT" - BRIEF_DOCS "User provided unity source file to be used instead of the automatically generated one." - FULL_DOCS - "If set, cotire will only add the given file(s) to the generated unity source file." - "If not set, cotire will add all the target source files to the generated unity source file." - "The property can be set to a user provided unity source file." - "Defaults to empty." - ) - - define_property( - TARGET PROPERTY "COTIRE__PREFIX_HEADER_INIT" - BRIEF_DOCS "User provided prefix header file to be used instead of the automatically generated one." - FULL_DOCS - "If set, cotire will add the given header file(s) to the generated prefix header file." - "If not set, cotire will generate a prefix header by tracking the header files included by the unity source file." - "The property can be set to a user provided prefix header file (e.g., stdafx.h)." - "Defaults to empty." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_LINK_LIBRARIES_INIT" INHERITED - BRIEF_DOCS "Define strategy for setting up unity target's link libraries." - FULL_DOCS - "If this property is empty or set to NONE, the generated unity target's link libraries have to be set up manually." - "If this property is set to COPY, the unity target's link libraries will be copied from this target." - "If this property is set to COPY_UNITY, the unity target's link libraries will be copied from this target with considering existing unity targets." - "Inherited from directory." - "Defaults to empty." - ) - - define_property( - TARGET PROPERTY "COTIRE__UNITY_SOURCE" - BRIEF_DOCS "Read-only property. The generated unity source file(s)." - FULL_DOCS - "cotire sets this property to the path of the generated single computation unit source file for the target." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE__PREFIX_HEADER" - BRIEF_DOCS "Read-only property. The generated prefix header file." - FULL_DOCS - "cotire sets this property to the full path of the generated language prefix header for the target." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE__PRECOMPILED_HEADER" - BRIEF_DOCS "Read-only property. The generated precompiled header file." - FULL_DOCS - "cotire sets this property to the full path of the generated language precompiled header binary for the target." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_TARGET_NAME" - BRIEF_DOCS "The name of the generated unity build target corresponding to this target." - FULL_DOCS - "This property can be set to the desired name of the unity target that will be created by cotire." - "If not set, the unity target name will be set to this target's name with the suffix _unity appended." - "After this target has been processed by cotire, the property is set to the actual name of the generated unity target." - "Defaults to empty string." - ) - - # define cotire source properties - - define_property( - SOURCE PROPERTY "COTIRE_EXCLUDED" - BRIEF_DOCS "Do not modify source file's build command." - FULL_DOCS - "If this property is set to TRUE, the source file's build command will not be modified to make use of the precompiled header." - "The source file will also be excluded from the generated unity source file." - "Source files that have their COMPILE_FLAGS property set will be excluded by default." - "Defaults to FALSE." - ) - - define_property( - SOURCE PROPERTY "COTIRE_DEPENDENCY" - BRIEF_DOCS "Add this source file to dependencies of the automatically generated prefix header file." - FULL_DOCS - "If this property is set to TRUE, the source file is added to dependencies of the generated prefix header file." - "If the file is modified, cotire will re-generate the prefix header source upon build." - "Defaults to FALSE." - ) - - define_property( - SOURCE PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of this source file." - FULL_DOCS - "This may be set to a semicolon-separated list of preprocessor symbols." - "cotire will add corresponding #undef directives to the generated unit source file before this file is included." - "Defaults to empty string." - ) - - define_property( - SOURCE PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of this source file." - FULL_DOCS - "This may be set to a semicolon-separated list of preprocessor symbols." - "cotire will add corresponding #undef directives to the generated unit source file after this file is included." - "Defaults to empty string." - ) - - define_property( - SOURCE PROPERTY "COTIRE_START_NEW_UNITY_SOURCE" - BRIEF_DOCS "Start a new unity source file which includes this source file as the first one." - FULL_DOCS - "If this property is set to TRUE, cotire will complete the current unity file and start a new one." - "The new unity source file will include this source file as the first one." - "This property essentially works as a separator for unity source files." - "Defaults to FALSE." - ) - - define_property( - SOURCE PROPERTY "COTIRE_TARGET" - BRIEF_DOCS "Read-only property. Mark this source file as cotired for the given target." - FULL_DOCS - "cotire sets this property to the name of target, that the source file's build command has been altered for." - "Defaults to empty string." - ) - - message (STATUS "cotire ${COTIRE_CMAKE_MODULE_VERSION} loaded.") - -endif() diff --git a/build/Modules/FindCERES.cmake b/build/Modules/FindCERES.cmake deleted file mode 100644 index 6905393be..000000000 --- a/build/Modules/FindCERES.cmake +++ /dev/null @@ -1,103 +0,0 @@ -# - try to find CERES headers -# -# Users may optionally supply: -# CERES_DIR - a prefix to start searching for the toon headers. -# -# Cache Variables: (probably not for direct use in your scripts) -# CERES_INCLUDE_DIR -# -# Non-cache variables you might use in your CMakeLists.txt: -# CERES_FOUND -# CERES_INCLUDE_DIRS -# CERES_LIBS -# CERES_DEFINITIONS -# -# Requires these CMake modules: -# FindPackageHandleStandardArgs (known included with CMake >=2.6.2) - -#try to use the Config script -if(NOT EXISTS "${CERES_DIR}") - find_path(CERES_DIR "CeresConfig.cmake" - HINTS "${CERES_ROOT}" "$ENV{CERES_ROOT}" "$ENV{CERES_DIR}" "$ENV{CERES_ROOT}/CMake" - PATHS "$ENV{PROGRAMFILES}" "$ENV{PROGRAMW6432}" "/usr" "/usr/local" "/usr/share" "/usr/local/share" "/usr/lib/cmake" "/usr/local/lib/cmake" "/usr/include" "/usr/lib/x86_64-linux-gnu/cmake" - PATH_SUFFIXES "Ceres" - DOC "Root directory of CERES") -endif() - -set(CERES_VERSION "") -if(EXISTS "${CERES_DIR}") - - ## Include the standard CMake script - include("${CERES_DIR}/CeresConfig.cmake") - set(CERES_LIBS ${CERES_LIBS} ${CERES_LIBRARIES}) - -else() - - # Find required packages - FIND_PACKAGE(Eigen ${SYSTEM_PACKAGE_QUIET}) - FIND_PACKAGE(SUITESPARSE ${SYSTEM_PACKAGE_QUIET}) - if(SUITESPARSE_FOUND) - set(CERES_LIBS ${CERES_LIBS} ${SUITESPARSE_LIBS}) - endif() - FIND_PACKAGE(GLOG ${SYSTEM_PACKAGE_QUIET}) - if(GLOG_FOUND) - set(CERES_INCLUDE_DIRS ${CERES_INCLUDE_DIRS} ${GLOG_INCLUDE_DIRS}) - set(CERES_LIBS ${CERES_LIBS} ${GLOG_LIBS}) - endif() - - if(NOT CERES_DIR OR "${CERES_DIR}" STREQUAL "") - set(CERES_DIR "$ENV{CERES_ROOT}") - endif() - set(CERES_DIR "${CERES_DIR}" CACHE PATH "Root directory of CERES library") - - #try to guess path - find_path(CERES_INCLUDE_DIR - NAMES "ceres/ceres.h" - HINTS "${CERES_DIR}" "$ENV{CERES_ROOT}" "/usr" "/usr/local" - PATH_SUFFIXES "include") - - set(CERES_FOUND FALSE) - if(EXISTS "${CERES_INCLUDE_DIR}" AND NOT "${CERES_INCLUDE_DIR}" STREQUAL "") - set(CERES_FOUND TRUE) - - find_library(CERES_LIBRARY_DEBUG "libceres" "ceres" "ceres_shared" PATHS "${CERES_DIR}/lib${PACKAGE_LIB_SUFFIX_DBG}" "$ENV{OpenCV_ROOT}/lib${PACKAGE_LIB_SUFFIX_DBG}" NO_DEFAULT_PATH) - find_library(CERES_LIBRARY_RELEASE "libceres" "ceres" "ceres_shared" PATHS "${CERES_DIR}/lib${PACKAGE_LIB_SUFFIX_REL}" "$ENV{OpenCV_ROOT}/lib${PACKAGE_LIB_SUFFIX_REL}" NO_DEFAULT_PATH) - find_library(CERES_LIBRARY_ALL NAMES "ceres" PATH_SUFFIXES "ceres") - - #Remove the cache value - set(CERES_LIBRARY "" CACHE STRING "" FORCE) - - #both debug/release - if(CERES_LIBRARY_DEBUG AND CERES_LIBRARY_RELEASE) - set(CERES_LIBRARY debug ${CERES_LIBRARY_DEBUG} optimized ${CERES_LIBRARY_RELEASE} CACHE STRING "" FORCE) - #only debug - elseif(CERES_LIBRARY_DEBUG) - set(CERES_LIBRARY ${CERES_LIBRARY_DEBUG} CACHE STRING "" FORCE) - #only release - elseif(CERES_LIBRARY_RELEASE) - set(CERES_LIBRARY ${CERES_LIBRARY_RELEASE} CACHE STRING "" FORCE) - #both debug/release - elseif(CERES_LIBRARY_ALL) - set(CERES_LIBRARY ${CERES_LIBRARY_ALL} CACHE STRING "" FORCE) - #no library found - else() - message("CERES library NOT found") - set(CERES_FOUND FALSE) - endif() - - #Add to the general list - if(CERES_LIBRARY) - set(CERES_LIBS ${CERES_LIBS} ${CERES_LIBRARY}) - endif() - endif() - -endif() - -if(CERES_FOUND) - set(CERES_INCLUDE_DIRS ${CERES_INCLUDE_DIRS} "${CERES_INCLUDE_DIR}") - set(CERES_DIR "${CERES_DIR}" CACHE PATH "" FORCE) - mark_as_advanced(CERES_DIR) - message(STATUS "CERES ${CERES_VERSION} found (include: ${CERES_INCLUDE_DIRS})") -else() - package_report_not_found(CERES "Please specify CERES directory using CERES_ROOT env. variable") -endif() diff --git a/build/Modules/FindCGAL.cmake b/build/Modules/FindCGAL.cmake deleted file mode 100644 index fcdc7b4fd..000000000 --- a/build/Modules/FindCGAL.cmake +++ /dev/null @@ -1,89 +0,0 @@ -########################################################### -# Find CGAL Library -#---------------------------------------------------------- -# CGAL_FOUND - True if headers and requested libraries were found -# CGAL_INCLUDE_DIRS - CGAL include directories -# CGAL_LIBRARY_DIRS - Link directories for CGAL libraries -# CGAL_LIBS - CGAL libraries -# CGAL_VERSION - MAJOR.MINOR -#---------------------------------------------------------- - -set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) - -if(NOT CGAL_DIR) - # Get the system search path as a list. - if(UNIX) - string(REGEX MATCHALL "[^:]+" CGAL_DIR_SEARCH1 "$ENV{PATH}") - else() - string(REGEX REPLACE "\\\\" "/" CGAL_DIR_SEARCH1 "$ENV{PATH}") - endif() - string(REGEX REPLACE "/;" ";" CGAL_DIR_SEARCH2 "${CGAL_DIR_SEARCH1}") - # Construct a set of paths relative to the system search path. - set(CGAL_DIR_SEARCH "") - foreach(dir ${CGAL_DIR_SEARCH2}) - set(CGAL_DIR_SEARCH ${CGAL_DIR_SEARCH} ${dir}/../lib/CGAL) - endforeach() - set(CGAL_DIR_SEARCH ${CGAL_DIR_SEARCH} "lib" "lib64") - - # - # Look for an installation or build tree. - # - find_path(CGAL_DIR "CGALConfig.cmake" - # Look for an environment variable CGAL_DIR. - HINTS "${CGAL_ROOT}" "$ENV{CGAL_ROOT}" "$ENV{CGAL_DIR}" "$ENV{PROGRAMFILES}" "$ENV{PROGRAMW6432}" - - # Look in places relative to the system executable search path. - ${CGAL_DIR_SEARCH} - - # Look in standard UNIX install locations. - PATHS "/usr" "/usr/local" "/usr/share" "/usr/local/share" "/usr/lib/cmake" "/usr/local/lib/cmake" "/usr/include" "/usr/lib/x86_64-linux-gnu/cmake" - - # Read from the CMakeSetup registry entries. It is likely that - # CGAL will have been recently built. - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild1] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild2] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild3] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild4] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild5] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild6] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild7] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild8] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild9] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild10] - - PATH_SUFFIXES "CGAL" "share" "share/cgal" "share/cmake" "share/cmake/cgal" - - DOC "Root directory of CGAL library" - ) -endif() - -##==================================================== -## Include CGAL library -##---------------------------------------------------- -set(CGAL_VERSION "") -if(EXISTS "${CGAL_DIR}" AND NOT "${CGAL_DIR}" STREQUAL "") - if(EXISTS "${CGAL_DIR}/CGALConfig.cmake") - include("${CGAL_DIR}/CGALConfig.cmake") - set(CGAL_TARGET_LIBS "") - if(TARGET CGAL::CGAL) - get_target_property(CGAL_TARGET_LIBS CGAL::CGAL INTERFACE_LINK_LIBRARIES) - elseif(TARGET CGAL) - get_target_property(CGAL_TARGET_LIBS CGAL INTERFACE_LINK_LIBRARIES) - endif() - if(NOT CGAL_TARGET_LIBS) - set(CGAL_TARGET_LIBS "") - endif() - set(CGAL_LIBS ${CGAL_LIBS} ${CGAL_LIBRARIES} ${CGAL_LIBRARY} ${CGAL_TARGET_LIBS} ${CGAL_Core_LIBRARY} ${CGAL_ImageIO_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${CGAL_Core_3RD_PARTY_LIBRARIES} ${CGAL_ImageIO_3RD_PARTY_LIBRARIES} ${MPFR_LIBRARIES} ${GMP_LIBRARIES} ${ZLIB_LIBRARIES}) - set(CGAL_VERSION "${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}") - else() - set(CGAL_INCLUDE_DIRS "${CGAL_DIR}/include" "${CGAL_DIR}/auxiliary/gmp/include") - set(CGAL_LIBRARY_DIRS "${CGAL_DIR}/lib${PACKAGE_LIB_SUFFIX}") - endif() - set(CGAL_FOUND TRUE) - set(CGAL_DIR "${CGAL_DIR}" CACHE PATH "" FORCE) - mark_as_advanced(CGAL_DIR) - message(STATUS "CGAL ${CGAL_VERSION} found (include: ${CGAL_INCLUDE_DIRS})") -else() - package_report_not_found(CGAL "Please specify CGAL directory using CGAL_ROOT env. variable") -endif() -##==================================================== diff --git a/build/Modules/FindEigen.cmake b/build/Modules/FindEigen.cmake deleted file mode 100644 index 0f1d74c02..000000000 --- a/build/Modules/FindEigen.cmake +++ /dev/null @@ -1,55 +0,0 @@ -########################################################### -# Find EIGEN Library -#---------------------------------------------------------- - -find_path(EIGEN_DIR "Eigen/Core" - HINTS "${EIGEN_ROOT}" "$ENV{EIGEN_ROOT}" - PATHS "$ENV{PROGRAMFILES}" "$ENV{PROGRAMW6432}" "/usr" "/usr/local" "/usr/share" "/usr/local/share" "/usr/lib/x86_64-linux-gnu/cmake" - PATH_SUFFIXES "eigen" "eigen3" "include" - DOC "Root directory of EIGEN library") - -##==================================================== -## Include EIGEN library -##---------------------------------------------------- -if(EXISTS "${EIGEN_DIR}" AND NOT "${EIGEN_DIR}" STREQUAL "") - set(EIGEN_FOUND TRUE) - set(EIGEN_INCLUDE_DIRS ${EIGEN_DIR}) - set(EIGEN_DIR "${EIGEN_DIR}" CACHE PATH "" FORCE) - mark_as_advanced(EIGEN_DIR) - - # Extract Eigen version from Eigen/src/Core/util/Macros.h - SET(EIGEN_VERSION_FILE ${EIGEN_INCLUDE_DIRS}/Eigen/src/Core/util/Macros.h) - IF (NOT EXISTS ${EIGEN_VERSION_FILE}) - EIGEN_REPORT_NOT_FOUND( - "Could not find file: ${EIGEN_VERSION_FILE} " - "containing version information in Eigen install located at: " - "${EIGEN_INCLUDE_DIRS}.") - ELSE (NOT EXISTS ${EIGEN_VERSION_FILE}) - FILE(READ ${EIGEN_VERSION_FILE} EIGEN_VERSION_FILE_CONTENTS) - - STRING(REGEX MATCH "#define EIGEN_WORLD_VERSION [0-9]+" - EIGEN_WORLD_VERSION "${EIGEN_VERSION_FILE_CONTENTS}") - STRING(REGEX REPLACE "#define EIGEN_WORLD_VERSION ([0-9]+)" "\\1" - EIGEN_WORLD_VERSION "${EIGEN_WORLD_VERSION}") - - STRING(REGEX MATCH "#define EIGEN_MAJOR_VERSION [0-9]+" - EIGEN_MAJOR_VERSION "${EIGEN_VERSION_FILE_CONTENTS}") - STRING(REGEX REPLACE "#define EIGEN_MAJOR_VERSION ([0-9]+)" "\\1" - EIGEN_MAJOR_VERSION "${EIGEN_MAJOR_VERSION}") - - STRING(REGEX MATCH "#define EIGEN_MINOR_VERSION [0-9]+" - EIGEN_MINOR_VERSION "${EIGEN_VERSION_FILE_CONTENTS}") - STRING(REGEX REPLACE "#define EIGEN_MINOR_VERSION ([0-9]+)" "\\1" - EIGEN_MINOR_VERSION "${EIGEN_MINOR_VERSION}") - - # This is on a single line s/t CMake does not interpret it as a list of - # elements and insert ';' separators which would result in 3.;2.;0 nonsense. - SET(EIGEN_VERSION "${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION}") - ENDIF (NOT EXISTS ${EIGEN_VERSION_FILE}) - set(EIGEN_INCLUDE_DIR ${EIGEN_DIR}) - - message(STATUS "Eigen ${EIGEN_VERSION} found (include: ${EIGEN_INCLUDE_DIRS})") -else() - package_report_not_found(EIGEN "Please specify EIGEN directory using EIGEN_ROOT env. variable") -endif() -##==================================================== diff --git a/build/Modules/FindEigen3.cmake b/build/Modules/FindEigen3.cmake new file mode 100644 index 000000000..0b36805e7 --- /dev/null +++ b/build/Modules/FindEigen3.cmake @@ -0,0 +1,107 @@ +# - Try to find Eigen3 lib +# +# This module supports requiring a minimum version, e.g. you can do +# find_package(Eigen3 3.1.2) +# to require version 3.1.2 or newer of Eigen3. +# +# Once done this will define +# +# EIGEN3_FOUND - system has eigen lib with correct version +# EIGEN3_INCLUDE_DIR - the eigen include directory +# EIGEN3_VERSION - eigen version +# +# and the following imported target: +# +# Eigen3::Eigen - The header-only Eigen library +# +# This module reads hints about search locations from +# the following environment variables: +# +# EIGEN3_ROOT +# EIGEN3_ROOT_DIR + +# Copyright (c) 2006, 2007 Montel Laurent, +# Copyright (c) 2008, 2009 Gael Guennebaud, +# Copyright (c) 2009 Benoit Jacob +# Redistribution and use is allowed according to the terms of the 2-clause BSD license. + +if(NOT Eigen3_FIND_VERSION) + if(NOT Eigen3_FIND_VERSION_MAJOR) + set(Eigen3_FIND_VERSION_MAJOR 2) + endif() + if(NOT Eigen3_FIND_VERSION_MINOR) + set(Eigen3_FIND_VERSION_MINOR 91) + endif() + if(NOT Eigen3_FIND_VERSION_PATCH) + set(Eigen3_FIND_VERSION_PATCH 0) + endif() + + set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") +endif() + +macro(_eigen3_check_version) + file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) + + string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") + set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") + set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") + set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") + + set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) + if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK FALSE) + else() + set(EIGEN3_VERSION_OK TRUE) + endif() + + if(NOT EIGEN3_VERSION_OK) + + message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " + "but at least version ${Eigen3_FIND_VERSION} is required") + endif() +endmacro() + +if (EIGEN3_INCLUDE_DIR) + + # in cache already + _eigen3_check_version() + set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) + set(Eigen3_FOUND ${EIGEN3_VERSION_OK}) + +else () + + # search first if an Eigen3Config.cmake is available in the system, + # if successful this would set EIGEN3_INCLUDE_DIR and the rest of + # the script will work as usual + find_package(Eigen3 ${Eigen3_FIND_VERSION} NO_MODULE QUIET) + + if(NOT EIGEN3_INCLUDE_DIR) + find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library + HINTS + ENV EIGEN3_ROOT + ENV EIGEN3_ROOT_DIR + PATHS + ${CMAKE_INSTALL_PREFIX}/include + ${KDE4_INCLUDE_DIR} + PATH_SUFFIXES eigen3 eigen + ) + endif() + + if(EIGEN3_INCLUDE_DIR) + _eigen3_check_version() + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) + + mark_as_advanced(EIGEN3_INCLUDE_DIR) + +endif() + +if(EIGEN3_FOUND AND NOT TARGET Eigen3::Eigen) + add_library(Eigen3::Eigen INTERFACE IMPORTED) + set_target_properties(Eigen3::Eigen PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${EIGEN3_INCLUDE_DIR}") +endif() diff --git a/build/OpenMVSConfig.cmake.in b/build/OpenMVSConfig.cmake.in deleted file mode 100644 index 96b8fe2ce..000000000 --- a/build/OpenMVSConfig.cmake.in +++ /dev/null @@ -1,18 +0,0 @@ -# - Configure file for the OpenMVS package -# It defines the following variables -# OpenMVS_INCLUDE_DIRS - include directories for OpenMVS -# OpenMVS_LIBRARIES - libraries to link against -# OpenMVS_BINARIES - the binaries - -# Compute paths -get_filename_component(OpenMVS_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) -set(OpenMVS_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@") - -# Our library dependencies (contains definitions for IMPORTED targets) -if(NOT TARGET MVS AND NOT OpenMVS_BINARY_DIR) - include("${OpenMVS_CMAKE_DIR}/OpenMVSTargets.cmake") -endif() - -# These are IMPORTED targets created by OpenMVSTargets.cmake -set(OpenMVS_LIBRARIES MVS) -set(OpenMVS_BINARIES InterfaceVisualSFM DensifyPointCloud ReconstructMesh RefineMesh TextureMesh) diff --git a/build/OpenMVSConfigVersion.cmake.in b/build/OpenMVSConfigVersion.cmake.in deleted file mode 100644 index 3c9e7eb61..000000000 --- a/build/OpenMVSConfigVersion.cmake.in +++ /dev/null @@ -1,11 +0,0 @@ -set(PACKAGE_VERSION "@OpenMVS_VERSION@") - -# Check whether the requested PACKAGE_FIND_VERSION is compatible -if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") - set(PACKAGE_VERSION_COMPATIBLE FALSE) -else() - set(PACKAGE_VERSION_COMPATIBLE TRUE) - if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") - set(PACKAGE_VERSION_EXACT TRUE) - endif() -endif() diff --git a/build/Templates/OpenMVSConfig.cmake.in b/build/Templates/OpenMVSConfig.cmake.in new file mode 100644 index 000000000..9747b3e16 --- /dev/null +++ b/build/Templates/OpenMVSConfig.cmake.in @@ -0,0 +1,23 @@ +# Configure file for the OpenMVS package, defining the following variables: +# OpenMVS_INCLUDE_DIRS - include directories +# OpenMVS_DEFINITIONS - definitions to be used +# OpenMVS_LIBRARIES - libraries to link against +# OpenMVS_BINARIES - binaries + +@PACKAGE_INIT@ + +set(OpenMVS_VERSION "@OpenMVS_VERSION@") + +# Compute paths +set(OpenMVS_PREFIX "@CMAKE_INSTALL_PREFIX@") +set(OpenMVS_CMAKE_DIR "@INSTALL_CMAKE_DIR_IN@") +set(OpenMVS_INCLUDE_DIRS "@INSTALL_INCLUDE_DIR_IN@") + +set(OpenMVS_DEFINITIONS "@OpenMVS_DEFINITIONS@") + +# These are IMPORTED targets created by OpenMVSTargets.cmake +set(OpenMVS_LIBRARIES MVS) +set(OpenMVS_BINARIES InterfaceCOLMAP DensifyPointCloud ReconstructMesh RefineMesh TextureMesh) + +include("${CMAKE_CURRENT_LIST_DIR}/OpenMVSTargets.cmake") +check_required_components("OpenMVS") diff --git a/build/cmake_uninstall.cmake.in b/build/Templates/cmake_uninstall.cmake.in similarity index 100% rename from build/cmake_uninstall.cmake.in rename to build/Templates/cmake_uninstall.cmake.in diff --git a/build/Utils.cmake b/build/Utils.cmake index f41c9d89f..106d43167 100644 --- a/build/Utils.cmake +++ b/build/Utils.cmake @@ -174,9 +174,9 @@ macro(ComposePackageLibSuffix) set(PACKAGE_LIB_SUFFIX_DBG "") set(PACKAGE_LIB_SUFFIX_REL "") if(MSVC) - if("${MSVC_VERSION}" STREQUAL "1921") + if("${MSVC_VERSION}" STRGREATER "1916") set(PACKAGE_LIB_SUFFIX "/vc16") - elseif("${MSVC_VERSION}" STREQUAL "1916") + elseif("${MSVC_VERSION}" STRGREATER "1900") set(PACKAGE_LIB_SUFFIX "/vc15") elseif("${MSVC_VERSION}" STREQUAL "1900") set(PACKAGE_LIB_SUFFIX "/vc14") @@ -273,18 +273,6 @@ macro(add_option variable description value) endmacro() -# Set as Pre-Compiled Header automaticaly or to the given file name -macro(set_target_pch TRGT) - if(ENABLE_PRECOMPILED_HEADERS AND COMMAND cotire) - if(NOT ${ARGN} STREQUAL "") - set_target_properties("${TRGT}" PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${ARGN}") - endif() - set_target_properties("${TRGT}" PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) - cotire("${TRGT}") - endif() -endmacro() - - # Optimize compiler settings set(STATIC_COMPILER_FAIL_REGEX @@ -401,21 +389,6 @@ macro(optimize_default_compiler_settings) add_option(ENABLE_NOISY_WARNINGS "Show all warnings even if they are too noisy" OFF ) add_option(ENABLE_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF ) - if(MINGW) - # mingw compiler is known to produce unstable SSE code with -O3 hence we are trying to use -O2 instead - if(CMAKE_COMPILER_IS_GNUCXX) - foreach(flags CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG) - string(REPLACE "-O3" "-O2" ${flags} "${${flags}}") - endforeach() - endif() - - if(CMAKE_COMPILER_IS_GNUCC) - foreach(flags CMAKE_C_FLAGS CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_DEBUG) - string(REPLACE "-O3" "-O2" ${flags} "${${flags}}") - endforeach() - endif() - endif() - set(BUILD_EXTRA_FLAGS "") set(BUILD_EXTRA_C_FLAGS "") set(BUILD_EXTRA_CXX_FLAGS "") @@ -452,15 +425,6 @@ macro(optimize_default_compiler_settings) set(CMAKE_CXX_EXTENSIONS OFF) message("Compiling with C++${CMAKE_CXX_STANDARD}") - if(MINGW) - # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838 - # here we are trying to workaround the problem - add_extra_compiler_option(-mstackrealign) - if(NOT HAVE_CXX_MSTACKREALIGN) - add_extra_compiler_option(-mpreferred-stack-boundary=2) - endif() - endif() - if(FLG_COMPILER_IS_GNU) # High level of warnings. add_extra_compiler_option(-W) @@ -526,17 +490,13 @@ macro(optimize_default_compiler_settings) add_extra_compiler_option(-Werror) endif() - if(X86 AND NOT MINGW64 AND NOT X86_64 AND NOT APPLE) - add_extra_compiler_option(-march=i686) - endif() - # Other optimizations if(ENABLE_OMIT_FRAME_POINTER) add_extra_compiler_option(-fomit-frame-pointer) else() add_extra_compiler_option(-fno-omit-frame-pointer) - endif() - if(NOT CLANG) + endif() + if(NOT CLANG) if(ENABLE_FAST_MATH) add_extra_compiler_option(-ffast-math) else() @@ -825,32 +785,35 @@ endmacro() # Initialize variables needed for a library type project. macro(ConfigLibrary) # Offer the user the choice of overriding the installation directories - set(INSTALL_LIB_DIR "lib/${PROJECT_NAME}" CACHE PATH "Installation directory for libraries") - set(INSTALL_BIN_DIR "bin/${PROJECT_NAME}" CACHE PATH "Installation directory for executables") - set(INSTALL_INCLUDE_DIR "include/${PROJECT_NAME}" CACHE PATH "Installation directory for header files") + set(INSTALL_LIB_DIR "lib" CACHE PATH "Installation directory for libraries") + set(INSTALL_BIN_DIR "bin" CACHE PATH "Installation directory for executables") + set(INSTALL_INCLUDE_DIR "include" CACHE PATH "Installation directory for header files") if(WIN32 AND NOT CYGWIN) set(DEF_INSTALL_CMAKE_DIR "CMake") else() - set(DEF_INSTALL_CMAKE_DIR "lib/CMake/${PROJECT_NAME}") + set(DEF_INSTALL_CMAKE_DIR "lib/cmake") endif() set(INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH "Installation directory for CMake files") - # Make relative paths absolute (needed later on) foreach(p LIB BIN INCLUDE CMAKE) set(var INSTALL_${p}_DIR) - if(NOT IS_ABSOLUTE "${${var}}") - set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}") + set(varp INSTALL_${p}_DIR_PREFIX) + if(IS_ABSOLUTE "${${varp}}") + set(${varp} "${${varp}}") + else() + set(${varp} "${CMAKE_INSTALL_PREFIX}/${${var}}") endif() + set(${var} "${${varp}}/${PROJECT_NAME}") endforeach() endmacro() # Defines the main libraries. User tests should link # with one of them. -function(cxx_library_with_type_no_pch name folder type cxx_flags) +function(cxx_library_with_type name folder type cxx_flags) # type can be either STATIC or SHARED to denote a static or shared library. # ARGN refers to additional arguments after 'cxx_flags'. add_library("${name}" ${type} ${ARGN}) - set_target_properties("${name}" PROPERTIES COMPILE_FLAGS "${cxx_flags}") + #set_target_properties("${name}" PROPERTIES COMPILE_FLAGS "${cxx_flags}") if ((BUILD_SHARED_LIBS AND NOT type STREQUAL "STATIC") OR type STREQUAL "SHARED") set_target_properties("${name}" PROPERTIES COMPILE_DEFINITIONS "_USRDLL") else() @@ -860,29 +823,11 @@ function(cxx_library_with_type_no_pch name folder type cxx_flags) set_target_properties("${name}" PROPERTIES FOLDER "${folder}") endfunction() -function(cxx_library_with_type name folder type cxx_flags) - cxx_library_with_type_no_pch("${name}" "${folder}" "${type}" "${cxx_flags}" ${ARGN}) - # Generate precompiled headers - set_target_pch("${name}") -endfunction() - -######################################################################## -# -# Helper functions for creating build targets. - -function(cxx_shared_library name folder cxx_flags) - cxx_library_with_type("${name}" "${folder}" SHARED "${cxx_flags}" ${ARGN}) -endfunction() - -function(cxx_library name folder cxx_flags) - cxx_library_with_type("${name}" "${folder}" "" "${cxx_flags}" ${ARGN}) -endfunction() - # cxx_executable_with_flags(name cxx_flags libs srcs...) # # creates a named C++ executable that depends on the given libraries and # is built from the given source files with the given compiler flags. -function(cxx_executable_with_flags_no_pch name folder cxx_flags libs) +function(cxx_executable_with_flags name folder cxx_flags libs) add_executable("${name}" ${ARGN}) if (cxx_flags) set_target_properties("${name}" PROPERTIES COMPILE_FLAGS "${cxx_flags}") @@ -895,36 +840,3 @@ function(cxx_executable_with_flags_no_pch name folder cxx_flags libs) # Set project folder set_target_properties("${name}" PROPERTIES FOLDER "${folder}") endfunction() - -function(cxx_executable_with_flags name folder cxx_flags libs) - cxx_executable_with_flags_no_pch("${name}" "${folder}" "${cxx_flags}" "${libs}" ${ARGN}) - # Generate precompiled headers - set_target_pch("${name}") -endfunction() - -# cxx_executable(name dir lib srcs...) -# -# creates a named target that depends on the given libs and is built -# from the given source files. dir/name.cc is implicitly included in -# the source file list. -function(cxx_executable name folder dir libs) - cxx_executable_with_flags("${name}" "${folder}" "${cxx_default}" "${libs}" "${dir}/${name}.cpp" ${ARGN}) -endfunction() - -# cxx_test_with_flags(name cxx_flags libs srcs...) -# -# creates a named C++ test that depends on the given libs and is built -# from the given source files with the given compiler flags. -function(cxx_test_with_flags name folder cxx_flags libs) - cxx_executable_with_flags("${name}" "${folder}" "${cxx_flags}" "${libs}" ${ARGN}) - add_test("${name}" "${name}") -endfunction() - -# cxx_test(name libs srcs...) -# -# creates a named test target that depends on the given libs and is -# built from the given source files. Unlike cxx_test_with_flags, -# test/name.cc is already implicitly included in the source file list. -function(cxx_test name folder libs) - cxx_test_with_flags("${name}" "${folder}" "${cxx_default}" "${libs}" "test/${name}.cc" ${ARGN}) -endfunction() diff --git a/docker/Dockerfile b/docker/Dockerfile index 03e40a39d..a528c34f8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,8 @@ FROM ubuntu:18.04 +ARG USER_ID +ARG GROUP_ID + # Initialize the environment RUN apt update RUN apt install -y cmake git vim @@ -9,8 +12,8 @@ RUN apt-get update -yq RUN apt-get install -yq RUN apt-get -y install git mercurial cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mesa-dev -# Eigen (Known issues with eigen 3.3.7 as of 12/10/2019, so using this tested branch/commit instead) -RUN git clone https://gitlab.com/libeigen/eigen --branch 3.2 ed5cd0a4d16e12daa1bef608628c103e67969d63 +# Eigen +RUN git clone https://gitlab.com/libeigen/eigen --branch 3.4 RUN mkdir eigen_build RUN cd eigen_build &&\ cmake . ../eigen &&\ @@ -29,18 +32,25 @@ RUN apt-get -y install libcgal-dev libcgal-qt5-dev # VCGLib RUN git clone https://github.com/cdcseacave/VCG.git vcglib -# Build from stable release openMVS1.0 -RUN git clone https://github.com/cdcseacave/openMVS.git --branch v1.0 +# Build from stable openMVS release +RUN git clone https://github.com/cdcseacave/openMVS.git --branch master -# Uncomment below (and comment above) to use the latest commit from the master branch -# RUN git clone https://github.com/cdcseacave/openMVS.git openMVS +# Uncomment below (and comment above) to use the latest commit from the develop branch +#RUN git clone https://github.com/cdcseacave/openMVS.git --branch develop RUN mkdir openMVS_build RUN cd openMVS_build &&\ - cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_ROOT=/vcglib + cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_ROOT=/vcglib -DOpenMVS_USE_CUDA=OFF # Install OpenMVS library RUN cd openMVS_build &&\ make -j4 &&\ make install + +# Set permissions such that the output files can be accessed by the current user (optional) +RUN addgroup --gid $GROUP_ID user +RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID user +USER user + +# Add binaries to path ENV PATH /usr/local/bin/OpenMVS:$PATH diff --git a/docker/Dockerfile_CUDA b/docker/Dockerfile_CUDA new file mode 100644 index 000000000..86f4e8748 --- /dev/null +++ b/docker/Dockerfile_CUDA @@ -0,0 +1,58 @@ +FROM nvidia/cuda:11.1-devel-ubuntu18.04 + +ARG USER_ID +ARG GROUP_ID + +# Initialize the environment +RUN apt update +RUN apt install -y cmake git vim + +# Prepare and empty machine for building: +RUN apt-get update -yq +RUN apt-get install -yq +RUN apt-get -y install git mercurial cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mesa-dev + +ENV PATH=/usr/local/cuda-11.1/bin:$PATH + +# Eigen +RUN git clone https://gitlab.com/libeigen/eigen --branch 3.4 +RUN mkdir eigen_build +RUN cd eigen_build &&\ + cmake . ../eigen -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda/ &&\ + make && make install &&\ + cd .. + +# Boost +RUN apt-get -y install libboost-iostreams-dev libboost-program-options-dev libboost-system-dev libboost-serialization-dev + +# OpenCV +RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq libopencv-dev + +# CGAL +RUN apt-get -y install libcgal-dev libcgal-qt5-dev + +# VCGLib +RUN git clone https://github.com/cdcseacave/VCG.git vcglib + +# Build from stable openMVS release +RUN git clone https://github.com/cdcseacave/openMVS.git --branch master + +# Uncomment below (and comment above) to use the latest commit from the develop branch +#RUN git clone https://github.com/cdcseacave/openMVS.git --branch develop + +RUN mkdir openMVS_build +RUN cd openMVS_build &&\ + cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_ROOT=/vcglib -DOpenMVS_USE_CUDA=ON -DCMAKE_LIBRARY_PATH=/usr/local/cuda/lib64/stubs/ -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-11.1/ -DCUDA_INCLUDE_DIRS=/usr/local/cuda-11.1/include/ -DCUDA_CUDART_LIBRARY=/usr/local/cuda-11.1/lib64 -DCUDA_NVCC_EXECUTABLE=/usr/local/cuda-11.1/bin/ + +# Install OpenMVS library +RUN cd openMVS_build &&\ + make -j4 &&\ + make install + +# Set permissions such that the output files can be accessed by the current user (optional) +RUN addgroup --gid $GROUP_ID user +RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID user +USER user + +# Add binaries to path +ENV PATH /usr/local/bin/OpenMVS:$PATH diff --git a/docker/buildFromScratch.sh b/docker/buildFromScratch.sh index 2f3e1efa4..d522ce5d6 100755 --- a/docker/buildFromScratch.sh +++ b/docker/buildFromScratch.sh @@ -1,2 +1,2 @@ -docker build -t="openmvs-ubuntu" .; +docker build --no-cache -t="openmvs-ubuntu" --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) .; docker run -w /working -v $1:/working -it openmvs-ubuntu; \ No newline at end of file diff --git a/libs/Common/AABB.h b/libs/Common/AABB.h index 63c707181..61904896b 100644 --- a/libs/Common/AABB.h +++ b/libs/Common/AABB.h @@ -56,8 +56,8 @@ class TAABB inline bool IsEmpty() const; - inline void Enlarge(TYPE); - inline void EnlargePercent(TYPE); + inline TAABB& Enlarge(TYPE); + inline TAABB& EnlargePercent(TYPE); void InsertFull(const POINT&); void Insert(const POINT&); @@ -88,6 +88,15 @@ class TAABB inline TYPE& operator [] (BYTE i) { ASSERT(i + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & ptMin; + ar & ptMax; + } + #endif }; // class TAABB /*----------------------------------------------------------------*/ diff --git a/libs/Common/AABB.inl b/libs/Common/AABB.inl index b9040cfea..d10543e38 100644 --- a/libs/Common/AABB.inl +++ b/libs/Common/AABB.inl @@ -99,16 +99,18 @@ inline bool TAABB::IsEmpty() const template -inline void TAABB::Enlarge(TYPE x) +inline TAABB& TAABB::Enlarge(TYPE x) { ptMin.array() -= x; ptMax.array() += x; + return *this; } template -inline void TAABB::EnlargePercent(TYPE x) +inline TAABB& TAABB::EnlargePercent(TYPE x) { ptMin *= x; ptMax *= x; + return *this; } // Enlarge /*----------------------------------------------------------------*/ diff --git a/libs/Common/CMakeLists.txt b/libs/Common/CMakeLists.txt index 2e6c1a408..18899afde 100644 --- a/libs/Common/CMakeLists.txt +++ b/libs/Common/CMakeLists.txt @@ -1,20 +1,15 @@ # List sources files -FILE(GLOB PCH_C "Common.cpp") - FILE(GLOB LIBRARY_FILES_C "*.cpp") FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -# Place Common.cpp as the first file in the list -# needed by cotire when setting PCH manually -LIST(REMOVE_ITEM LIBRARY_FILES_C ${PCH_C}) -SET(LIBRARY_FILES_C "${PCH_C};${LIBRARY_FILES_C}") - -cxx_library_with_type_no_pch(Common "Libs" "STATIC" "${cxx_default}" +cxx_library_with_type(Common "Libs" "" "${cxx_default}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H} ) # Manually set Common.h as the precompiled header -set_target_pch(Common Common.h) +IF(CMAKE_VERSION VERSION_GREATER_EQUAL 3.16.0) + TARGET_PRECOMPILE_HEADERS(Common PRIVATE "Common.h") +endif() # Link its dependencies TARGET_LINK_LIBRARIES(Common ${Boost_LIBRARIES} ${OpenCV_LIBS}) @@ -24,7 +19,7 @@ SET_TARGET_PROPERTIES(Common PROPERTIES PUBLIC_HEADER "${LIBRARY_FILES_H}") INSTALL(TARGETS Common EXPORT OpenMVSTargets - RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin - LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib - ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" COMPONENT lib - PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/Common" COMPONENT dev) + LIBRARY DESTINATION "${INSTALL_LIB_DIR}" + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" + PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/Common") diff --git a/libs/Common/Common.cpp b/libs/Common/Common.cpp index 052951d47..54ccc8aed 100644 --- a/libs/Common/Common.cpp +++ b/libs/Common/Common.cpp @@ -25,12 +25,23 @@ String g_strWorkingFolderFull; #ifdef _USE_BOOST #ifdef BOOST_NO_EXCEPTIONS +#if (BOOST_VERSION / 100000) > 1 || (BOOST_VERSION / 100 % 1000) > 72 +#include +#endif namespace boost { void throw_exception(std::exception const & e) { VERBOSE("exception thrown: %s", e.what()); ASSERT("boost exception thrown" == NULL); exit(EXIT_FAILURE); } + #if (BOOST_VERSION / 100000) > 1 || (BOOST_VERSION / 100 % 1000) > 72 + void throw_exception(std::exception const & e, boost::source_location const & loc) { + std::ostringstream ostr; ostr << loc; + VERBOSE("exception thrown at %s: %s", ostr.str().c_str(), e.what()); + ASSERT("boost exception thrown" == NULL); + exit(EXIT_FAILURE); + } + #endif } // namespace boost #endif #endif diff --git a/libs/Common/Config.h b/libs/Common/Config.h index 521355e27..37fa297e5 100644 --- a/libs/Common/Config.h +++ b/libs/Common/Config.h @@ -50,6 +50,27 @@ #define _USE_MATH_DEFINES +#if _MSC_VER >= 1400 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS 1 +#endif +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE 1 +#endif +#ifndef _ATL_SECURE_NO_DEPRECATE +#define _ATL_SECURE_NO_DEPRECATE 1 +#endif +#ifndef _CRT_NON_CONFORMING_SWPRINTFS +#define _CRT_NON_CONFORMING_SWPRINTFS 1 +#endif +#ifndef _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES +#define _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 0 // disable automatically overloading CPP names to secure versions +#endif +#if 0 && defined(_DEBUG) && !defined(_ITERATOR_DEBUG_LEVEL) // might not build if linking statically to 3rd party libraries +#define _ITERATOR_DEBUG_LEVEL 1 // disable std iterator debugging even in Debug, as it is very slow +#endif +#endif + //---------------------------------------------------------------------- // For Microsoft Visual C++, externally accessible symbols must be @@ -120,33 +141,19 @@ #endif // _MSC_VER -#if !defined(_DEBUG) && !defined(_PROFILE) -#define _RELEASE // exclude code useful only for debug +#if defined(__arm__) || defined (__arm64__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARMT) +#define _PLATFORM_ARM 1 +#else +#define _PLATFORM_X86 1 #endif -#if defined(_MSC_VER) && _MSC_VER >= 1400 -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS 1 -#endif -#ifndef _CRT_SECURE_NO_DEPRECATE -#define _CRT_SECURE_NO_DEPRECATE 1 -#endif -#ifndef _ATL_SECURE_NO_DEPRECATE -#define _ATL_SECURE_NO_DEPRECATE 1 -#endif -#ifndef _CRT_NON_CONFORMING_SWPRINTFS -#define _CRT_NON_CONFORMING_SWPRINTFS 1 -#endif -#ifndef _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES -#define _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 0 // disable automatically overloading CPP names to secure versions -#endif -#if 0 && defined(_DEBUG) && !defined(_ITERATOR_DEBUG_LEVEL) // might not build if linking statically to 3rd party libraries -#define _ITERATOR_DEBUG_LEVEL 1 // disable std iterator debugging even in Debug, as it is very slow -#endif +#if !defined(_DEBUG) && !defined(_PROFILE) +#define _RELEASE // exclude code useful only for debug #endif + //optimization flags #if defined(_MSC_VER) # define ALIGN(n) __declspec(align(n)) @@ -204,7 +211,7 @@ #ifdef _MSC_VER #define _DEBUGINFO #define _CRTDBG_MAP_ALLOC //enable this to show also the filename (DEBUG_NEW should also be defined in each file) -#include +#include #include #ifdef _INC_CRTDBG #define ASSERT(exp) {if (!(exp) && 1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, NULL, #exp)) _CrtDbgBreak();} diff --git a/libs/Common/ConfigTable.h b/libs/Common/ConfigTable.h index d112a5ea1..337512108 100644 --- a/libs/Common/ConfigTable.h +++ b/libs/Common/ConfigTable.h @@ -56,7 +56,7 @@ class GENERAL_API CConfigTable const SML& GetConfig() const { return m_oSML; } SML& GetConfig() { return m_oSML; } inline SMLVALUE& operator[] (const String& name) { return m_oSML[name]; } - inline IDX InsertChild(CConfigTable& oCfg) { oCfg.SetParent(this); return m_oSML.InsertChild((const LPSML)&oCfg.m_oSML); } + inline IDX InsertChild(CConfigTable& oCfg) { oCfg.SetParent(this); return m_oSML.InsertChild(&oCfg.m_oSML); } inline void RemoveChild(CConfigTable& oCfg) { oCfg.SetParent(NULL); m_oSML.RemoveChild(oCfg.m_oSML.GetName()); } // misc methods diff --git a/libs/Common/File.h b/libs/Common/File.h index f369bb5df..9c4be9a7f 100644 --- a/libs/Common/File.h +++ b/libs/Common/File.h @@ -441,7 +441,7 @@ class GENERAL_API File : public IOStream { if (flags & NOBUFFER) m |= O_DIRECT; #endif - h = ::open(aFileName, m, S_IRUSR | S_IWUSR); + h = ::open(aFileName, m, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); } bool isOpen() { return h != -1; }; diff --git a/libs/Common/HalfFloat.h b/libs/Common/HalfFloat.h index 80a0a4c9a..3f38c831a 100644 --- a/libs/Common/HalfFloat.h +++ b/libs/Common/HalfFloat.h @@ -39,10 +39,6 @@ class GENERAL_API hfloat template inline hfloat(T v) : val(fromFloat(static_cast(v))) {} - inline hfloat& operator = (hfloat v) { - val = v.val; - return *this; - } inline hfloat& operator = (float v) { val = fromFloat(v); return *this; diff --git a/libs/Common/List.h b/libs/Common/List.h index 7b714308c..750aa90c4 100644 --- a/libs/Common/List.h +++ b/libs/Common/List.h @@ -11,6 +11,8 @@ // I N C L U D E S ///////////////////////////////////////////////// +#include + // D E F I N E S /////////////////////////////////////////////////// @@ -70,6 +72,10 @@ #define CLISTDEFIDX(TYPE,IDXTYPE) SEACAVE::cList< TYPE, const TYPE&, 1, 16, IDXTYPE > #define CLISTDEF2IDX(TYPE,IDXTYPE) SEACAVE::cList< TYPE, const TYPE&, 2, 16, IDXTYPE > +#ifndef STCALL +#define STCALL +#endif + namespace SEACAVE { @@ -137,6 +143,15 @@ class cList _ArrayCopyConstruct(_vector, rList._vector, _size); } + // constructor a list from a raw data array + explicit inline cList(TYPE* pDataBegin, TYPE* pDataEnd) : _size((IDX)(pDataEnd-pDataBegin)), _vectorSize(_size) + { + if (_vectorSize == 0) + return; + _vector = (TYPE*) operator new[] (_vectorSize * sizeof(TYPE)); + _ArrayCopyConstruct(_vector, pDataBegin, _size); + } + // constructor a list from a raw data array, taking ownership of the array memory explicit inline cList(IDX nSize, TYPE* pData) : _size(nSize), _vectorSize(nSize), _vector(pData) { diff --git a/libs/Common/OBB.h b/libs/Common/OBB.h index 30ff5fcb9..766257b2c 100644 --- a/libs/Common/OBB.h +++ b/libs/Common/OBB.h @@ -42,16 +42,19 @@ class TOBB enum { numCorners = (DIMS==1 ? 2 : (DIMS==2 ? 4 : 8)) }; // 2^DIMS enum { numScalar = (5*DIMS) }; - MATRIX m_rot; // rotation matrix of the transformation (orthonormal axes) - POINT m_pos; // translation of the transformation (center-point) - POINT m_ext; // bounding box extents (half axis length) + MATRIX m_rot; // rotation matrix from world to local (orthonormal axes) + POINT m_pos; // translation from local to world (center-point) + POINT m_ext; // bounding box extents in local (half axis length) //--------------------------------------- inline TOBB() {} + inline TOBB(bool); + inline TOBB(const MATRIX& rot, const POINT& ptMin, const POINT& ptMax); inline TOBB(const POINT* pts, size_t n); inline TOBB(const POINT* pts, size_t n, const TRIANGLE* tris, size_t s); + inline void Set(const MATRIX& rot, const POINT& ptMin, const POINT& ptMax); // build from rotation matrix from world to local, and local min/max corners inline void Set(const POINT* pts, size_t n); // build from points inline void Set(const POINT* pts, size_t n, const TRIANGLE* tris, size_t s); // build from triangles inline void Set(const MATRIX& C, const POINT* pts, size_t n); // build from covariance matrix @@ -62,6 +65,8 @@ class TOBB inline void BuildAdd(const POINT&); // add a new point to the online build inline void BuildEnd(); // end online build for computing the rotation + inline bool IsValid() const; + inline void Enlarge(TYPE); inline void EnlargePercent(TYPE); @@ -79,8 +84,20 @@ class TOBB inline TYPE GetVolume() const; + bool Intersects(const POINT&) const; + inline TYPE& operator [] (BYTE i) { ASSERT(i + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & m_rot; + ar & m_pos; + ar & m_ext; + } + #endif }; // class TOBB /*----------------------------------------------------------------*/ diff --git a/libs/Common/OBB.inl b/libs/Common/OBB.inl index 94fe16566..7596b171d 100644 --- a/libs/Common/OBB.inl +++ b/libs/Common/OBB.inl @@ -11,6 +11,19 @@ // S T R U C T S /////////////////////////////////////////////////// +template +inline TOBB::TOBB(bool) + : + m_rot(MATRIX::Identity()), + m_pos(POINT::Zero()), + m_ext(POINT::Zero()) +{ +} +template +inline TOBB::TOBB(const MATRIX& rot, const POINT& ptMin, const POINT& ptMax) +{ + Set(rot, ptMin, ptMax); +} template inline TOBB::TOBB(const POINT* pts, size_t n) { @@ -24,6 +37,15 @@ inline TOBB::TOBB(const POINT* pts, size_t n, const TRIANGLE* tris, s /*----------------------------------------------------------------*/ +// build from rotation matrix from world to local, and local min/max corners +template +inline void TOBB::Set(const MATRIX& rot, const POINT& ptMin, const POINT& ptMax) +{ + m_rot = rot; + m_pos = (ptMax + ptMin) * TYPE(0.5); + m_ext = (ptMax - ptMin) * TYPE(0.5); +} + // Inspired from "Fitting Oriented Bounding Boxes" by James Gregson // http://jamesgregson.blogspot.ro/2011/03/latex-test.html @@ -140,24 +162,12 @@ template inline void TOBB::SetRotation(const MATRIX& C) { // extract the eigenvalues and eigenvectors from C - const Eigen::EigenSolver es(C); - if (es.info() != Eigen::Success) - return; - const MATRIX eigvec(es.eigenvectors().real()); - const POINT eigval(es.eigenvalues().real()); - int indices[3] = {0,1,2}; - std::sort(indices, indices+3, [&] (int i, int j) { - return eigval(i) < eigval(j); - }); - + const Eigen::SelfAdjointEigenSolver es(C); + ASSERT(es.info() == Eigen::Success); // find the right, up and forward vectors from the eigenvectors // and set the rotation matrix using the eigenvectors - m_rot.row(0) = eigvec.col(indices[0]); - m_rot.row(1) = eigvec.col(indices[1]); - m_rot.row(2) = eigvec.col(indices[2]); - ASSERT(ISEQUAL(m_rot.row(0).norm(), TYPE(1))); - ASSERT(ISEQUAL(m_rot.row(1).norm(), TYPE(1))); - ASSERT(ISEQUAL(m_rot.row(2).norm(), TYPE(1))); + ASSERT(es.eigenvalues()(0) < es.eigenvalues()(1) && es.eigenvalues()(1) < es.eigenvalues()(2)); + m_rot = es.eigenvectors().transpose(); if (m_rot.determinant() < 0) m_rot = -m_rot; } @@ -235,6 +245,15 @@ inline void TOBB::BuildEnd() /*----------------------------------------------------------------*/ +// check if the oriented bounding box has positive size +template +inline bool TOBB::IsValid() const +{ + return m_ext.minCoeff() > TYPE(0); +} // IsValid +/*----------------------------------------------------------------*/ + + template inline void TOBB::Enlarge(TYPE x) { @@ -248,6 +267,22 @@ inline void TOBB::EnlargePercent(TYPE x) /*----------------------------------------------------------------*/ +// Update the box by the given pos delta. +template +inline void TOBB::Translate(const POINT& d) +{ + m_pos += d; +} +// Update the box by the given transform. +template +inline void TOBB::Transform(const MATRIX& m) +{ + m_rot = m * m_rot; + m_pos = m * m_pos; +} +/*----------------------------------------------------------------*/ + + template inline typename TOBB::POINT TOBB::GetCenter() const { @@ -294,12 +329,12 @@ inline void TOBB::GetCorners(POINT pts[numCorners]) const m_rot.row(2)*m_ext[2] }; pts[0] = m_pos - pEAxis[0] - pEAxis[1] - pEAxis[2]; - pts[1] = m_pos + pEAxis[0] - pEAxis[1] - pEAxis[2]; - pts[2] = m_pos + pEAxis[0] + pEAxis[1] - pEAxis[2]; - pts[3] = m_pos - pEAxis[0] + pEAxis[1] - pEAxis[2]; - pts[4] = m_pos - pEAxis[0] - pEAxis[1] + pEAxis[2]; - pts[5] = m_pos + pEAxis[0] - pEAxis[1] + pEAxis[2]; - pts[6] = m_pos + pEAxis[0] + pEAxis[1] + pEAxis[2]; + pts[1] = m_pos - pEAxis[0] - pEAxis[1] + pEAxis[2]; + pts[2] = m_pos + pEAxis[0] - pEAxis[1] - pEAxis[2]; + pts[3] = m_pos + pEAxis[0] - pEAxis[1] + pEAxis[2]; + pts[4] = m_pos + pEAxis[0] + pEAxis[1] - pEAxis[2]; + pts[5] = m_pos + pEAxis[0] + pEAxis[1] + pEAxis[2]; + pts[6] = m_pos - pEAxis[0] + pEAxis[1] - pEAxis[2]; pts[7] = m_pos - pEAxis[0] + pEAxis[1] + pEAxis[2]; } } // GetCorners @@ -307,6 +342,7 @@ inline void TOBB::GetCorners(POINT pts[numCorners]) const template inline typename TOBB::AABB TOBB::GetAABB() const { + #if 0 if (DIMS == 2) { const POINT pEAxis[2] = { m_rot.row(0)*m_ext[0], @@ -328,6 +364,11 @@ inline typename TOBB::AABB TOBB::GetAABB() const m_pos + pEAxis[0] + pEAxis[1] + pEAxis[2] ); } + #else + POINT pts[numCorners]; + GetCorners(pts); + return AABB(pts, numCorners); + #endif } // GetAABB /*----------------------------------------------------------------*/ @@ -341,20 +382,18 @@ inline TYPE TOBB::GetVolume() const /*----------------------------------------------------------------*/ -// Update the box by the given pos delta. template -inline void TOBB::Translate(const POINT& d) +bool TOBB::Intersects(const POINT& pt) const { - m_pos += d; -} -/*----------------------------------------------------------------*/ - - -// Update the box by the given transform. -template -inline void TOBB::Transform(const MATRIX& m) -{ - m_rot = m * m_rot; - m_pos = m * m_pos; -} + const POINT dist(m_rot * (pt - m_pos)); + if (DIMS == 2) { + return ABS(dist[0]) <= m_ext[0] + && ABS(dist[1]) <= m_ext[1]; + } + if (DIMS == 3) { + return ABS(dist[0]) <= m_ext[0] + && ABS(dist[1]) <= m_ext[1] + && ABS(dist[2]) <= m_ext[2]; + } +} // Intersects(POINT) /*----------------------------------------------------------------*/ diff --git a/libs/Common/Octree.h b/libs/Common/Octree.h index cc5f22e95..70acd14c6 100644 --- a/libs/Common/Octree.h +++ b/libs/Common/Octree.h @@ -22,106 +22,82 @@ namespace SEACAVE { // S T R U C T S /////////////////////////////////////////////////// -// raw array wrapper -template -class TItemArr -{ -public: - typedef TYPE Type; - - inline TItemArr() {} - inline TItemArr(const TYPE* data, IDX size) : m_data(data), m_size(size) {} - inline void Set(const TYPE* data, IDX size) { m_data = data; m_size = size; } - - inline const TYPE& operator[](IDX i) const { return m_data[i]; } - inline const TYPE* Begin() const { return m_data; } - inline const TYPE* GetData() const { return m_data; } - inline IDX GetSize() const { return m_size; } - -protected: - const TYPE* m_data; - IDX m_size; -}; // class TItemArr -/*----------------------------------------------------------------*/ - - -// octree cell class -template -class TOctreeCell +// basic octree class +// each item should define the operator const POINT_TYPE& returning its center; +// at build time, a functor must be supplied that returns true as long as +// the current cell should be split farther, given its number of items and radius, ex: +// [](Octree::IDX_TYPE size, Octree::Type radius) { +// return size > SIZE && radius > RADIUS; +// } +// where: +// SIZE is the minimum number of items contained by the cell so that this to be divided further +// RADIUS is the minimum size of the cell allowed to be divided further +// both conditions represent exclusive limits and both should be true for the division to take place +template +class TOctree { STATIC_ASSERT(DIMS > 0 && DIMS <= 3); public: + typedef TYPE Type; + typedef typename ITEMARR_TYPE::Type ITEM_TYPE; + typedef typename ITEMARR_TYPE::IDX IDX_TYPE; + typedef SEACAVE::cList IDXARR_TYPE; typedef Eigen::Matrix POINT_TYPE; typedef SEACAVE::TAABB AABB_TYPE; typedef uint32_t SIZE_TYPE; - typedef struct { - POINT_TYPE center; // center of the current cell - } NODE_TYPE; - typedef struct { - IDX idxBegin; // index in the global array of the first item contained by this cell - SIZE_TYPE size; // number of items contained by this cell in the global array - DATA_TYPE data; // user data associated with this leaf - } LEAF_TYPE; - enum { dataSize = (sizeof(NODE_TYPE)>sizeof(LEAF_TYPE) ? sizeof(NODE_TYPE) : sizeof(LEAF_TYPE)) }; - enum { numChildren = (2<<(DIMS-1)) }; - -public: - inline TOctreeCell(); - inline TOctreeCell(TOctreeCell*); - inline ~TOctreeCell(); - - inline void Release(); - inline void Swap(TOctreeCell&); - - inline unsigned ComputeChild(const POINT_TYPE& item) const; - static void ComputeCenter(POINT_TYPE []); - - inline bool IsLeaf() const { return (m_child==NULL); } - inline const TOctreeCell& GetChild(int i) const { ASSERT(!IsLeaf() && i -class TOctree -{ -public: - typedef TYPE Type; - typedef TOctreeCell CELL_TYPE; - typedef typename ITEMARR_TYPE::Type ITEM_TYPE; - typedef SEACAVE::cList IDXARR_TYPE; - typedef SEACAVE::cList CELLPTRARR_TYPE; - typedef typename IDXARR_TYPE::Type IDX_TYPE; - typedef typename CELL_TYPE::POINT_TYPE POINT_TYPE; - typedef typename CELL_TYPE::AABB_TYPE AABB_TYPE; + class CELL_TYPE { + public: + typedef struct { + POINT_TYPE center; // center of the current cell + } NODE_TYPE; + typedef struct { + IDX_TYPE idxBegin; // index in the global array of the first item contained by this cell + SIZE_TYPE size; // number of items contained by this cell in the global array + DATA_TYPE data; // user data associated with this leaf + } LEAF_TYPE; + enum { dataSize = (sizeof(NODE_TYPE)>sizeof(LEAF_TYPE) ? sizeof(NODE_TYPE) : sizeof(LEAF_TYPE)) }; + enum { numChildren = (2<<(DIMS-1)) }; + + public: + inline CELL_TYPE(); + inline ~CELL_TYPE(); + + inline void Release(); + inline void Swap(CELL_TYPE&); + + inline unsigned ComputeChild(const POINT_TYPE& item) const; + static void ComputeCenter(POINT_TYPE []); + static inline POINT_TYPE ComputeChildCenter(const POINT_TYPE&, TYPE, unsigned); + + inline bool IsLeaf() const { return (m_child==NULL); } + inline const CELL_TYPE& GetChild(int i) const { ASSERT(!IsLeaf() && i(m_data); } + inline NODE_TYPE& Node() { ASSERT(!IsLeaf()); return *reinterpret_cast(m_data); } + inline const LEAF_TYPE& Leaf() const { ASSERT(IsLeaf()); return *reinterpret_cast(m_data); } + inline LEAF_TYPE& Leaf() { ASSERT(IsLeaf()); return *reinterpret_cast(m_data); } + inline const POINT_TYPE& GetCenter() const { return Node().center; } + inline AABB_TYPE GetAabb(TYPE radius) const { return AABB_TYPE(Node().center, radius); } + inline AABB_TYPE GetChildAabb(unsigned idxChild, TYPE radius) const { const TYPE childRadius(radius / TYPE(2)); return AABB_TYPE(ComputeChildCenter(Node().center, childRadius, idxChild), childRadius); } + inline IDX_TYPE GetFirstItemIdx() const { return Leaf().idxBegin; } + inline IDX_TYPE GetLastItemIdx() const { return Leaf().idxBegin + Leaf().size; } + inline SIZE_TYPE GetNumItems() const { return Leaf().size; } + inline const DATA_TYPE& GetUserData() const { return Leaf().data; } + inline DATA_TYPE& GetUserData() { return Leaf().data; } + size_t GetNumItemsHeld() const; + + public: + CELL_TYPE* m_child; // if not a leaf, 2^DIMS child objects + uint8_t m_data[dataSize]; // a LEAF_TYPE or NODE_TYPE object, if it is a leaf or not respectively + }; + typedef SEACAVE::cList CELLPTRARR_TYPE; struct IndexInserter { IDXARR_TYPE& indices; IndexInserter(IDXARR_TYPE& _indices) : indices(_indices) {} void operator()(IDX_TYPE idx) { indices.Insert(idx); } - void operator()(const IDX_TYPE* idices, size_t size) { indices.Join(idices, size); } + void operator()(const IDX_TYPE* idices, SIZE_TYPE size) { indices.Join(idices, size); } }; struct CellInserter { @@ -132,17 +108,23 @@ class TOctree public: inline TOctree() {} - inline TOctree(const ITEMARR_TYPE&); - inline TOctree(const ITEMARR_TYPE&, const AABB_TYPE&); + template + inline TOctree(const ITEMARR_TYPE&, Functor split); + template + inline TOctree(const ITEMARR_TYPE&, const AABB_TYPE&, Functor split); inline void Release(); inline void Swap(TOctree&); - void Insert(const ITEMARR_TYPE&); - void Insert(const ITEMARR_TYPE&, const AABB_TYPE&); + template + void Insert(const ITEMARR_TYPE&, Functor split); + template + void Insert(const ITEMARR_TYPE&, const AABB_TYPE&, Functor split); template inline void CollectCells(INSERTER&) const; + template + void CollectCells(const CELL_TYPE&, INSERTER&) const; template inline void ParseCells(PARSER&); @@ -170,30 +152,29 @@ class TOctree template inline void TraverseCells(const TFrustum&, CELLPTRARR_TYPE&); + template + void SplitVolume(float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter); + + 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 TYPE GetRadius(const AABB_TYPE& aabb) const { - // radius of the root cell - const POINT_TYPE size(aabb.GetSize() / TYPE(2)); - TYPE radius = size[0]; - if (DIMS > 1 && radius < size[1]) - radius = size[1]; - if (DIMS > 2 && radius < size[2]) - radius = size[2]; - return radius; - } inline bool IsEmpty() const { return m_indices.IsEmpty(); } inline size_t GetNumItems() const { return m_indices.GetSize(); } 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: - static inline POINT_TYPE ComputeChildCenter(const POINT_TYPE&, TYPE, unsigned); - - void _Insert(CELL_TYPE&, TYPE, IDXARR_TYPE []); - void _Insert(CELL_TYPE&, const POINT_TYPE&, TYPE, IDXARR_TYPE&); + template + struct _InsertData { + enum : IDX_TYPE { NO_INDEX = DECLARE_NO_INDEX(IDX_TYPE) }; + IDXARR_TYPE successors; // single connected list of next item indices + Functor split; // used to decide if a cell needs to be split farther + }; + template + void _Insert(CELL_TYPE&, const POINT_TYPE& center, TYPE radius, IDX_TYPE start, IDX_TYPE size, _InsertData&); - template - void _CollectCells(const CELL_TYPE&, INSERTER&) const; template void _ParseCells(CELL_TYPE&, TYPE, PARSER&); @@ -207,6 +188,9 @@ class TOctree template void _TraverseCells(CELL_TYPE&, TYPE, const TFrustum&, PARSER&); + template + void _SplitVolume(const CELL_TYPE& parentCell, TYPE parentRadius, unsigned idxChild, float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter, const UnsignedArr& indices=UnsignedArr{0,1,2,3,4,5,6,7}); + protected: const ITEM_TYPE* m_items; // original input items (the only condition is that every item to resolve to a position) IDXARR_TYPE m_indices; // indices to input items re-arranged spatially (as dictated by the octree) diff --git a/libs/Common/Octree.inl b/libs/Common/Octree.inl index 83075ebb3..e0e0f298b 100644 --- a/libs/Common/Octree.inl +++ b/libs/Common/Octree.inl @@ -8,53 +8,37 @@ // D E F I N E S /////////////////////////////////////////////////// -// max number of items in one cell -#define OCTREE_CELLITEMS SIZE - #ifdef _USE_OPENMP // minimum number of polygons for which we do multi-threading #define OCTREE_MIN_ITEMS_MINTHREAD 1024*2 #endif -// size of a cell -#define OCTREE_CELLSIZE (TYPE(NOM)/TYPE(DENOM)) -// radius of a cell -#define OCTREE_CELLRADIUS (OCTREE_CELLSIZE/2) - // S T R U C T S /////////////////////////////////////////////////// -template -inline TOctreeCell::TOctreeCell() +template +inline TOctree::CELL_TYPE::CELL_TYPE() : m_child(NULL) { } // constructor -template -inline TOctreeCell::TOctreeCell(TOctreeCell* children) - : - m_child(children) -{ -} // constructor -/*----------------------------------------------------------------*/ - -template -inline TOctreeCell::~TOctreeCell() +template +inline TOctree::CELL_TYPE::~CELL_TYPE() { delete[] m_child; } // destructor /*----------------------------------------------------------------*/ -template -inline void TOctreeCell::Release() +template +inline void TOctree::CELL_TYPE::Release() { delete[] m_child; m_child = NULL; -} // destructor +} // Release // swap the two octrees -template -inline void TOctreeCell::Swap(TOctreeCell& rhs) +template +inline void TOctree::CELL_TYPE::Swap(CELL_TYPE& rhs) { std::swap(m_child, rhs.m_child); uint8_t tmpData[dataSize]; @@ -66,8 +50,8 @@ inline void TOctreeCell::Swap(TOctreeCell& rhs) // compute item's index corresponding to the containing cell -template -inline unsigned TOctreeCell::ComputeChild(const POINT_TYPE& item) const +template +inline unsigned TOctree::CELL_TYPE::ComputeChild(const POINT_TYPE& item) const { ASSERT(!IsLeaf()); unsigned idx = 0; @@ -83,8 +67,8 @@ inline unsigned TOctreeCell::ComputeChild(const POINT_TYPE& } // ComputeChild /*----------------------------------------------------------------*/ -template -void TOctreeCell::ComputeCenter(POINT_TYPE centers[]) +template +void TOctree::CELL_TYPE::ComputeCenter(POINT_TYPE centers[]) { if (DIMS == 1) { centers[0] << -1; @@ -109,10 +93,22 @@ void TOctreeCell::ComputeCenter(POINT_TYPE centers[]) } // ComputeCenter /*----------------------------------------------------------------*/ +template +inline typename TOctree::POINT_TYPE TOctree::CELL_TYPE::ComputeChildCenter(const POINT_TYPE& center, TYPE radius, unsigned idxChild) +{ + struct CENTERARR_TYPE { + POINT_TYPE child[CELL_TYPE::numChildren]; + inline CENTERARR_TYPE() { CELL_TYPE::ComputeCenter(child); } + }; + static const CENTERARR_TYPE centers; + return center + centers.child[idxChild] * radius; +} // ComputeChildCenter +/*----------------------------------------------------------------*/ + // count the number of items contained by the given octree-cell -template -size_t TOctreeCell::GetNumItemsHeld() const +template +size_t TOctree::CELL_TYPE::GetNumItemsHeld() const { if (IsLeaf()) return GetNumItems(); @@ -128,29 +124,31 @@ size_t TOctreeCell::GetNumItemsHeld() const // S T R U C T S /////////////////////////////////////////////////// // build tree with the given items -template -inline TOctree::TOctree(const ITEMARR_TYPE& items) +template +template +inline TOctree::TOctree(const ITEMARR_TYPE& items, Functor split) { - Insert(items); + Insert(items, split); } -template -inline TOctree::TOctree(const ITEMARR_TYPE& items, const AABB_TYPE& aabb) +template +template +inline TOctree::TOctree(const ITEMARR_TYPE& items, const AABB_TYPE& aabb, Functor split) { - Insert(items, aabb); + Insert(items, aabb, split); } // constructor /*----------------------------------------------------------------*/ // destroy tree -template -inline void TOctree::Release() +template +inline void TOctree::Release() { m_indices.Release(); m_root.Release(); } // Release // swap the two octrees -template -inline void TOctree::Swap(TOctree& rhs) +template +inline void TOctree::Swap(TOctree& rhs) { std::swap(m_items, rhs.m_items); m_indices.Swap(rhs.m_indices); @@ -160,122 +158,108 @@ inline void TOctree::Swap(TOctr /*----------------------------------------------------------------*/ -// destroy tree -template -inline typename TOctree::POINT_TYPE TOctree::ComputeChildCenter(const POINT_TYPE& center, TYPE radius, unsigned idxChild) -{ - struct CENTERARR_TYPE { - POINT_TYPE child[CELL_TYPE::numChildren]; - inline CENTERARR_TYPE() { CELL_TYPE::ComputeCenter(child); } - }; - static const CENTERARR_TYPE centers; - return center + centers.child[idxChild] * radius; -} // ComputeChildCenter -/*----------------------------------------------------------------*/ - // add the given item to the tree -template -void TOctree::_Insert(CELL_TYPE& cell, TYPE radius, IDXARR_TYPE childrenIndices[]) -{ - ASSERT(!cell.IsLeaf()); - // setup each cell - const POINT_TYPE& center = cell.Node().center; - const TYPE childRadius = radius / TYPE(2); - for (int i=0; i OCTREE_CELLITEMS && (NOM == 0 || childRadius > OCTREE_CELLRADIUS)) { - // proceed recursively - const POINT_TYPE childCenter(ComputeChildCenter(center, childRadius, i)); - _Insert(child, childCenter, childRadius, childIndices); - } else { - // init leaf - typename CELL_TYPE::LEAF_TYPE& leaf = child.Leaf(); - leaf.idxBegin = m_indices.GetSize(); - leaf.size = (typename CELL_TYPE::SIZE_TYPE)childIndices.GetSize(); - // store items - m_indices.Join(childIndices); - childIndices.Release(); +template +template +void TOctree::_Insert(CELL_TYPE& cell, const POINT_TYPE& center, TYPE radius, IDX_TYPE start, IDX_TYPE size, _InsertData& insertData) +{ + ASSERT(size > 0); + // if this child cell needs to be divided further + if (bForceSplit || insertData.split(size, radius)) { + // init node and proceed recursively + cell.m_child = new CELL_TYPE[CELL_TYPE::numChildren]; + cell.Node().center = center; + struct ChildData { + enum { ESTART=0, EEND=CELL_TYPE::numChildren, ESIZE=CELL_TYPE::numChildren*2, EALL=CELL_TYPE::numChildren*3}; + IDX_TYPE data[EALL]; + ChildData() { memset(data, 0, sizeof(IDX_TYPE)*EALL); } + inline IDX_TYPE Start(unsigned i) const { return data[ESTART+i]; } + inline IDX_TYPE& Start(unsigned i) { return data[ESTART+i]; } + inline IDX_TYPE End(unsigned i) const { return data[EEND+i]; } + inline IDX_TYPE& End(unsigned i) { return data[EEND+i]; } + inline IDX_TYPE Size(unsigned i) const { return data[ESIZE+i]; } + inline IDX_TYPE& Size(unsigned i) { return data[ESIZE+i]; } + } childD; + IDX_TYPE idx(start); + for (IDX_TYPE i=0; i::NO_INDEX); + const TYPE childRadius(radius / TYPE(2)); + for (unsigned i=0; i::NO_INDEX; // mark the end of child successors + const POINT_TYPE childCenter(CELL_TYPE::ComputeChildCenter(center, childRadius, i)); + _Insert(child, childCenter, childRadius, childD.Start(i), childD.Size(i), insertData); + } + } else { + // init leaf + cell.Leaf().idxBegin = m_indices.size(); + cell.Leaf().size = (SIZE_TYPE)size; + for (IDX_TYPE idx=start; idx!=_InsertData::NO_INDEX; idx=insertData.successors[idx]) + m_indices.push_back(idx); } } // _Insert -template -void TOctree::_Insert(CELL_TYPE& cell, const POINT_TYPE& center, TYPE radius, IDXARR_TYPE& indices) -{ - ASSERT(cell.IsLeaf()); - ASSERT(indices.GetSize() > OCTREE_CELLITEMS); - ASSERT(NOM == 0 || radius > OCTREE_CELLRADIUS); - // divide cell - // transform this cell in node - cell.m_child = new CELL_TYPE[CELL_TYPE::numChildren]; - cell.Node().center = center; - // divide the items in separate indices lists corresponding to each child - const size_t reserveSize = indices.GetSize() / 3; - IDXARR_TYPE childrenIndices[CELL_TYPE::numChildren]; - for (int i=0; i -inline void TOctree::Insert(const ITEMARR_TYPE& items, const AABB_TYPE& aabb) +template +template +inline void TOctree::Insert(const ITEMARR_TYPE& items, const AABB_TYPE& aabb, Functor split) { Release(); - m_items = items.Begin(); - m_radius = GetRadius(aabb); - // build tree + m_items = items.data(); // create root as node, even if we do not need to divide - m_indices.Reserve(items.GetSize()); + m_indices.Reserve(items.size()); // divide cell m_root.m_child = new CELL_TYPE[CELL_TYPE::numChildren]; m_root.Node().center = aabb.GetCenter(); - // divide the items in separate indices lists corresponding to each child - const size_t reserveSize = items.GetSize() / 3; - IDXARR_TYPE childrenIndices[CELL_TYPE::numChildren]; - for (int i=0; i insertData = {items.size(), split}; + std::iota(insertData.successors.begin(), insertData.successors.end(), IDX_TYPE(1)); + insertData.successors.back() = _InsertData::NO_INDEX; // setup each cell - _Insert(m_root, m_radius, childrenIndices); + _Insert(m_root, m_root.GetCenter(), m_radius, 0, items.size(), insertData); } -template -inline void TOctree::Insert(const ITEMARR_TYPE& items) +template +template +inline void TOctree::Insert(const ITEMARR_TYPE& items, Functor split) { ASSERT(!items.IsEmpty()); - AABB_TYPE aabb; ASSERT(sizeof(POINT_TYPE) == sizeof(typename ITEMARR_TYPE::Type)); - aabb.Set((const POINT_TYPE*)items.Begin(), items.GetSize()); + AABB_TYPE aabb((const POINT_TYPE*)items.data(), items.size()); aabb.Enlarge(ZEROTOLERANCE()*TYPE(10)); - Insert(items, aabb); + Insert(items, aabb, split); } // Insert /*----------------------------------------------------------------*/ -template +template template -void TOctree::_CollectCells(const CELL_TYPE& cell, INSERTER& inserter) const +void TOctree::CollectCells(const CELL_TYPE& cell, INSERTER& inserter) const { if (cell.IsLeaf()) { - inserter(m_indices.Begin()+cell.Leaf().idxBegin, cell.GetNumItems()); + inserter(m_indices.data()+cell.GetFirstItemIdx(), cell.GetNumItems()); return; } for (int i=0; i +template template -void TOctree::_ParseCells(CELL_TYPE& cell, TYPE radius, PARSER& parser) +void TOctree::_ParseCells(CELL_TYPE& cell, TYPE radius, PARSER& parser) { if (cell.IsLeaf()) { parser(cell, radius); @@ -288,16 +272,16 @@ void TOctree::_ParseCells(CELL_ /*----------------------------------------------------------------*/ // calls parser for each leaf of the octree (the IDX_TYPE operator has to be defined) -template +template template -void TOctree::CollectCells(INSERTER& inserter) const +void TOctree::CollectCells(INSERTER& inserter) const { - _CollectCells(m_root, inserter); + CollectCells(m_root, inserter); } // calls parser for each leaf of the octree (the CELL_TYPE operator has to be defined) -template +template template -void TOctree::ParseCells(PARSER& parser) +void TOctree::ParseCells(PARSER& parser) { _ParseCells(m_root, m_radius, parser); } @@ -305,15 +289,15 @@ void TOctree::ParseCells(PARSER // find all items contained by the given bounding box -template +template template -void TOctree::_Collect(const CELL_TYPE& cell, const AABB_TYPE& aabb, INSERTER& inserter) const +void TOctree::_Collect(const CELL_TYPE& cell, const AABB_TYPE& aabb, INSERTER& inserter) const { if (cell.IsLeaf()) { // add all items contained by the bounding-box - for (IDX i=0; i::_Collect(const CE } } // Collect // find all items contained by the cells intersected by the given line -template +template template -void TOctree::_Collect(const CELL_TYPE& cell, TYPE radius, const COLLECTOR& collector, INSERTER& inserter) const +void TOctree::_Collect(const CELL_TYPE& cell, TYPE radius, const COLLECTOR& collector, INSERTER& inserter) const { ASSERT(!cell.IsLeaf()); const TYPE childRadius = radius / TYPE(2); for (int i=0; i::_Collect(const CE } // Collect /*----------------------------------------------------------------*/ -template +template template -inline void TOctree::Collect(INSERTER& inserter, const AABB_TYPE& aabb) const +inline void TOctree::Collect(INSERTER& inserter, const AABB_TYPE& aabb) const { _Collect(m_root, aabb, inserter); } -template -inline void TOctree::Collect(IDXARR_TYPE& indices, const AABB_TYPE& aabb) const +template +inline void TOctree::Collect(IDXARR_TYPE& indices, const AABB_TYPE& aabb) const { _Collect(m_root, aabb, IndexInserter(indices)); } -template +template template -inline void TOctree::Collect(INSERTER& inserter, const POINT_TYPE& center, TYPE radius) const +inline void TOctree::Collect(INSERTER& inserter, const POINT_TYPE& center, TYPE radius) const { _Collect(m_root, AABB_TYPE(center, radius), inserter); } -template -inline void TOctree::Collect(IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const +template +inline void TOctree::Collect(IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const { _Collect(m_root, AABB_TYPE(center, radius), IndexInserter(indices)); } -template +template template -inline void TOctree::Collect(INSERTER& inserter, const COLLECTOR& collector) const +inline void TOctree::Collect(INSERTER& inserter, const COLLECTOR& collector) const { _Collect(m_root, m_radius, collector, inserter); } -template +template template -inline void TOctree::Collect(IDXARR_TYPE& indices, const COLLECTOR& collector) const +inline void TOctree::Collect(IDXARR_TYPE& indices, const COLLECTOR& collector) const { _Collect(m_root, m_radius, collector, IndexInserter(indices)); } -template -inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const AABB_TYPE& aabb) const +template +inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const AABB_TYPE& aabb) const { _Collect(m_root, aabb, IndexInserter(indices)); - if (indices.GetSize() > maxNeighbors) { + if (indices.size() > maxNeighbors) { // keep only the closest neighbors typedef TIndexScore ItemIndexScore; typedef cList ItemIndexScoreArr; - ItemIndexScoreArr indexscores(indices.GetSize()); + ItemIndexScoreArr indexscores(indices.size()); const POINT_TYPE center(aabb.GetCenter()); FOREACH(i, indices) { const IDX_TYPE& idx = indices[i]; - const TYPE score(-(center-(const POINT_TYPE)m_items[idx]).squaredNorm()); + const TYPE score(-(center-GetItem(idx)).squaredNorm()); indexscores[i] = ItemIndexScore(idx,score); } indices.Empty(); indexscores.Sort(); - for (IDX i=0; i -inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const +template +inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const { Collect(maxNeighbors, indices, AABB_TYPE(center, radius)); } // Collect @@ -439,9 +423,9 @@ inline void TOctree::Collect(ID // walk through the tree and collect visible indices -template +template template -void TOctree::_Traverse(const CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, INSERTER& inserter) const +void TOctree::_Traverse(const CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, INSERTER& inserter) const { ASSERT(!cell.IsLeaf()); switch (frustum.Classify(cell.GetAabb(radius))) { @@ -450,9 +434,9 @@ void TOctree::_Traverse(const C for (int i=0; i::_Traverse(const C break; } case VISIBLE: { for (int i=0; i +template template -void TOctree::_TraverseCells(CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, PARSER& parser) +void TOctree::_TraverseCells(CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, PARSER& parser) { ASSERT(!cell.IsLeaf()); switch (frustum.Classify(cell.GetAabb(radius))) { @@ -476,7 +460,7 @@ void TOctree::_TraverseCells(CE for (int i=0; i::_TraverseCells(CE } } -template +template template -inline void TOctree::Traverse(const TFrustum& frustum, INSERTER& inserter) const +inline void TOctree::Traverse(const TFrustum& frustum, INSERTER& inserter) const { _Traverse(m_root, m_radius, frustum, inserter); } -template +template template -inline void TOctree::Traverse(const TFrustum& frustum, IDXARR_TYPE& indices) const +inline void TOctree::Traverse(const TFrustum& frustum, IDXARR_TYPE& indices) const { _Traverse(m_root, m_radius, frustum, IndexInserter(indices)); } -template +template template -inline void TOctree::TraverseCells(const TFrustum& frustum, PARSER& parser) +inline void TOctree::TraverseCells(const TFrustum& frustum, PARSER& parser) { _TraverseCells(m_root, m_radius, frustum, parser); } -template +template template -inline void TOctree::TraverseCells(const TFrustum& frustum, CELLPTRARR_TYPE& leaves) +inline void TOctree::TraverseCells(const TFrustum& frustum, CELLPTRARR_TYPE& leaves) { _TraverseCells(m_root, m_radius, frustum, CellInserter(leaves)); } // Traverse /*----------------------------------------------------------------*/ +template +template +void TOctree::_SplitVolume(const CELL_TYPE& parentCell, TYPE parentRadius, unsigned idxChild, float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter, const UnsignedArr& indices) +{ + ASSERT(!indices.empty()); + typedef std::pair PairIndices; + struct GenerateSamples { + const UnsignedArr& indices; + const unsigned numSamples; + const unsigned halfSamples; + const unsigned numCommonAxis; + POINT_TYPE centers[8]; + cList arrHalfIndices; + GenerateSamples(const UnsignedArr& _indices) + : indices(_indices), numSamples((unsigned)indices.size()), halfSamples(numSamples/2), numCommonAxis(halfSamples==4?1:2), arrHalfIndices(0, numSamples) + { + ASSERT(indices.size()%2 == 0 && indices.IsSorted()); + ASSERT(halfSamples == 4 || halfSamples == 2); + CELL_TYPE::ComputeCenter(centers); + UnsignedArr samples(halfSamples); + for (unsigned hs=0; hs= maxArea) + ++numOverAreas; + if (numOverAreas == indices.size()) { + for (unsigned c: indices) + if (childArea[c] > 0) + _SplitVolume(cell, radius, c, maxArea, areaEstimator, chunkInserter); + return; + } + // split mesh children and retain the components with surface smaller than the given area + const cList halfIndices(std::move(GenerateSamples(indices).arrHalfIndices)); + IDX bestSplit(NO_ID); + float bestArea(0); + Point2f bestAs; + FOREACH(idx, halfIndices) { + const PairIndices& pairHalfIndices = halfIndices[idx]; + ASSERT(pairHalfIndices.first.size() == pairHalfIndices.second.size()); + Point2f as(Point2f::ZERO); + for (unsigned i=0; i qIndicesFirst(std::move(GenerateSamples(pairHalfIndices.first).arrHalfIndices)); + const cList qIndicesSecond(std::move(GenerateSamples(pairHalfIndices.second).arrHalfIndices)); + ASSERT(qIndicesFirst.size() == qIndicesSecond.size()); + FOREACH(q, qIndicesFirst) { + const PairIndices& qFirst = qIndicesFirst[q]; + const PairIndices& qSecond = qIndicesSecond[q]; + Eigen::Vector4f as(Eigen::Vector4f::Zero()); + for (unsigned i=0; i 0) { + // store found clusters + for (unsigned i=0; i<4; ++i) { + if (bestAs[i] < maxArea) { + chunkInserter(cell, radius, bestQIndices[i]); + } else { + _SplitVolume(cell, radius, bestQIndices[i][0], maxArea, areaEstimator, chunkInserter); + _SplitVolume(cell, radius, bestQIndices[i][1], maxArea, areaEstimator, chunkInserter); + } + } + return; + } + } + // split each child + for (unsigned c: indices) { + if (childArea[c] == 0) + continue; + if (childArea[c] < maxArea) + chunkInserter(cell, radius, UnsignedArr{c}); + else + _SplitVolume(cell, radius, c, maxArea, areaEstimator, chunkInserter); + } +} +template +template +void TOctree::SplitVolume(float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter) +{ + CELL_TYPE parent; + parent.m_child = new CELL_TYPE[1]; + parent.m_child[0] = m_root; + parent.Node().center = m_root.Node().center + POINT_TYPE::Constant(m_radius); + _SplitVolume(parent, m_radius*TYPE(2), 0, maxArea, areaEstimator, chunkInserter); + parent.m_child[0].m_child = NULL; +} // SplitVolume +/*----------------------------------------------------------------*/ + #ifndef _RELEASE -template -void TOctree::_GetDebugInfo(const CELL_TYPE& cell, unsigned nDepth, DEBUGINFO& info) const +template +void TOctree::_GetDebugInfo(const CELL_TYPE& cell, unsigned nDepth, DEBUGINFO& info) const { if (cell.IsLeaf()) { if (info.minDepth > nDepth) @@ -537,8 +738,8 @@ void TOctree::_GetDebugInfo(con _GetDebugInfo(cell.m_child[i], nDepth, info); } -template -void TOctree::GetDebugInfo(DEBUGINFO* pInfo, bool bPrintStats) const +template +void TOctree::GetDebugInfo(DEBUGINFO* pInfo, bool bPrintStats) const { DEBUGINFO localInfo; DEBUGINFO& info = (pInfo ? *pInfo : localInfo); @@ -555,8 +756,8 @@ void TOctree::GetDebugInfo(DEBU } // GetDebugInfo /*----------------------------------------------------------------*/ -template -void TOctree::LogDebugInfo(const DEBUGINFO& info) +template +void TOctree::LogDebugInfo(const DEBUGINFO& info) { //VERBOSE("NoItems: %d; Mem %s; MemItems %s; MemStruct %s; AvgMemStruct %.2f%%%%; NoNodes %d; NoLeaf %d; AvgLeaf %.2f%%%%; AvgDepth %.2f; MinDepth %d; MaxDepth %d", VERBOSE("NumItems %d; Mem %s (%s items, %s struct - %.2f%%%%); NumNodes %d (leaves %d - %.2f%%%%); Depth %.2f (%d min, %d max)", @@ -574,7 +775,7 @@ inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true srand(bRandom ? (unsigned)time(NULL) : 0); typedef Eigen::Matrix POINT_TYPE; typedef SEACAVE::cList TestArr; - typedef TOctree TestTree; + typedef TOctree TestTree; const TYPE ptMinData[] = {0,0,0}, ptMaxData[] = {640,480,240}; typename TestTree::AABB_TYPE aabb; aabb.Set(Eigen::Map(ptMinData), Eigen::Map(ptMaxData)); @@ -600,7 +801,9 @@ inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true if (DIMS > 2) pt(2) = RAND()%ROUND2INT(ptMaxData[2]); const TYPE radius(TYPE(3+RAND()%30)); // build octree and find interest items - TestTree tree(items, aabb); + TestTree tree(items, aabb, [](typename TestTree::IDX_TYPE size, typename TestTree::Type radius) { + return size > 16 && radius > 10; + }); typename TestTree::IDXARR_TYPE indices; tree.Collect(indices, pt, radius); // find interest items by brute force @@ -620,7 +823,7 @@ inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true // compare results unsigned nMatches = 0; FOREACH(i, trueIndices) { - const IDX idx = trueIndices[i]; + const typename TestTree::IDX_TYPE idx = trueIndices[i]; FOREACH(j, indices) { if (indices[j] == idx) { ++nMatches; @@ -629,8 +832,8 @@ inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true } } nTotalMatches += nMatches; - nTotalMissed += trueIndices.GetSize()-nMatches; - nTotalExtra += indices.GetSize()-nMatches; + nTotalMissed += trueIndices.size()-nMatches; + nTotalExtra += indices.size()-nMatches; #ifndef _RELEASE // print stats typename TestTree::DEBUGINFO_TYPE info; diff --git a/libs/Common/Plane.h b/libs/Common/Plane.h index a293c94d2..7cd439207 100644 --- a/libs/Common/Plane.h +++ b/libs/Common/Plane.h @@ -64,6 +64,15 @@ class TPlane inline TYPE& operator [] (BYTE i) { ASSERT(i<=DIMS); return m_vN.data()[i]; } inline TYPE operator [] (BYTE i) const { ASSERT(i<=DIMS); return m_vN.data()[i]; } + + #ifdef _USE_BOOST + // implement BOOST serialization + template + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & m_vN; + ar & m_fD; + } + #endif }; // class TPlane /*----------------------------------------------------------------*/ @@ -106,6 +115,14 @@ class TFrustum inline TYPE& operator [] (BYTE i) { ASSERT(i + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & m_planes; + } + #endif }; // class TPlane /*----------------------------------------------------------------*/ diff --git a/libs/Common/Rotation.h b/libs/Common/Rotation.h index 87761e56e..22d00a356 100644 --- a/libs/Common/Rotation.h +++ b/libs/Common/Rotation.h @@ -280,9 +280,6 @@ class TRMatrixBase : public TMatrix inline TRMatrixBase(const BaseBase& rhs) : Base(rhs) {} template inline TRMatrixBase(const cv::Matx& rhs) : Base(rhs) {} - /** @brief Copy constructor from rotation matrix */ - inline TRMatrixBase(const TRMatrixBase& r); - /** @brief Copy constructor from 3x3 matrix @attention Orthonormality of matrix is enforced automatically! */ inline TRMatrixBase(const Mat& mat); @@ -299,8 +296,6 @@ class TRMatrixBase : public TMatrix /** @brief Initialization with the rotation from roll/pitch/yaw (in rad) */ inline TRMatrixBase(TYPE roll, TYPE pitch, TYPE yaw); - inline ~TRMatrixBase(); - template inline TRMatrixBase& operator = (const cv::Matx& rhs) { BaseBase::operator = (rhs); return *this; } inline TRMatrixBase& operator = (const cv::Mat& rhs) { BaseBase::operator = (rhs); return *this; } #ifdef _USE_EIGEN diff --git a/libs/Common/Rotation.inl b/libs/Common/Rotation.inl index 212d6c075..fb9b4e1d5 100644 --- a/libs/Common/Rotation.inl +++ b/libs/Common/Rotation.inl @@ -521,13 +521,6 @@ inline TRMatrixBase::TRMatrixBase() : Base(Base::IDENTITY) {} -template -inline TRMatrixBase::TRMatrixBase(const TRMatrixBase& r) - : Base(r) -{ - ASSERT(Check(R_CONSTRAINT_ACCURACY)); -} - template inline TRMatrixBase::TRMatrixBase(const Mat& mat) : Base(mat) @@ -559,10 +552,6 @@ inline TRMatrixBase::TRMatrixBase(TYPE roll, TYPE pitch, TYPE yaw) SetXYZ(roll, pitch, yaw); } -template -inline TRMatrixBase::~TRMatrixBase() -{} - template void TRMatrixBase::SetXYZ(TYPE PhiX, TYPE PhiY, TYPE PhiZ) diff --git a/libs/Common/Types.h b/libs/Common/Types.h index 631f024ce..11ae8f67d 100644 --- a/libs/Common/Types.h +++ b/libs/Common/Types.h @@ -268,7 +268,6 @@ int _vscprintf(LPCSTR format, va_list pargs); #endif //_MSC_VER #define DECLARE_NO_INDEX(...) std::numeric_limits<__VA_ARGS__>::max() -#define NO_ID DECLARE_NO_INDEX(uint32_t) #ifndef MAKEWORD #define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD)(b)) & 0xff))) << 8)) @@ -311,6 +310,10 @@ int _vscprintf(LPCSTR format, va_list pargs); #define MAXF std::max #endif +#ifndef RAND +#define RAND std::rand +#endif + namespace SEACAVE { // signed and unsigned types of the size of the architecture @@ -329,7 +332,10 @@ typedef int64_t size_f_t; // type used as the default floating number precision typedef double REAL; -template +// invalid index +constexpr uint32_t NO_ID = DECLARE_NO_INDEX(uint32_t); + +template struct RealType { typedef typename std::conditional::value, TYPE, REALTYPE>::type type; }; template @@ -341,15 +347,11 @@ inline T MAXF3(const T& x1, const T& x2, const T& x3) { return MAXF(MAXF(x1, x2), x3); } -#ifndef RAND -#define RAND std::rand -#endif template FORCEINLINE T RANDOM() { return T(RAND())/RAND_MAX; } template -union TAliasCast -{ +union TAliasCast { T1 f; T2 i; inline TAliasCast() {} @@ -1634,6 +1636,22 @@ class TDMatrix : public cv::Mat_ return pt.x>=T(border) && pt.y>=T(border) && pt.x<=T(Base::size().width-(border+1)) && pt.y<=T(Base::size().height-(border+1)); } + template + static inline void clip(TPoint2& ptMin, TPoint2& ptMax, const cv::Size& size) { + if (ptMin.x < T(border)) + ptMin.x = T(border); + if (ptMin.y < T(border)) + ptMin.y = T(border); + if (ptMax.x >= T(size.width-border)) + ptMax.x = T(size.width-(border+1)); + if (ptMax.y >= T(size.height-border)) + ptMax.y = T(size.height-(border+1)); + } + template + inline void clip(TPoint2& ptMin, TPoint2& ptMax) const { + clip(ptMin, ptMax, Base::size()); + } + /// Remove the given element from the vector inline void remove(int idx) { // replace the removed element by the last one and decrease the size @@ -2153,6 +2171,8 @@ class TImage : public TDMatrix template static void RasterizeTriangle(const TPoint2& v1, const TPoint2& v2, const TPoint2& v3, PARSER& parser); 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); template @@ -2179,6 +2199,7 @@ class TImage : public TDMatrix }; /*----------------------------------------------------------------*/ typedef TImage Image8U; +typedef TImage Image16U; typedef TImage Image16F; typedef TImage Image32F; typedef TImage Image64F; @@ -2774,6 +2795,6 @@ class SO2 #include "Ray.h" #include "Octree.h" #include "Util.inl" -#include "CUDA.h" +#include "UtilCUDA.h" #endif // __SEACAVE_TYPES_H__ diff --git a/libs/Common/Types.inl b/libs/Common/Types.inl index cbdf89fe1..7cfa7b36e 100644 --- a/libs/Common/Types.inl +++ b/libs/Common/Types.inl @@ -2416,15 +2416,9 @@ void TImage::toGray(TImage& out, int code, bool bNormalize, bool bSRGB) template cv::Size TImage::computeResize(const cv::Size& size, REAL scale) { - cv::Size scaledSize; - if (size.width > size.height) { - scaledSize.width = ROUND2INT(REAL(size.width)*scale); - scaledSize.height = ROUND2INT(REAL(size.height)*scaledSize.width/REAL(size.width)); - } else { - scaledSize.height = ROUND2INT(REAL(size.height)*scale); - scaledSize.width = ROUND2INT(REAL(size.width)*scaledSize.height/REAL(size.height)); - } - return scaledSize; + return cv::Size( + cv::saturate_cast((REAL)size.width*scale), + cv::saturate_cast((REAL)size.height*scale)); } // compute the final scaled size by performing successive resizes // with the given scale value @@ -2605,6 +2599,48 @@ 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 +void TImage::RasterizeTriangleBary(const TPoint2& v1, const TPoint2& v2, const TPoint2& v3, PARSER& parser) +{ + // compute bounding-box fully containing the triangle + const TPoint2 boxMin(MINF3(v1.x, v2.x, v3.x), MINF3(v1.y, v2.y, v3.y)); + const TPoint2 boxMax(MAXF3(v1.x, v2.x, v3.x), MAXF3(v1.y, v2.y, v3.y)); + // check the bounding-box intersects the image + const cv::Size size(parser.Size()); + 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); + // 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)); + if (b1 < 0) + continue; + const T b2(EdgeFunction(v3, v1, p)); + if (b2 < 0) + continue; + const T b3(EdgeFunction(v1, v2, p)); + if (b3 < 0) + continue; + parser(pt, TPoint3(b1, b2, b3) * invArea); + } + } +} + // drawing line between 2 points from left to right // papb -> pcpd // pa, pb, pc, pd must then be sorted before @@ -2965,8 +3001,8 @@ bool TImage::Load(const String& fileName) Base::create(h, w); ASSERT(sizeof(float)*Base::channels() == Base::step.p[1]); const size_t rowbytes((size_t)Base::size.p[1]*Base::step.p[1]); - for (int i=0; i(i), rowbytes) != rowbytes) + for (int i=rows; i>0; ) + if (fImage.read(cv::Mat::template ptr(--i), rowbytes) != rowbytes) return false; return true; } @@ -3014,14 +3050,16 @@ bool TImage::Save(const String& fileName) const if (!fImage.isOpen()) return false; ASSERT(sizeof(float) == 4); - static const uint8_t b[4] = { 255, 0, 0, 0 }; - static const bool is_little_endian = (*((float*)b) < 1.f); - static const double scale = (is_little_endian ? -1.0 : 1.0); + #if __BYTE_ORDER == __LITTLE_ENDIAN + static const double scale(-1.0); + #else + static const double scale(1.0); + #endif fImage.print("Pf\n%d %d\n%lf\n", width(), height(), scale*Base::channels()); ASSERT(sizeof(float)*Base::channels() == Base::step.p[1]); const size_t rowbytes = (size_t)Base::size.p[1]*Base::step.p[1]; - for (int i=0; i(i), rowbytes); + for (int i=rows; i>0; ) + fImage.write(cv::Mat::template ptr(--i), rowbytes); return true; } @@ -3575,8 +3613,7 @@ namespace boost { // Serialization support for cv::Mat template - void save(Archive& ar, const cv::Mat& m, const unsigned int /*version*/) - { + void save(Archive& ar, const cv::Mat& m, const unsigned int /*version*/) { const int elem_type = m.type(); const size_t elem_size = m.elemSize(); @@ -3595,8 +3632,7 @@ namespace boost { } } template - void load(Archive& ar, cv::Mat& m, const unsigned int /*version*/) - { + void load(Archive& ar, cv::Mat& m, const unsigned int /*version*/) { int cols, rows, elem_type; size_t elem_size; @@ -3617,8 +3653,7 @@ namespace boost { // Serialization support for cv::Mat_ template - void save(Archive& ar, const cv::Mat_<_Tp>& m, const unsigned int /*version*/) - { + void save(Archive& ar, const cv::Mat_<_Tp>& m, const unsigned int /*version*/) { ar & m.cols; ar & m.rows; @@ -3632,8 +3667,7 @@ namespace boost { } } template - void load(Archive& ar, cv::Mat_<_Tp>& m, const unsigned int /*version*/) - { + void load(Archive& ar, cv::Mat_<_Tp>& m, const unsigned int /*version*/) { int cols, rows; ar & cols; ar & rows; @@ -3706,6 +3740,25 @@ namespace boost { ar & m.distance; } + #ifdef _USE_EIGEN + // Serialization support for Eigen::Matrix + // specialization handling fixed sized matrices + template + inline void save(Archive& ar, const Eigen::Matrix& M, const unsigned int /*version*/) { + ar << make_nvp("data", make_array(M.data(), _Rows*_Cols)); + } + template + inline void load(Archive& ar, Eigen::Matrix& M, const unsigned int /*version*/) { + ar >> make_nvp("data", make_array(M.data(), _Rows*_Cols)); + } + // The function that causes boost::serialization to look for separate + // save() and load() functions when serializing and Eigen matrix. + template + inline void serialize(Archive& ar, Eigen::Matrix& M, const unsigned int version) { + split_free(ar, M, version); + } + #endif // _USE_EIGEN + } // namespace serialization } // namespace boost /*----------------------------------------------------------------*/ @@ -3713,6 +3766,7 @@ namespace boost { // include headers that implement a archive in simple text and binary format or XML format #if defined(_MSC_VER) #pragma warning (push) +#pragma warning (disable : 4275) // non dll-interface class #pragma warning (disable : 4715) // not all control paths return a value #endif #include @@ -3724,6 +3778,9 @@ namespace boost { // include headers that implement compressed serialization support #include #include +#if BOOST_VERSION >= 106900 +#include +#endif #if defined(_MSC_VER) #pragma warning (pop) #endif @@ -3733,12 +3790,18 @@ enum ARCHIVE_TYPE { ARCHIVE_TEXT = 0, ARCHIVE_BINARY, ARCHIVE_BINARY_ZIP, - ARCHIVE_LAST + ARCHIVE_BINARY_ZSTD, + ARCHIVE_LAST, + #if BOOST_VERSION >= 106900 + ARCHIVE_DEFAULT = ARCHIVE_BINARY_ZSTD + #else + ARCHIVE_DEFAULT = ARCHIVE_BINARY_ZIP + #endif }; // export the current state of the given reconstruction object template -bool SerializeSave(const TYPE& obj, std::ofstream& fs, ARCHIVE_TYPE type, unsigned flags=0) +bool SerializeSave(const TYPE& obj, std::ofstream& fs, ARCHIVE_TYPE type, unsigned flags=boost::archive::no_header) { // serialize out the current state switch (type) { @@ -3758,6 +3821,16 @@ bool SerializeSave(const TYPE& obj, std::ofstream& fs, ARCHIVE_TYPE type, unsign boost::archive::binary_oarchive ar(ffs, flags); ar << obj; break; } + #if BOOST_VERSION >= 106900 + case ARCHIVE_BINARY_ZSTD: { + namespace io = boost::iostreams; + io::filtering_streambuf ffs; + ffs.push(io::zstd_compressor(io::zstd::best_speed)); + ffs.push(fs); + boost::archive::binary_oarchive ar(ffs, flags); + ar << obj; + break; } + #endif default: VERBOSE("error: Can not save the object, invalid archive type"); return false; @@ -3765,7 +3838,7 @@ bool SerializeSave(const TYPE& obj, std::ofstream& fs, ARCHIVE_TYPE type, unsign return true; } // SerializeSave template -bool SerializeSave(const TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYPE type, unsigned flags=0) +bool SerializeSave(const TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYPE type, unsigned flags=boost::archive::no_header) { // open the output stream std::ofstream fs(fileName, std::ios::out | std::ios::binary); @@ -3777,7 +3850,7 @@ bool SerializeSave(const TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYP // import the state to the given reconstruction object template -bool SerializeLoad(TYPE& obj, std::ifstream& fs, ARCHIVE_TYPE type, unsigned flags=0) +bool SerializeLoad(TYPE& obj, std::ifstream& fs, ARCHIVE_TYPE type, unsigned flags=boost::archive::no_header) { try { // serialize in the saved state @@ -3798,6 +3871,16 @@ bool SerializeLoad(TYPE& obj, std::ifstream& fs, ARCHIVE_TYPE type, unsigned fla boost::archive::binary_iarchive ar(ffs, flags); ar >> obj; break; } + #if BOOST_VERSION >= 106900 + case ARCHIVE_BINARY_ZSTD: { + namespace io = boost::iostreams; + io::filtering_streambuf ffs; + ffs.push(io::zstd_decompressor()); + ffs.push(fs); + boost::archive::binary_iarchive ar(ffs, flags); + ar >> obj; + break; } + #endif default: VERBOSE("error: Can not load the object, invalid archive type"); return false; @@ -3810,7 +3893,7 @@ bool SerializeLoad(TYPE& obj, std::ifstream& fs, ARCHIVE_TYPE type, unsigned fla return true; } // SerializeLoad template -bool SerializeLoad(TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYPE type, unsigned flags=0) +bool SerializeLoad(TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYPE type, unsigned flags=boost::archive::no_header) { // open the input stream std::ifstream fs(fileName, std::ios::in | std::ios::binary); diff --git a/libs/Common/Util.cpp b/libs/Common/Util.cpp index a32268cf7..445fb7bbf 100644 --- a/libs/Common/Util.cpp +++ b/libs/Common/Util.cpp @@ -473,6 +473,7 @@ Flags InitCPU() /*----------------------------------------------------------------*/ +#if _PLATFORM_X86 #ifdef _MSC_VER #include inline void CPUID(int CPUInfo[4], int level) { @@ -485,6 +486,11 @@ inline void CPUID(int CPUInfo[4], int level) { __get_cpuid((unsigned&)level, p+0, p+1, p+2, p+3); } #endif +#else // _PLATFORM_X86 +inline void CPUID(int CPUInfo[4], int level) { + memset(CPUInfo, 0, sizeof(int)*4); +} +#endif // _PLATFORM_X86 /** * Function to detect SSE availability in CPU. @@ -551,6 +557,7 @@ CPUINFO GetCPUInfo() } /*----------------------------------------------------------------*/ +#if _PLATFORM_X86 #ifdef _MSC_VER // Function to detect SSE availability in operating system. bool OSSupportsSSE() @@ -626,6 +633,21 @@ bool OSSupportsAVX() /*----------------------------------------------------------------*/ #endif // _MSC_VER +#else // _PLATFORM_X86 + +// Function to detect SSE availability in operating system. +bool OSSupportsSSE() +{ + return false; +} +// Function to detect AVX availability in operating system. +bool OSSupportsAVX() +{ + return false; +} +/*----------------------------------------------------------------*/ +#endif // _PLATFORM_X86 + // print details about the current build and PC void Util::LogBuild() @@ -644,6 +666,7 @@ void Util::LogBuild() } // print information about the memory usage +#if _PLATFORM_X86 #ifdef _MSC_VER #include #pragma comment(lib, "Psapi.lib") @@ -679,6 +702,11 @@ void Util::LogMemoryInfo() LOG(_T("} ENDINFO")); } #endif // _MSC_VER +#else // _PLATFORM_X86 +void Util::LogMemoryInfo() +{ +} +#endif // _PLATFORM_X86 // Parses a ASCII command line string and returns an array of pointers to the command line arguments, diff --git a/libs/Common/Util.inl b/libs/Common/Util.inl index d605b9173..67199026e 100644 --- a/libs/Common/Util.inl +++ b/libs/Common/Util.inl @@ -468,13 +468,17 @@ TYPE ComputeTriangleAreaLenSq(TYPE lenaSq, TYPE lenbSq, TYPE lencSq) { } // compute area for a triangle defined by three 2D points template +TYPE EdgeFunction(const TPoint2& x0, const TPoint2& x1, const TPoint2& x2) { + return TYPE((x2-x0).cross(x1-x0)); +} +template TYPE ComputeTriangleArea(const TPoint2& x0, const TPoint2& x1, const TPoint2& x2) { - return abs((TYPE)((x0-x2).cross(x0-x1)/TYPE(2))); + return (TYPE)abs(EdgeFunction(x0, x1, x2)/TYPE(2)); } // compute area for a triangle defined by three 3D points template TYPE ComputeTriangleAreaSq(const TPoint3& x0, const TPoint3& x1, const TPoint3& x2) { - return (TYPE)(normSq((x1-x0).cross(x2-x0))/TYPE(4)); + return (TYPE)normSq((x1-x0).cross(x2-x0))/TYPE(4); } template TYPE ComputeTriangleArea(const TPoint3& x0, const TPoint3& x1, const TPoint3& x2) { @@ -607,6 +611,11 @@ inline TPoint3 CorrectBarycentricCoordinates(TPoint2 b) { ASSERT((b.x >= 0) && (b.y >= 0) && (b.x+b.y <= 1) && ISEQUAL(b.x+b.y+z, TYPE(1))); return TPoint3(b.x, b.y, z); } +template +inline TPoint3 PerspectiveCorrectBarycentricCoordinates(const TPoint3& b, TYPE z0, TYPE z1, TYPE z2) { + const TPoint3 pb(b.x * z1 * z2, b.y * z0 * z2, b.z * z0 * z1); + return pb / (pb.x + pb.y + pb.z); +} /*----------------------------------------------------------------*/ // Encodes/decodes a normalized 3D vector in two parameters for the direction @@ -656,7 +665,7 @@ inline T MaxDepthDifference(T d, T threshold) { } template inline T DepthSimilarity(T d0, T d1) { - ASSERT(d0 > 0 && d1 > 0); + ASSERT(d0 > 0); #if 0 return ABS(d0-d1)*T(2)/(d0+d1); #else diff --git a/libs/Common/CUDA.cpp b/libs/Common/UtilCUDA.cpp similarity index 99% rename from libs/Common/CUDA.cpp rename to libs/Common/UtilCUDA.cpp index c46f3afdd..8b4a79add 100644 --- a/libs/Common/CUDA.cpp +++ b/libs/Common/UtilCUDA.cpp @@ -1,12 +1,12 @@ //////////////////////////////////////////////////////////////////// -// CUDA.cpp +// UtilCUDA.cpp // // Copyright 2007 cDc@seacave // Distributed under the Boost Software License, Version 1.0 // (See http://www.boost.org/LICENSE_1_0.txt) #include "Common.h" -#include "CUDA.h" +#include "UtilCUDA.h" #ifdef _USE_CUDA diff --git a/libs/Common/CUDA.h b/libs/Common/UtilCUDA.h similarity index 97% rename from libs/Common/CUDA.h rename to libs/Common/UtilCUDA.h index 1ab9b5303..c0ed5748d 100644 --- a/libs/Common/CUDA.h +++ b/libs/Common/UtilCUDA.h @@ -1,5 +1,5 @@ //////////////////////////////////////////////////////////////////// -// CUDA.h +// UtilCUDA.h // // Copyright 2007 cDc@seacave // Distributed under the Boost Software License, Version 1.0 @@ -16,16 +16,23 @@ // CUDA driver #include +// CUDA toolkit +#include +#include +#include +#include +#include + // D E F I N E S /////////////////////////////////////////////////// +// S T R U C T S /////////////////////////////////////////////////// + namespace SEACAVE { namespace CUDA { -// S T R U C T S /////////////////////////////////////////////////// - // global list of initialized devices struct Device { CUdevice ID; @@ -69,6 +76,18 @@ inline void __ensureCudaResult(CUresult result, LPCSTR errorMessage) { } #define ensureCudaResult(val) CUDA::__ensureCudaResult(val, #val) +inline void checkCudaCall(const cudaError_t error) { + if (error == cudaSuccess) + return; + #ifdef _DEBUG + VERBOSE("CUDA error at %s:%d: %s (code %d)", __FILE__, __LINE__, cudaGetErrorString(error), error); + #else + DEBUG("CUDA error: %s (code %d)", cudaGetErrorString(error), error); + #endif + ASSERT("CudaError" == NULL); + exit(EXIT_FAILURE); +} + // rounds up addr to the align boundary template inline T align(T o, T a) { @@ -333,7 +352,7 @@ class KernelRT if ((result=cuParamSetSize(hKernel, paramOffset)) != CUDA_SUCCESS) return result; // launch the kernel (Driver API) - const CUdevprop& deviceProp = CUDA::devices.Last().prop; + const CUdevprop& deviceProp = CUDA::devices.back().prop; const int numBlockThreads(MINF(numThreads, deviceProp.maxThreadsPerBlock)); const int nBlocks(MAXF((numThreads+numBlockThreads-1)/numBlockThreads, 1)); if ((result=cuFuncSetBlockShape(hKernel, numBlockThreads, 1, 1)) != CUDA_SUCCESS) @@ -354,7 +373,7 @@ class KernelRT if ((result=cuParamSetSize(hKernel, paramOffset)) != CUDA_SUCCESS) return result; // launch the kernel (Driver API) - const CUdevprop& deviceProp = CUDA::devices.Last().prop; + const CUdevprop& deviceProp = CUDA::devices.back().prop; const REAL scale(MINF(REAL(1), SQRT((REAL)deviceProp.maxThreadsPerBlock/(REAL)(numThreads.x*numThreads.y)))); const SEACAVE::TPoint2 numBlockThreads(FLOOR2INT(SEACAVE::TPoint2(numThreads)*scale)); const TPoint2 nBlocks( diff --git a/libs/IO/CMakeLists.txt b/libs/IO/CMakeLists.txt index a354376be..da3c15920 100644 --- a/libs/IO/CMakeLists.txt +++ b/libs/IO/CMakeLists.txt @@ -25,22 +25,17 @@ else() endif() # List sources files -FILE(GLOB PCH_C "Common.cpp") - FILE(GLOB LIBRARY_FILES_C "*.cpp") FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -# Place Common.cpp as the first file in the list -# needed by cotire when setting PCH manually -LIST(REMOVE_ITEM LIBRARY_FILES_C ${PCH_C}) -SET(LIBRARY_FILES_C "${PCH_C};${LIBRARY_FILES_C}") - -cxx_library_with_type_no_pch(IO "Libs" "STATIC" "${cxx_default}" +cxx_library_with_type(IO "Libs" "" "${cxx_default}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H} ) # Manually set Common.h as the precompiled header -set_target_pch(IO Common.h) +IF(CMAKE_VERSION VERSION_GREATER_EQUAL 3.16.0) + TARGET_PRECOMPILE_HEADERS(IO PRIVATE "Common.h") +endif() # Link its dependencies TARGET_LINK_LIBRARIES(IO Common ${PNG_LIBRARIES} ${JPEG_LIBRARIES} ${TIFF_LIBRARIES} ${EXIV2_LIBS}) @@ -50,7 +45,7 @@ SET_TARGET_PROPERTIES(IO PROPERTIES PUBLIC_HEADER "${LIBRARY_FILES_H}") INSTALL(TARGETS IO EXPORT OpenMVSTargets - RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin - LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib - ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" COMPONENT lib - PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/IO" COMPONENT dev) + LIBRARY DESTINATION "${INSTALL_LIB_DIR}" + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" + PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/IO") diff --git a/libs/IO/TinyXML2.cpp b/libs/IO/TinyXML2.cpp new file mode 100644 index 000000000..bea34144c --- /dev/null +++ b/libs/IO/TinyXML2.cpp @@ -0,0 +1,2987 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Common.h" +#include "TinyXML2.h" + +#include // yes, this one new style header, is in the Android SDK. +#if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) +# include +# include +#else +# include +# include +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) + // Microsoft Visual Studio, version 2005 and higher. Not WinCE. + /*int _snprintf_s( + char *buffer, + size_t sizeOfBuffer, + size_t count, + const char *format [, + argument] ... + );*/ + static inline int TIXML_SNPRINTF( char* buffer, size_t size, const char* format, ... ) + { + va_list va; + va_start( va, format ); + const int result = vsnprintf_s( buffer, size, _TRUNCATE, format, va ); + va_end( va ); + return result; + } + + static inline int TIXML_VSNPRINTF( char* buffer, size_t size, const char* format, va_list va ) + { + const int result = vsnprintf_s( buffer, size, _TRUNCATE, format, va ); + return result; + } + + #define TIXML_VSCPRINTF _vscprintf + #define TIXML_SSCANF sscanf_s +#elif defined _MSC_VER + // Microsoft Visual Studio 2003 and earlier or WinCE + #define TIXML_SNPRINTF _snprintf + #define TIXML_VSNPRINTF _vsnprintf + #define TIXML_SSCANF sscanf + #if (_MSC_VER < 1400 ) && (!defined WINCE) + // Microsoft Visual Studio 2003 and not WinCE. + #define TIXML_VSCPRINTF _vscprintf // VS2003's C runtime has this, but VC6 C runtime or WinCE SDK doesn't have. + #else + // Microsoft Visual Studio 2003 and earlier or WinCE. + static inline int TIXML_VSCPRINTF( const char* format, va_list va ) + { + int len = 512; + for (;;) { + len = len*2; + char* str = new char[len](); + const int required = _vsnprintf(str, len, format, va); + delete[] str; + if ( required != -1 ) { + TIXMLASSERT( required >= 0 ); + len = required; + break; + } + } + TIXMLASSERT( len >= 0 ); + return len; + } + #endif +#else + // GCC version 3 and higher + //#warning( "Using sn* functions." ) + #define TIXML_SNPRINTF snprintf + #define TIXML_VSNPRINTF vsnprintf + static inline int TIXML_VSCPRINTF( const char* format, va_list va ) + { + int len = vsnprintf( 0, 0, format, va ); + TIXMLASSERT( len >= 0 ); + return len; + } + #define TIXML_SSCANF sscanf +#endif + +#if defined(_WIN64) + #define TIXML_FSEEK _fseeki64 + #define TIXML_FTELL _ftelli64 +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__ANDROID__) + #define TIXML_FSEEK fseeko + #define TIXML_FTELL ftello +#elif defined(__unix__) && defined(__x86_64__) + #define TIXML_FSEEK fseeko64 + #define TIXML_FTELL ftello64 +#else + #define TIXML_FSEEK fseek + #define TIXML_FTELL ftell +#endif + + +static const char LINE_FEED = static_cast(0x0a); // all line endings are normalized to LF +static const char LF = LINE_FEED; +static const char CARRIAGE_RETURN = static_cast(0x0d); // CR gets filtered out +static const char CR = CARRIAGE_RETURN; +static const char SINGLE_QUOTE = '\''; +static const char DOUBLE_QUOTE = '\"'; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// ef bb bf (Microsoft "lead bytes") - designates UTF-8 + +static const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +static const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +static const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + +namespace tinyxml2 +{ + +struct Entity { + const char* pattern; + int length; + char value; +}; + +static const int NUM_ENTITIES = 5; +static const Entity entities[NUM_ENTITIES] = { + { "quot", 4, DOUBLE_QUOTE }, + { "amp", 3, '&' }, + { "apos", 4, SINGLE_QUOTE }, + { "lt", 2, '<' }, + { "gt", 2, '>' } +}; + + +StrPair::~StrPair() +{ + Reset(); +} + + +void StrPair::TransferTo( StrPair* other ) +{ + if ( this == other ) { + return; + } + // This in effect implements the assignment operator by "moving" + // ownership (as in auto_ptr). + + TIXMLASSERT( other != 0 ); + TIXMLASSERT( other->_flags == 0 ); + TIXMLASSERT( other->_start == 0 ); + TIXMLASSERT( other->_end == 0 ); + + other->Reset(); + + other->_flags = _flags; + other->_start = _start; + other->_end = _end; + + _flags = 0; + _start = 0; + _end = 0; +} + + +void StrPair::Reset() +{ + if ( _flags & NEEDS_DELETE ) { + delete [] _start; + } + _flags = 0; + _start = 0; + _end = 0; +} + + +void StrPair::SetStr( const char* str, int flags ) +{ + TIXMLASSERT( str ); + Reset(); + size_t len = strlen( str ); + TIXMLASSERT( _start == 0 ); + _start = new char[ len+1 ]; + memcpy( _start, str, len+1 ); + _end = _start + len; + _flags = flags | NEEDS_DELETE; +} + + +char* StrPair::ParseText( char* p, const char* endTag, int strFlags, int* curLineNumPtr ) +{ + TIXMLASSERT( p ); + TIXMLASSERT( endTag && *endTag ); + TIXMLASSERT(curLineNumPtr); + + char* start = p; + const char endChar = *endTag; + size_t length = strlen( endTag ); + + // Inner loop of text parsing. + while ( *p ) { + if ( *p == endChar && strncmp( p, endTag, length ) == 0 ) { + Set( start, p, strFlags ); + return p + length; + } else if (*p == '\n') { + ++(*curLineNumPtr); + } + ++p; + TIXMLASSERT( p ); + } + return 0; +} + + +char* StrPair::ParseName( char* p ) +{ + if ( !p || !(*p) ) { + return 0; + } + if ( !XMLUtil::IsNameStartChar( (unsigned char) *p ) ) { + return 0; + } + + char* const start = p; + ++p; + while ( *p && XMLUtil::IsNameChar( (unsigned char) *p ) ) { + ++p; + } + + Set( start, p, 0 ); + return p; +} + + +void StrPair::CollapseWhitespace() +{ + // Adjusting _start would cause undefined behavior on delete[] + TIXMLASSERT( ( _flags & NEEDS_DELETE ) == 0 ); + // Trim leading space. + _start = XMLUtil::SkipWhiteSpace( _start, 0 ); + + if ( *_start ) { + const char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while( *p ) { + if ( XMLUtil::IsWhiteSpace( *p )) { + p = XMLUtil::SkipWhiteSpace( p, 0 ); + if ( *p == 0 ) { + break; // don't write to q; this trims the trailing space. + } + *q = ' '; + ++q; + } + *q = *p; + ++q; + ++p; + } + *q = 0; + } +} + + +const char* StrPair::GetStr() +{ + TIXMLASSERT( _start ); + TIXMLASSERT( _end ); + if ( _flags & NEEDS_FLUSH ) { + *_end = 0; + _flags ^= NEEDS_FLUSH; + + if ( _flags ) { + const char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while( p < _end ) { + if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == CR ) { + // CR-LF pair becomes LF + // CR alone becomes LF + // LF-CR becomes LF + if ( *(p+1) == LF ) { + p += 2; + } + else { + ++p; + } + *q = LF; + ++q; + } + else if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == LF ) { + if ( *(p+1) == CR ) { + p += 2; + } + else { + ++p; + } + *q = LF; + ++q; + } + else if ( (_flags & NEEDS_ENTITY_PROCESSING) && *p == '&' ) { + // Entities handled by tinyXML2: + // - special entities in the entity table [in/out] + // - numeric character reference [in] + // 中 or 中 + + if ( *(p+1) == '#' ) { + const int buflen = 10; + char buf[buflen] = { 0 }; + int len = 0; + const char* adjusted = const_cast( XMLUtil::GetCharacterRef( p, buf, &len ) ); + if ( adjusted == 0 ) { + *q = *p; + ++p; + ++q; + } + else { + TIXMLASSERT( 0 <= len && len <= buflen ); + TIXMLASSERT( q + len <= adjusted ); + p = adjusted; + memcpy( q, buf, len ); + q += len; + } + } + else { + bool entityFound = false; + for( int i = 0; i < NUM_ENTITIES; ++i ) { + const Entity& entity = entities[i]; + if ( strncmp( p + 1, entity.pattern, entity.length ) == 0 + && *( p + entity.length + 1 ) == ';' ) { + // Found an entity - convert. + *q = entity.value; + ++q; + p += entity.length + 2; + entityFound = true; + break; + } + } + if ( !entityFound ) { + // fixme: treat as error? + ++p; + ++q; + } + } + } + else { + *q = *p; + ++p; + ++q; + } + } + *q = 0; + } + // The loop below has plenty going on, and this + // is a less useful mode. Break it out. + if ( _flags & NEEDS_WHITESPACE_COLLAPSING ) { + CollapseWhitespace(); + } + _flags = (_flags & NEEDS_DELETE); + } + TIXMLASSERT( _start ); + return _start; +} + + + + +// --------- XMLUtil ----------- // + +const char* XMLUtil::writeBoolTrue = "true"; +const char* XMLUtil::writeBoolFalse = "false"; + +void XMLUtil::SetBoolSerialization(const char* writeTrue, const char* writeFalse) +{ + static const char* defTrue = "true"; + static const char* defFalse = "false"; + + writeBoolTrue = (writeTrue) ? writeTrue : defTrue; + writeBoolFalse = (writeFalse) ? writeFalse : defFalse; +} + + +const char* XMLUtil::ReadBOM( const char* p, bool* bom ) +{ + TIXMLASSERT( p ); + TIXMLASSERT( bom ); + *bom = false; + const unsigned char* pu = reinterpret_cast(p); + // Check for BOM: + if ( *(pu+0) == TIXML_UTF_LEAD_0 + && *(pu+1) == TIXML_UTF_LEAD_1 + && *(pu+2) == TIXML_UTF_LEAD_2 ) { + *bom = true; + p += 3; + } + TIXMLASSERT( p ); + return p; +} + + +void XMLUtil::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) { + *length = 1; + } + else if ( input < 0x800 ) { + *length = 2; + } + else if ( input < 0x10000 ) { + *length = 3; + } + else if ( input < 0x200000 ) { + *length = 4; + } + else { + *length = 0; // This code won't convert this correctly anyway. + return; + } + + output += *length; + + // Scary scary fall throughs are annotated with carefully designed comments + // to suppress compiler warnings such as -Wimplicit-fallthrough in gcc + switch (*length) { + case 4: + --output; + *output = static_cast((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 3: + --output; + *output = static_cast((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 2: + --output; + *output = static_cast((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 1: + --output; + *output = static_cast(input | FIRST_BYTE_MARK[*length]); + break; + default: + TIXMLASSERT( false ); + } +} + + +const char* XMLUtil::GetCharacterRef( const char* p, char* value, int* length ) +{ + // Presume an entity, and pull it out. + *length = 0; + + if ( *(p+1) == '#' && *(p+2) ) { + unsigned long ucs = 0; + TIXMLASSERT( sizeof( ucs ) >= 4 ); + ptrdiff_t delta = 0; + unsigned mult = 1; + static const char SEMICOLON = ';'; + + if ( *(p+2) == 'x' ) { + // Hexadecimal. + const char* q = p+3; + if ( !(*q) ) { + return 0; + } + + q = strchr( q, SEMICOLON ); + + if ( !q ) { + return 0; + } + TIXMLASSERT( *q == SEMICOLON ); + + delta = q-p; + --q; + + while ( *q != 'x' ) { + unsigned int digit = 0; + + if ( *q >= '0' && *q <= '9' ) { + digit = *q - '0'; + } + else if ( *q >= 'a' && *q <= 'f' ) { + digit = *q - 'a' + 10; + } + else if ( *q >= 'A' && *q <= 'F' ) { + digit = *q - 'A' + 10; + } + else { + return 0; + } + TIXMLASSERT( digit < 16 ); + TIXMLASSERT( digit == 0 || mult <= UINT_MAX / digit ); + const unsigned int digitScaled = mult * digit; + TIXMLASSERT( ucs <= ULONG_MAX - digitScaled ); + ucs += digitScaled; + TIXMLASSERT( mult <= UINT_MAX / 16 ); + mult *= 16; + --q; + } + } + else { + // Decimal. + const char* q = p+2; + if ( !(*q) ) { + return 0; + } + + q = strchr( q, SEMICOLON ); + + if ( !q ) { + return 0; + } + TIXMLASSERT( *q == SEMICOLON ); + + delta = q-p; + --q; + + while ( *q != '#' ) { + if ( *q >= '0' && *q <= '9' ) { + const unsigned int digit = *q - '0'; + TIXMLASSERT( digit < 10 ); + TIXMLASSERT( digit == 0 || mult <= UINT_MAX / digit ); + const unsigned int digitScaled = mult * digit; + TIXMLASSERT( ucs <= ULONG_MAX - digitScaled ); + ucs += digitScaled; + } + else { + return 0; + } + TIXMLASSERT( mult <= UINT_MAX / 10 ); + mult *= 10; + --q; + } + } + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + return p + delta + 1; + } + return p+1; +} + + +void XMLUtil::ToStr( int v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%d", v ); +} + + +void XMLUtil::ToStr( unsigned v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%u", v ); +} + + +void XMLUtil::ToStr( bool v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%s", v ? writeBoolTrue : writeBoolFalse); +} + +/* + ToStr() of a number is a very tricky topic. + https://github.com/leethomason/tinyxml2/issues/106 +*/ +void XMLUtil::ToStr( float v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%.8g", v ); +} + + +void XMLUtil::ToStr( double v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%.17g", v ); +} + + +void XMLUtil::ToStr( int64_t v, char* buffer, int bufferSize ) +{ + // horrible syntax trick to make the compiler happy about %lld + TIXML_SNPRINTF(buffer, bufferSize, "%lld", static_cast(v)); +} + +void XMLUtil::ToStr( uint64_t v, char* buffer, int bufferSize ) +{ + // horrible syntax trick to make the compiler happy about %llu + TIXML_SNPRINTF(buffer, bufferSize, "%llu", (long long)v); +} + +bool XMLUtil::ToInt(const char* str, int* value) +{ + if (IsPrefixHex(str)) { + unsigned v; + if (TIXML_SSCANF(str, "%x", &v) == 1) { + *value = static_cast(v); + return true; + } + } + else { + if (TIXML_SSCANF(str, "%d", value) == 1) { + return true; + } + } + return false; +} + +bool XMLUtil::ToUnsigned(const char* str, unsigned* value) +{ + if (TIXML_SSCANF(str, IsPrefixHex(str) ? "%x" : "%u", value) == 1) { + return true; + } + return false; +} + +bool XMLUtil::ToBool( const char* str, bool* value ) +{ + int ival = 0; + if ( ToInt( str, &ival )) { + *value = (ival==0) ? false : true; + return true; + } + static const char* TRUE_VALS[] = { "true", "True", "TRUE", 0 }; + static const char* FALSE_VALS[] = { "false", "False", "FALSE", 0 }; + + for (int i = 0; TRUE_VALS[i]; ++i) { + if (StringEqual(str, TRUE_VALS[i])) { + *value = true; + return true; + } + } + for (int i = 0; FALSE_VALS[i]; ++i) { + if (StringEqual(str, FALSE_VALS[i])) { + *value = false; + return true; + } + } + return false; +} + + +bool XMLUtil::ToFloat( const char* str, float* value ) +{ + if ( TIXML_SSCANF( str, "%f", value ) == 1 ) { + return true; + } + return false; +} + + +bool XMLUtil::ToDouble( const char* str, double* value ) +{ + if ( TIXML_SSCANF( str, "%lf", value ) == 1 ) { + return true; + } + return false; +} + + +bool XMLUtil::ToInt64(const char* str, int64_t* value) +{ + if (IsPrefixHex(str)) { + unsigned long long v = 0; // horrible syntax trick to make the compiler happy about %llx + if (TIXML_SSCANF(str, "%llx", &v) == 1) { + *value = static_cast(v); + return true; + } + } + else { + long long v = 0; // horrible syntax trick to make the compiler happy about %lld + if (TIXML_SSCANF(str, "%lld", &v) == 1) { + *value = static_cast(v); + return true; + } + } + return false; +} + + +bool XMLUtil::ToUnsigned64(const char* str, uint64_t* value) { + unsigned long long v = 0; // horrible syntax trick to make the compiler happy about %llu + if(TIXML_SSCANF(str, IsPrefixHex(str) ? "%llx" : "%llu", &v) == 1) { + *value = (uint64_t)v; + return true; + } + return false; +} + + +char* XMLDocument::Identify( char* p, XMLNode** node ) +{ + TIXMLASSERT( node ); + TIXMLASSERT( p ); + char* const start = p; + int const startLine = _parseCurLineNum; + p = XMLUtil::SkipWhiteSpace( p, &_parseCurLineNum ); + if( !*p ) { + *node = 0; + TIXMLASSERT( p ); + return p; + } + + // These strings define the matching patterns: + static const char* xmlHeader = { "( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += xmlHeaderLen; + } + else if ( XMLUtil::StringEqual( p, commentHeader, commentHeaderLen ) ) { + returnNode = CreateUnlinkedNode( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += commentHeaderLen; + } + else if ( XMLUtil::StringEqual( p, cdataHeader, cdataHeaderLen ) ) { + XMLText* text = CreateUnlinkedNode( _textPool ); + returnNode = text; + returnNode->_parseLineNum = _parseCurLineNum; + p += cdataHeaderLen; + text->SetCData( true ); + } + else if ( XMLUtil::StringEqual( p, dtdHeader, dtdHeaderLen ) ) { + returnNode = CreateUnlinkedNode( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += dtdHeaderLen; + } + else if ( XMLUtil::StringEqual( p, elementHeader, elementHeaderLen ) ) { + returnNode = CreateUnlinkedNode( _elementPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += elementHeaderLen; + } + else { + returnNode = CreateUnlinkedNode( _textPool ); + returnNode->_parseLineNum = _parseCurLineNum; // Report line of first non-whitespace character + p = start; // Back it up, all the text counts. + _parseCurLineNum = startLine; + } + + TIXMLASSERT( returnNode ); + TIXMLASSERT( p ); + *node = returnNode; + return p; +} + + +bool XMLDocument::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + if ( visitor->VisitEnter( *this ) ) { + for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { + if ( !node->Accept( visitor ) ) { + break; + } + } + } + return visitor->VisitExit( *this ); +} + + +// --------- XMLNode ----------- // + +XMLNode::XMLNode( XMLDocument* doc ) : + _document( doc ), + _parent( 0 ), + _value(), + _parseLineNum( 0 ), + _firstChild( 0 ), _lastChild( 0 ), + _prev( 0 ), _next( 0 ), + _userData( 0 ), + _memPool( 0 ) +{ +} + + +XMLNode::~XMLNode() +{ + DeleteChildren(); + if ( _parent ) { + _parent->Unlink( this ); + } +} + +const char* XMLNode::Value() const +{ + // Edge case: XMLDocuments don't have a Value. Return null. + if ( this->ToDocument() ) + return 0; + return _value.GetStr(); +} + +void XMLNode::SetValue( const char* str, bool staticMem ) +{ + if ( staticMem ) { + _value.SetInternedStr( str ); + } + else { + _value.SetStr( str ); + } +} + +XMLNode* XMLNode::DeepClone(XMLDocument* target) const +{ + XMLNode* clone = this->ShallowClone(target); + if (!clone) return 0; + + for (const XMLNode* child = this->FirstChild(); child; child = child->NextSibling()) { + XMLNode* childClone = child->DeepClone(target); + TIXMLASSERT(childClone); + clone->InsertEndChild(childClone); + } + return clone; +} + +void XMLNode::DeleteChildren() +{ + while( _firstChild ) { + TIXMLASSERT( _lastChild ); + DeleteChild( _firstChild ); + } + _firstChild = _lastChild = 0; +} + + +void XMLNode::Unlink( XMLNode* child ) +{ + TIXMLASSERT( child ); + TIXMLASSERT( child->_document == _document ); + TIXMLASSERT( child->_parent == this ); + if ( child == _firstChild ) { + _firstChild = _firstChild->_next; + } + if ( child == _lastChild ) { + _lastChild = _lastChild->_prev; + } + + if ( child->_prev ) { + child->_prev->_next = child->_next; + } + if ( child->_next ) { + child->_next->_prev = child->_prev; + } + child->_next = 0; + child->_prev = 0; + child->_parent = 0; +} + + +void XMLNode::DeleteChild( XMLNode* node ) +{ + TIXMLASSERT( node ); + TIXMLASSERT( node->_document == _document ); + TIXMLASSERT( node->_parent == this ); + Unlink( node ); + TIXMLASSERT(node->_prev == 0); + TIXMLASSERT(node->_next == 0); + TIXMLASSERT(node->_parent == 0); + DeleteNode( node ); +} + + +XMLNode* XMLNode::InsertEndChild( XMLNode* addThis ) +{ + TIXMLASSERT( addThis ); + if ( addThis->_document != _document ) { + TIXMLASSERT( false ); + return 0; + } + InsertChildPreamble( addThis ); + + if ( _lastChild ) { + TIXMLASSERT( _firstChild ); + TIXMLASSERT( _lastChild->_next == 0 ); + _lastChild->_next = addThis; + addThis->_prev = _lastChild; + _lastChild = addThis; + + addThis->_next = 0; + } + else { + TIXMLASSERT( _firstChild == 0 ); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; +} + + +XMLNode* XMLNode::InsertFirstChild( XMLNode* addThis ) +{ + TIXMLASSERT( addThis ); + if ( addThis->_document != _document ) { + TIXMLASSERT( false ); + return 0; + } + InsertChildPreamble( addThis ); + + if ( _firstChild ) { + TIXMLASSERT( _lastChild ); + TIXMLASSERT( _firstChild->_prev == 0 ); + + _firstChild->_prev = addThis; + addThis->_next = _firstChild; + _firstChild = addThis; + + addThis->_prev = 0; + } + else { + TIXMLASSERT( _lastChild == 0 ); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; +} + + +XMLNode* XMLNode::InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ) +{ + TIXMLASSERT( addThis ); + if ( addThis->_document != _document ) { + TIXMLASSERT( false ); + return 0; + } + + TIXMLASSERT( afterThis ); + + if ( afterThis->_parent != this ) { + TIXMLASSERT( false ); + return 0; + } + if ( afterThis == addThis ) { + // Current state: BeforeThis -> AddThis -> OneAfterAddThis + // Now AddThis must disappear from it's location and then + // reappear between BeforeThis and OneAfterAddThis. + // So just leave it where it is. + return addThis; + } + + if ( afterThis->_next == 0 ) { + // The last node or the only node. + return InsertEndChild( addThis ); + } + InsertChildPreamble( addThis ); + addThis->_prev = afterThis; + addThis->_next = afterThis->_next; + afterThis->_next->_prev = addThis; + afterThis->_next = addThis; + addThis->_parent = this; + return addThis; +} + + + + +const XMLElement* XMLNode::FirstChildElement( const char* name ) const +{ + for( const XMLNode* node = _firstChild; node; node = node->_next ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +const XMLElement* XMLNode::LastChildElement( const char* name ) const +{ + for( const XMLNode* node = _lastChild; node; node = node->_prev ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +const XMLElement* XMLNode::NextSiblingElement( const char* name ) const +{ + for( const XMLNode* node = _next; node; node = node->_next ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +const XMLElement* XMLNode::PreviousSiblingElement( const char* name ) const +{ + for( const XMLNode* node = _prev; node; node = node->_prev ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +char* XMLNode::ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) +{ + // This is a recursive method, but thinking about it "at the current level" + // it is a pretty simple flat list: + // + // + // + // With a special case: + // + // + // + // + // Where the closing element (/foo) *must* be the next thing after the opening + // element, and the names must match. BUT the tricky bit is that the closing + // element will be read by the child. + // + // 'endTag' is the end tag for this node, it is returned by a call to a child. + // 'parentEnd' is the end tag for the parent, which is filled in and returned. + + XMLDocument::DepthTracker tracker(_document); + if (_document->Error()) + return 0; + + while( p && *p ) { + XMLNode* node = 0; + + p = _document->Identify( p, &node ); + TIXMLASSERT( p ); + if ( node == 0 ) { + break; + } + + const int initialLineNum = node->_parseLineNum; + + StrPair endTag; + p = node->ParseDeep( p, &endTag, curLineNumPtr ); + if ( !p ) { + DeleteNode( node ); + if ( !_document->Error() ) { + _document->SetError( XML_ERROR_PARSING, initialLineNum, 0); + } + break; + } + + const XMLDeclaration* const decl = node->ToDeclaration(); + if ( decl ) { + // Declarations are only allowed at document level + // + // Multiple declarations are allowed but all declarations + // must occur before anything else. + // + // Optimized due to a security test case. If the first node is + // a declaration, and the last node is a declaration, then only + // declarations have so far been added. + bool wellLocated = false; + + if (ToDocument()) { + if (FirstChild()) { + wellLocated = + FirstChild() && + FirstChild()->ToDeclaration() && + LastChild() && + LastChild()->ToDeclaration(); + } + else { + wellLocated = true; + } + } + if ( !wellLocated ) { + _document->SetError( XML_ERROR_PARSING_DECLARATION, initialLineNum, "XMLDeclaration value=%s", decl->Value()); + DeleteNode( node ); + break; + } + } + + XMLElement* ele = node->ToElement(); + if ( ele ) { + // We read the end tag. Return it to the parent. + if ( ele->ClosingType() == XMLElement::CLOSING ) { + if ( parentEndTag ) { + ele->_value.TransferTo( parentEndTag ); + } + node->_memPool->SetTracked(); // created and then immediately deleted. + DeleteNode( node ); + return p; + } + + // Handle an end tag returned to this level. + // And handle a bunch of annoying errors. + bool mismatch = false; + if ( endTag.Empty() ) { + if ( ele->ClosingType() == XMLElement::OPEN ) { + mismatch = true; + } + } + else { + if ( ele->ClosingType() != XMLElement::OPEN ) { + mismatch = true; + } + else if ( !XMLUtil::StringEqual( endTag.GetStr(), ele->Name() ) ) { + mismatch = true; + } + } + if ( mismatch ) { + _document->SetError( XML_ERROR_MISMATCHED_ELEMENT, initialLineNum, "XMLElement name=%s", ele->Name()); + DeleteNode( node ); + break; + } + } + InsertEndChild( node ); + } + return 0; +} + +/*static*/ void XMLNode::DeleteNode( XMLNode* node ) +{ + if ( node == 0 ) { + return; + } + TIXMLASSERT(node->_document); + if (!node->ToDocument()) { + node->_document->MarkInUse(node); + } + + MemPool* pool = node->_memPool; + node->~XMLNode(); + pool->Free( node ); +} + +void XMLNode::InsertChildPreamble( XMLNode* insertThis ) const +{ + TIXMLASSERT( insertThis ); + TIXMLASSERT( insertThis->_document == _document ); + + if (insertThis->_parent) { + insertThis->_parent->Unlink( insertThis ); + } + else { + insertThis->_document->MarkInUse(insertThis); + insertThis->_memPool->SetTracked(); + } +} + +const XMLElement* XMLNode::ToElementWithName( const char* name ) const +{ + const XMLElement* element = this->ToElement(); + if ( element == 0 ) { + return 0; + } + if ( name == 0 ) { + return element; + } + if ( XMLUtil::StringEqual( element->Name(), name ) ) { + return element; + } + return 0; +} + +// --------- XMLText ---------- // +char* XMLText::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + if ( this->CData() ) { + p = _value.ParseText( p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_CDATA, _parseLineNum, 0 ); + } + return p; + } + else { + int flags = _document->ProcessEntities() ? StrPair::TEXT_ELEMENT : StrPair::TEXT_ELEMENT_LEAVE_ENTITIES; + if ( _document->WhitespaceMode() == COLLAPSE_WHITESPACE ) { + flags |= StrPair::NEEDS_WHITESPACE_COLLAPSING; + } + + p = _value.ParseText( p, "<", flags, curLineNumPtr ); + if ( p && *p ) { + return p-1; + } + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_TEXT, _parseLineNum, 0 ); + } + } + return 0; +} + + +XMLNode* XMLText::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLText* text = doc->NewText( Value() ); // fixme: this will always allocate memory. Intern? + text->SetCData( this->CData() ); + return text; +} + + +bool XMLText::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLText* text = compare->ToText(); + return ( text && XMLUtil::StringEqual( text->Value(), Value() ) ); +} + + +bool XMLText::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + + +// --------- XMLComment ---------- // + +XMLComment::XMLComment( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLComment::~XMLComment() +{ +} + + +char* XMLComment::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + // Comment parses as text. + p = _value.ParseText( p, "-->", StrPair::COMMENT, curLineNumPtr ); + if ( p == 0 ) { + _document->SetError( XML_ERROR_PARSING_COMMENT, _parseLineNum, 0 ); + } + return p; +} + + +XMLNode* XMLComment::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLComment* comment = doc->NewComment( Value() ); // fixme: this will always allocate memory. Intern? + return comment; +} + + +bool XMLComment::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLComment* comment = compare->ToComment(); + return ( comment && XMLUtil::StringEqual( comment->Value(), Value() )); +} + + +bool XMLComment::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + + +// --------- XMLDeclaration ---------- // + +XMLDeclaration::XMLDeclaration( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLDeclaration::~XMLDeclaration() +{ + //printf( "~XMLDeclaration\n" ); +} + + +char* XMLDeclaration::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + // Declaration parses as text. + p = _value.ParseText( p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); + if ( p == 0 ) { + _document->SetError( XML_ERROR_PARSING_DECLARATION, _parseLineNum, 0 ); + } + return p; +} + + +XMLNode* XMLDeclaration::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLDeclaration* dec = doc->NewDeclaration( Value() ); // fixme: this will always allocate memory. Intern? + return dec; +} + + +bool XMLDeclaration::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLDeclaration* declaration = compare->ToDeclaration(); + return ( declaration && XMLUtil::StringEqual( declaration->Value(), Value() )); +} + + + +bool XMLDeclaration::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + +// --------- XMLUnknown ---------- // + +XMLUnknown::XMLUnknown( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLUnknown::~XMLUnknown() +{ +} + + +char* XMLUnknown::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + // Unknown parses as text. + p = _value.ParseText( p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_UNKNOWN, _parseLineNum, 0 ); + } + return p; +} + + +XMLNode* XMLUnknown::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLUnknown* text = doc->NewUnknown( Value() ); // fixme: this will always allocate memory. Intern? + return text; +} + + +bool XMLUnknown::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLUnknown* unknown = compare->ToUnknown(); + return ( unknown && XMLUtil::StringEqual( unknown->Value(), Value() )); +} + + +bool XMLUnknown::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + +// --------- XMLAttribute ---------- // + +const char* XMLAttribute::Name() const +{ + return _name.GetStr(); +} + +const char* XMLAttribute::Value() const +{ + return _value.GetStr(); +} + +char* XMLAttribute::ParseDeep( char* p, bool processEntities, int* curLineNumPtr ) +{ + // Parse using the name rules: bug fix, was using ParseText before + p = _name.ParseName( p ); + if ( !p || !*p ) { + return 0; + } + + // Skip white space before = + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + if ( *p != '=' ) { + return 0; + } + + ++p; // move up to opening quote + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + if ( *p != '\"' && *p != '\'' ) { + return 0; + } + + const char endTag[2] = { *p, 0 }; + ++p; // move past opening quote + + p = _value.ParseText( p, endTag, processEntities ? StrPair::ATTRIBUTE_VALUE : StrPair::ATTRIBUTE_VALUE_LEAVE_ENTITIES, curLineNumPtr ); + return p; +} + + +void XMLAttribute::SetName( const char* n ) +{ + _name.SetStr( n ); +} + + +XMLError XMLAttribute::QueryIntValue( int* value ) const +{ + if ( XMLUtil::ToInt( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryUnsignedValue( unsigned int* value ) const +{ + if ( XMLUtil::ToUnsigned( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryInt64Value(int64_t* value) const +{ + if (XMLUtil::ToInt64(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryUnsigned64Value(uint64_t* value) const +{ + if(XMLUtil::ToUnsigned64(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryBoolValue( bool* value ) const +{ + if ( XMLUtil::ToBool( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryFloatValue( float* value ) const +{ + if ( XMLUtil::ToFloat( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryDoubleValue( double* value ) const +{ + if ( XMLUtil::ToDouble( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +void XMLAttribute::SetAttribute( const char* v ) +{ + _value.SetStr( v ); +} + + +void XMLAttribute::SetAttribute( int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +void XMLAttribute::SetAttribute( unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +void XMLAttribute::SetAttribute(int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); +} + +void XMLAttribute::SetAttribute(uint64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); +} + + +void XMLAttribute::SetAttribute( bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + +void XMLAttribute::SetAttribute( double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + +void XMLAttribute::SetAttribute( float v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +// --------- XMLElement ---------- // +XMLElement::XMLElement( XMLDocument* doc ) : XMLNode( doc ), + _closingType( OPEN ), + _rootAttribute( 0 ) +{ +} + + +XMLElement::~XMLElement() +{ + while( _rootAttribute ) { + XMLAttribute* next = _rootAttribute->_next; + DeleteAttribute( _rootAttribute ); + _rootAttribute = next; + } +} + + +const XMLAttribute* XMLElement::FindAttribute( const char* name ) const +{ + for( XMLAttribute* a = _rootAttribute; a; a = a->_next ) { + if ( XMLUtil::StringEqual( a->Name(), name ) ) { + return a; + } + } + return 0; +} + + +const char* XMLElement::Attribute( const char* name, const char* value ) const +{ + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return 0; + } + if ( !value || XMLUtil::StringEqual( a->Value(), value )) { + return a->Value(); + } + return 0; +} + +int XMLElement::IntAttribute(const char* name, int defaultValue) const +{ + int i = defaultValue; + QueryIntAttribute(name, &i); + return i; +} + +unsigned XMLElement::UnsignedAttribute(const char* name, unsigned defaultValue) const +{ + unsigned i = defaultValue; + QueryUnsignedAttribute(name, &i); + return i; +} + +int64_t XMLElement::Int64Attribute(const char* name, int64_t defaultValue) const +{ + int64_t i = defaultValue; + QueryInt64Attribute(name, &i); + return i; +} + +uint64_t XMLElement::Unsigned64Attribute(const char* name, uint64_t defaultValue) const +{ + uint64_t i = defaultValue; + QueryUnsigned64Attribute(name, &i); + return i; +} + +bool XMLElement::BoolAttribute(const char* name, bool defaultValue) const +{ + bool b = defaultValue; + QueryBoolAttribute(name, &b); + return b; +} + +double XMLElement::DoubleAttribute(const char* name, double defaultValue) const +{ + double d = defaultValue; + QueryDoubleAttribute(name, &d); + return d; +} + +float XMLElement::FloatAttribute(const char* name, float defaultValue) const +{ + float f = defaultValue; + QueryFloatAttribute(name, &f); + return f; +} + +const char* XMLElement::GetText() const +{ + /* skip comment node */ + const XMLNode* node = FirstChild(); + while (node) { + if (node->ToComment()) { + node = node->NextSibling(); + continue; + } + break; + } + + if ( node && node->ToText() ) { + return node->Value(); + } + return 0; +} + + +void XMLElement::SetText( const char* inText ) +{ + if ( FirstChild() && FirstChild()->ToText() ) + FirstChild()->SetValue( inText ); + else { + XMLText* theText = GetDocument()->NewText( inText ); + InsertFirstChild( theText ); + } +} + + +void XMLElement::SetText( int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText(int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); +} + +void XMLElement::SetText(uint64_t v) { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); +} + + +void XMLElement::SetText( bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( float v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +XMLError XMLElement::QueryIntText( int* ival ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToInt( t, ival ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryUnsignedText( unsigned* uval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToUnsigned( t, uval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryInt64Text(int64_t* ival) const +{ + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToInt64(t, ival)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryUnsigned64Text(uint64_t* ival) const +{ + if(FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if(XMLUtil::ToUnsigned64(t, ival)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryBoolText( bool* bval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToBool( t, bval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryDoubleText( double* dval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToDouble( t, dval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryFloatText( float* fval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToFloat( t, fval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + +int XMLElement::IntText(int defaultValue) const +{ + int i = defaultValue; + QueryIntText(&i); + return i; +} + +unsigned XMLElement::UnsignedText(unsigned defaultValue) const +{ + unsigned i = defaultValue; + QueryUnsignedText(&i); + return i; +} + +int64_t XMLElement::Int64Text(int64_t defaultValue) const +{ + int64_t i = defaultValue; + QueryInt64Text(&i); + return i; +} + +uint64_t XMLElement::Unsigned64Text(uint64_t defaultValue) const +{ + uint64_t i = defaultValue; + QueryUnsigned64Text(&i); + return i; +} + +bool XMLElement::BoolText(bool defaultValue) const +{ + bool b = defaultValue; + QueryBoolText(&b); + return b; +} + +double XMLElement::DoubleText(double defaultValue) const +{ + double d = defaultValue; + QueryDoubleText(&d); + return d; +} + +float XMLElement::FloatText(float defaultValue) const +{ + float f = defaultValue; + QueryFloatText(&f); + return f; +} + + +XMLAttribute* XMLElement::FindOrCreateAttribute( const char* name ) +{ + XMLAttribute* last = 0; + XMLAttribute* attrib = 0; + for( attrib = _rootAttribute; + attrib; + last = attrib, attrib = attrib->_next ) { + if ( XMLUtil::StringEqual( attrib->Name(), name ) ) { + break; + } + } + if ( !attrib ) { + attrib = CreateAttribute(); + TIXMLASSERT( attrib ); + if ( last ) { + TIXMLASSERT( last->_next == 0 ); + last->_next = attrib; + } + else { + TIXMLASSERT( _rootAttribute == 0 ); + _rootAttribute = attrib; + } + attrib->SetName( name ); + } + return attrib; +} + + +void XMLElement::DeleteAttribute( const char* name ) +{ + XMLAttribute* prev = 0; + for( XMLAttribute* a=_rootAttribute; a; a=a->_next ) { + if ( XMLUtil::StringEqual( name, a->Name() ) ) { + if ( prev ) { + prev->_next = a->_next; + } + else { + _rootAttribute = a->_next; + } + DeleteAttribute( a ); + break; + } + prev = a; + } +} + + +char* XMLElement::ParseAttributes( char* p, int* curLineNumPtr ) +{ + XMLAttribute* prevAttribute = 0; + + // Read the attributes. + while( p ) { + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + if ( !(*p) ) { + _document->SetError( XML_ERROR_PARSING_ELEMENT, _parseLineNum, "XMLElement name=%s", Name() ); + return 0; + } + + // attribute. + if (XMLUtil::IsNameStartChar( (unsigned char) *p ) ) { + XMLAttribute* attrib = CreateAttribute(); + TIXMLASSERT( attrib ); + attrib->_parseLineNum = _document->_parseCurLineNum; + + const int attrLineNum = attrib->_parseLineNum; + + p = attrib->ParseDeep( p, _document->ProcessEntities(), curLineNumPtr ); + if ( !p || Attribute( attrib->Name() ) ) { + DeleteAttribute( attrib ); + _document->SetError( XML_ERROR_PARSING_ATTRIBUTE, attrLineNum, "XMLElement name=%s", Name() ); + return 0; + } + // There is a minor bug here: if the attribute in the source xml + // document is duplicated, it will not be detected and the + // attribute will be doubly added. However, tracking the 'prevAttribute' + // avoids re-scanning the attribute list. Preferring performance for + // now, may reconsider in the future. + if ( prevAttribute ) { + TIXMLASSERT( prevAttribute->_next == 0 ); + prevAttribute->_next = attrib; + } + else { + TIXMLASSERT( _rootAttribute == 0 ); + _rootAttribute = attrib; + } + prevAttribute = attrib; + } + // end of the tag + else if ( *p == '>' ) { + ++p; + break; + } + // end of the tag + else if ( *p == '/' && *(p+1) == '>' ) { + _closingType = CLOSED; + return p+2; // done; sealed element. + } + else { + _document->SetError( XML_ERROR_PARSING_ELEMENT, _parseLineNum, 0 ); + return 0; + } + } + return p; +} + +void XMLElement::DeleteAttribute( XMLAttribute* attribute ) +{ + if ( attribute == 0 ) { + return; + } + MemPool* pool = attribute->_memPool; + attribute->~XMLAttribute(); + pool->Free( attribute ); +} + +XMLAttribute* XMLElement::CreateAttribute() +{ + TIXMLASSERT( sizeof( XMLAttribute ) == _document->_attributePool.ItemSize() ); + XMLAttribute* attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); + TIXMLASSERT( attrib ); + attrib->_memPool = &_document->_attributePool; + attrib->_memPool->SetTracked(); + return attrib; +} + + +XMLElement* XMLElement::InsertNewChildElement(const char* name) +{ + XMLElement* node = _document->NewElement(name); + return InsertEndChild(node) ? node : 0; +} + +XMLComment* XMLElement::InsertNewComment(const char* comment) +{ + XMLComment* node = _document->NewComment(comment); + return InsertEndChild(node) ? node : 0; +} + +XMLText* XMLElement::InsertNewText(const char* text) +{ + XMLText* node = _document->NewText(text); + return InsertEndChild(node) ? node : 0; +} + +XMLDeclaration* XMLElement::InsertNewDeclaration(const char* text) +{ + XMLDeclaration* node = _document->NewDeclaration(text); + return InsertEndChild(node) ? node : 0; +} + +XMLUnknown* XMLElement::InsertNewUnknown(const char* text) +{ + XMLUnknown* node = _document->NewUnknown(text); + return InsertEndChild(node) ? node : 0; +} + + + +// +// +// foobar +// +char* XMLElement::ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) +{ + // Read the element name. + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + + // The closing element is the form. It is + // parsed just like a regular element then deleted from + // the DOM. + if ( *p == '/' ) { + _closingType = CLOSING; + ++p; + } + + p = _value.ParseName( p ); + if ( _value.Empty() ) { + return 0; + } + + p = ParseAttributes( p, curLineNumPtr ); + if ( !p || !*p || _closingType != OPEN ) { + return p; + } + + p = XMLNode::ParseDeep( p, parentEndTag, curLineNumPtr ); + return p; +} + + + +XMLNode* XMLElement::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLElement* element = doc->NewElement( Value() ); // fixme: this will always allocate memory. Intern? + for( const XMLAttribute* a=FirstAttribute(); a; a=a->Next() ) { + element->SetAttribute( a->Name(), a->Value() ); // fixme: this will always allocate memory. Intern? + } + return element; +} + + +bool XMLElement::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLElement* other = compare->ToElement(); + if ( other && XMLUtil::StringEqual( other->Name(), Name() )) { + + const XMLAttribute* a=FirstAttribute(); + const XMLAttribute* b=other->FirstAttribute(); + + while ( a && b ) { + if ( !XMLUtil::StringEqual( a->Value(), b->Value() ) ) { + return false; + } + a = a->Next(); + b = b->Next(); + } + if ( a || b ) { + // different count + return false; + } + return true; + } + return false; +} + + +bool XMLElement::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + if ( visitor->VisitEnter( *this, _rootAttribute ) ) { + for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { + if ( !node->Accept( visitor ) ) { + break; + } + } + } + return visitor->VisitExit( *this ); +} + + +// --------- XMLDocument ----------- // + +// Warning: List must match 'enum XMLError' +const char* XMLDocument::_errorNames[XML_ERROR_COUNT] = { + "XML_SUCCESS", + "XML_NO_ATTRIBUTE", + "XML_WRONG_ATTRIBUTE_TYPE", + "XML_ERROR_FILE_NOT_FOUND", + "XML_ERROR_FILE_COULD_NOT_BE_OPENED", + "XML_ERROR_FILE_READ_ERROR", + "XML_ERROR_PARSING_ELEMENT", + "XML_ERROR_PARSING_ATTRIBUTE", + "XML_ERROR_PARSING_TEXT", + "XML_ERROR_PARSING_CDATA", + "XML_ERROR_PARSING_COMMENT", + "XML_ERROR_PARSING_DECLARATION", + "XML_ERROR_PARSING_UNKNOWN", + "XML_ERROR_EMPTY_DOCUMENT", + "XML_ERROR_MISMATCHED_ELEMENT", + "XML_ERROR_PARSING", + "XML_CAN_NOT_CONVERT_TEXT", + "XML_NO_TEXT_NODE", + "XML_ELEMENT_DEPTH_EXCEEDED" +}; + + +XMLDocument::XMLDocument( bool processEntities, Whitespace whitespaceMode ) : + XMLNode( 0 ), + _writeBOM( false ), + _processEntities( processEntities ), + _errorID(XML_SUCCESS), + _whitespaceMode( whitespaceMode ), + _errorStr(), + _errorLineNum( 0 ), + _charBuffer( 0 ), + _parseCurLineNum( 0 ), + _parsingDepth(0), + _unlinked(), + _elementPool(), + _attributePool(), + _textPool(), + _commentPool() +{ + // avoid VC++ C4355 warning about 'this' in initializer list (C4355 is off by default in VS2012+) + _document = this; +} + + +XMLDocument::~XMLDocument() +{ + Clear(); +} + + +void XMLDocument::MarkInUse(const XMLNode* const node) +{ + TIXMLASSERT(node); + TIXMLASSERT(node->_parent == 0); + + for (int i = 0; i < _unlinked.Size(); ++i) { + if (node == _unlinked[i]) { + _unlinked.SwapRemove(i); + break; + } + } +} + +void XMLDocument::Clear() +{ + DeleteChildren(); + while( _unlinked.Size()) { + DeleteNode(_unlinked[0]); // Will remove from _unlinked as part of delete. + } + +#ifdef TINYXML2_DEBUG + const bool hadError = Error(); +#endif + ClearError(); + + delete [] _charBuffer; + _charBuffer = 0; + _parsingDepth = 0; + +#if 0 + _textPool.Trace( "text" ); + _elementPool.Trace( "element" ); + _commentPool.Trace( "comment" ); + _attributePool.Trace( "attribute" ); +#endif + +#ifdef TINYXML2_DEBUG + if ( !hadError ) { + TIXMLASSERT( _elementPool.CurrentAllocs() == _elementPool.Untracked() ); + TIXMLASSERT( _attributePool.CurrentAllocs() == _attributePool.Untracked() ); + TIXMLASSERT( _textPool.CurrentAllocs() == _textPool.Untracked() ); + TIXMLASSERT( _commentPool.CurrentAllocs() == _commentPool.Untracked() ); + } +#endif +} + + +void XMLDocument::DeepCopy(XMLDocument* target) const +{ + TIXMLASSERT(target); + if (target == this) { + return; // technically success - a no-op. + } + + target->Clear(); + for (const XMLNode* node = this->FirstChild(); node; node = node->NextSibling()) { + target->InsertEndChild(node->DeepClone(target)); + } +} + +XMLElement* XMLDocument::NewElement( const char* name ) +{ + XMLElement* ele = CreateUnlinkedNode( _elementPool ); + ele->SetName( name ); + return ele; +} + + +XMLComment* XMLDocument::NewComment( const char* str ) +{ + XMLComment* comment = CreateUnlinkedNode( _commentPool ); + comment->SetValue( str ); + return comment; +} + + +XMLText* XMLDocument::NewText( const char* str ) +{ + XMLText* text = CreateUnlinkedNode( _textPool ); + text->SetValue( str ); + return text; +} + + +XMLDeclaration* XMLDocument::NewDeclaration( const char* str ) +{ + XMLDeclaration* dec = CreateUnlinkedNode( _commentPool ); + dec->SetValue( str ? str : "xml version=\"1.0\" encoding=\"UTF-8\"" ); + return dec; +} + + +XMLUnknown* XMLDocument::NewUnknown( const char* str ) +{ + XMLUnknown* unk = CreateUnlinkedNode( _commentPool ); + unk->SetValue( str ); + return unk; +} + +static FILE* callfopen( const char* filepath, const char* mode ) +{ + TIXMLASSERT( filepath ); + TIXMLASSERT( mode ); +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) + FILE* fp = 0; + const errno_t err = fopen_s( &fp, filepath, mode ); + if ( err ) { + return 0; + } +#else + FILE* fp = fopen( filepath, mode ); +#endif + return fp; +} + +void XMLDocument::DeleteNode( XMLNode* node ) { + TIXMLASSERT( node ); + TIXMLASSERT(node->_document == this ); + if (node->_parent) { + node->_parent->DeleteChild( node ); + } + else { + // Isn't in the tree. + // Use the parent delete. + // Also, we need to mark it tracked: we 'know' + // it was never used. + node->_memPool->SetTracked(); + // Call the static XMLNode version: + XMLNode::DeleteNode(node); + } +} + + +XMLError XMLDocument::LoadFile( const char* filename ) +{ + if ( !filename ) { + TIXMLASSERT( false ); + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, "filename=" ); + return _errorID; + } + + Clear(); + FILE* fp = callfopen( filename, "rb" ); + if ( !fp ) { + SetError( XML_ERROR_FILE_NOT_FOUND, 0, "filename=%s", filename ); + return _errorID; + } + LoadFile( fp ); + fclose( fp ); + return _errorID; +} + +XMLError XMLDocument::LoadFile( FILE* fp ) +{ + Clear(); + + TIXML_FSEEK( fp, 0, SEEK_SET ); + if ( fgetc( fp ) == EOF && ferror( fp ) != 0 ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + TIXML_FSEEK( fp, 0, SEEK_END ); + + unsigned long long filelength; + { + const long long fileLengthSigned = TIXML_FTELL( fp ); + TIXML_FSEEK( fp, 0, SEEK_SET ); + if ( fileLengthSigned == -1L ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + TIXMLASSERT( fileLengthSigned >= 0 ); + filelength = static_cast(fileLengthSigned); + } + + const size_t maxSizeT = static_cast(-1); + // We'll do the comparison as an unsigned long long, because that's guaranteed to be at + // least 8 bytes, even on a 32-bit platform. + if ( filelength >= static_cast(maxSizeT) ) { + // Cannot handle files which won't fit in buffer together with null terminator + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + if ( filelength == 0 ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + + const size_t size = static_cast(filelength); + TIXMLASSERT( _charBuffer == 0 ); + _charBuffer = new char[size+1]; + const size_t read = fread( _charBuffer, 1, size, fp ); + if ( read != size ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + _charBuffer[size] = 0; + + Parse(); + return _errorID; +} + + +XMLError XMLDocument::SaveFile( const char* filename, bool compact ) +{ + if ( !filename ) { + TIXMLASSERT( false ); + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, "filename=" ); + return _errorID; + } + + FILE* fp = callfopen( filename, "w" ); + if ( !fp ) { + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, "filename=%s", filename ); + return _errorID; + } + SaveFile(fp, compact); + fclose( fp ); + return _errorID; +} + + +XMLError XMLDocument::SaveFile( FILE* fp, bool compact ) +{ + // Clear any error from the last save, otherwise it will get reported + // for *this* call. + ClearError(); + XMLPrinter stream( fp, compact ); + Print( &stream ); + return _errorID; +} + + +XMLError XMLDocument::Parse( const char* p, size_t len ) +{ + Clear(); + + if ( len == 0 || !p || !*p ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + if ( len == static_cast(-1) ) { + len = strlen( p ); + } + TIXMLASSERT( _charBuffer == 0 ); + _charBuffer = new char[ len+1 ]; + memcpy( _charBuffer, p, len ); + _charBuffer[len] = 0; + + Parse(); + if ( Error() ) { + // clean up now essentially dangling memory. + // and the parse fail can put objects in the + // pools that are dead and inaccessible. + DeleteChildren(); + _elementPool.Clear(); + _attributePool.Clear(); + _textPool.Clear(); + _commentPool.Clear(); + } + return _errorID; +} + + +void XMLDocument::Print( XMLPrinter* streamer ) const +{ + if ( streamer ) { + Accept( streamer ); + } + else { + XMLPrinter stdoutStreamer( stdout ); + Accept( &stdoutStreamer ); + } +} + + +void XMLDocument::ClearError() { + _errorID = XML_SUCCESS; + _errorLineNum = 0; + _errorStr.Reset(); +} + + +void XMLDocument::SetError( XMLError error, int lineNum, const char* format, ... ) +{ + TIXMLASSERT( error >= 0 && error < XML_ERROR_COUNT ); + _errorID = error; + _errorLineNum = lineNum; + _errorStr.Reset(); + + const size_t BUFFER_SIZE = 1000; + char* buffer = new char[BUFFER_SIZE]; + + TIXMLASSERT(sizeof(error) <= sizeof(int)); + TIXML_SNPRINTF(buffer, BUFFER_SIZE, "Error=%s ErrorID=%d (0x%x) Line number=%d", ErrorIDToName(error), int(error), int(error), lineNum); + + if (format) { + size_t len = strlen(buffer); + TIXML_SNPRINTF(buffer + len, BUFFER_SIZE - len, ": "); + len = strlen(buffer); + + va_list va; + va_start(va, format); + TIXML_VSNPRINTF(buffer + len, BUFFER_SIZE - len, format, va); + va_end(va); + } + _errorStr.SetStr(buffer); + delete[] buffer; +} + + +/*static*/ const char* XMLDocument::ErrorIDToName(XMLError errorID) +{ + TIXMLASSERT( errorID >= 0 && errorID < XML_ERROR_COUNT ); + const char* errorName = _errorNames[errorID]; + TIXMLASSERT( errorName && errorName[0] ); + return errorName; +} + +const char* XMLDocument::ErrorStr() const +{ + return _errorStr.Empty() ? "" : _errorStr.GetStr(); +} + + +void XMLDocument::PrintError() const +{ + printf("%s\n", ErrorStr()); +} + +const char* XMLDocument::ErrorName() const +{ + return ErrorIDToName(_errorID); +} + +void XMLDocument::Parse() +{ + TIXMLASSERT( NoChildren() ); // Clear() must have been called previously + TIXMLASSERT( _charBuffer ); + _parseCurLineNum = 1; + _parseLineNum = 1; + char* p = _charBuffer; + p = XMLUtil::SkipWhiteSpace( p, &_parseCurLineNum ); + p = const_cast( XMLUtil::ReadBOM( p, &_writeBOM ) ); + if ( !*p ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return; + } + ParseDeep(p, 0, &_parseCurLineNum ); +} + +void XMLDocument::PushDepth() +{ + _parsingDepth++; + if (_parsingDepth == TINYXML2_MAX_ELEMENT_DEPTH) { + SetError(XML_ELEMENT_DEPTH_EXCEEDED, _parseCurLineNum, "Element nesting is too deep." ); + } +} + +void XMLDocument::PopDepth() +{ + TIXMLASSERT(_parsingDepth > 0); + --_parsingDepth; +} + +XMLPrinter::XMLPrinter( FILE* file, bool compact, int depth ) : + _elementJustOpened( false ), + _stack(), + _firstElement( true ), + _fp( file ), + _depth( depth ), + _textDepth( -1 ), + _processEntities( true ), + _compactMode( compact ), + _buffer() +{ + for( int i=0; i(entityValue); + TIXMLASSERT( flagIndex < ENTITY_RANGE ); + _entityFlag[flagIndex] = true; + } + _restrictedEntityFlag[static_cast('&')] = true; + _restrictedEntityFlag[static_cast('<')] = true; + _restrictedEntityFlag[static_cast('>')] = true; // not required, but consistency is nice + _buffer.Push( 0 ); +} + + +void XMLPrinter::Print( const char* format, ... ) +{ + va_list va; + va_start( va, format ); + + if ( _fp ) { + vfprintf( _fp, format, va ); + } + else { + const int len = TIXML_VSCPRINTF( format, va ); + // Close out and re-start the va-args + va_end( va ); + TIXMLASSERT( len >= 0 ); + va_start( va, format ); + TIXMLASSERT( _buffer.Size() > 0 && _buffer[_buffer.Size() - 1] == 0 ); + char* p = _buffer.PushArr( len ) - 1; // back up over the null terminator. + TIXML_VSNPRINTF( p, len+1, format, va ); + } + va_end( va ); +} + + +void XMLPrinter::Write( const char* data, size_t size ) +{ + if ( _fp ) { + fwrite ( data , sizeof(char), size, _fp); + } + else { + char* p = _buffer.PushArr( static_cast(size) ) - 1; // back up over the null terminator. + memcpy( p, data, size ); + p[size] = 0; + } +} + + +void XMLPrinter::Putc( char ch ) +{ + if ( _fp ) { + fputc ( ch, _fp); + } + else { + char* p = _buffer.PushArr( sizeof(char) ) - 1; // back up over the null terminator. + p[0] = ch; + p[1] = 0; + } +} + + +void XMLPrinter::PrintSpace( int depth ) +{ + for( int i=0; i 0 && *q < ENTITY_RANGE ) { + // Check for entities. If one is found, flush + // the stream up until the entity, write the + // entity, and keep looking. + if ( flag[static_cast(*q)] ) { + while ( p < q ) { + const size_t delta = q - p; + const int toPrint = ( INT_MAX < delta ) ? INT_MAX : static_cast(delta); + Write( p, toPrint ); + p += toPrint; + } + bool entityPatternPrinted = false; + for( int i=0; i(delta); + Write( p, toPrint ); + } + } + else { + Write( p ); + } +} + + +void XMLPrinter::PushHeader( bool writeBOM, bool writeDec ) +{ + if ( writeBOM ) { + static const unsigned char bom[] = { TIXML_UTF_LEAD_0, TIXML_UTF_LEAD_1, TIXML_UTF_LEAD_2, 0 }; + Write( reinterpret_cast< const char* >( bom ) ); + } + if ( writeDec ) { + PushDeclaration( "xml version=\"1.0\"" ); + } +} + +void XMLPrinter::PrepareForNewNode( bool compactMode ) +{ + SealElementIfJustOpened(); + + if ( compactMode ) { + return; + } + + if ( _firstElement ) { + PrintSpace (_depth); + } else if ( _textDepth < 0) { + Putc( '\n' ); + PrintSpace( _depth ); + } + + _firstElement = false; +} + +void XMLPrinter::OpenElement( const char* name, bool compactMode ) +{ + PrepareForNewNode( compactMode ); + _stack.Push( name ); + + Write ( "<" ); + Write ( name ); + + _elementJustOpened = true; + ++_depth; +} + + +void XMLPrinter::PushAttribute( const char* name, const char* value ) +{ + TIXMLASSERT( _elementJustOpened ); + Putc ( ' ' ); + Write( name ); + Write( "=\"" ); + PrintString( value, false ); + Putc ( '\"' ); +} + + +void XMLPrinter::PushAttribute( const char* name, int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute( const char* name, unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute(const char* name, int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + PushAttribute(name, buf); +} + + +void XMLPrinter::PushAttribute(const char* name, uint64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + PushAttribute(name, buf); +} + + +void XMLPrinter::PushAttribute( const char* name, bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute( const char* name, double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::CloseElement( bool compactMode ) +{ + --_depth; + const char* name = _stack.Pop(); + + if ( _elementJustOpened ) { + Write( "/>" ); + } + else { + if ( _textDepth < 0 && !compactMode) { + Putc( '\n' ); + PrintSpace( _depth ); + } + Write ( "" ); + } + + if ( _textDepth == _depth ) { + _textDepth = -1; + } + if ( _depth == 0 && !compactMode) { + Putc( '\n' ); + } + _elementJustOpened = false; +} + + +void XMLPrinter::SealElementIfJustOpened() +{ + if ( !_elementJustOpened ) { + return; + } + _elementJustOpened = false; + Putc( '>' ); +} + + +void XMLPrinter::PushText( const char* text, bool cdata ) +{ + _textDepth = _depth-1; + + SealElementIfJustOpened(); + if ( cdata ) { + Write( "" ); + } + else { + PrintString( text, true ); + } +} + + +void XMLPrinter::PushText( int64_t value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( uint64_t value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(value, buf, BUF_SIZE); + PushText(buf, false); +} + + +void XMLPrinter::PushText( int value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( unsigned value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( bool value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( float value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( double value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushComment( const char* comment ) +{ + PrepareForNewNode( _compactMode ); + + Write( "" ); +} + + +void XMLPrinter::PushDeclaration( const char* value ) +{ + PrepareForNewNode( _compactMode ); + + Write( "" ); +} + + +void XMLPrinter::PushUnknown( const char* value ) +{ + PrepareForNewNode( _compactMode ); + + Write( "' ); +} + + +bool XMLPrinter::VisitEnter( const XMLDocument& doc ) +{ + _processEntities = doc.ProcessEntities(); + if ( doc.HasBOM() ) { + PushHeader( true, false ); + } + return true; +} + + +bool XMLPrinter::VisitEnter( const XMLElement& element, const XMLAttribute* attribute ) +{ + const XMLElement* parentElem = 0; + if ( element.Parent() ) { + parentElem = element.Parent()->ToElement(); + } + const bool compactMode = parentElem ? CompactMode( *parentElem ) : _compactMode; + OpenElement( element.Name(), compactMode ); + while ( attribute ) { + PushAttribute( attribute->Name(), attribute->Value() ); + attribute = attribute->Next(); + } + return true; +} + + +bool XMLPrinter::VisitExit( const XMLElement& element ) +{ + CloseElement( CompactMode(element) ); + return true; +} + + +bool XMLPrinter::Visit( const XMLText& text ) +{ + PushText( text.Value(), text.CData() ); + return true; +} + + +bool XMLPrinter::Visit( const XMLComment& comment ) +{ + PushComment( comment.Value() ); + return true; +} + +bool XMLPrinter::Visit( const XMLDeclaration& declaration ) +{ + PushDeclaration( declaration.Value() ); + return true; +} + + +bool XMLPrinter::Visit( const XMLUnknown& unknown ) +{ + PushUnknown( unknown.Value() ); + return true; +} + +} // namespace tinyxml2 diff --git a/libs/IO/TinyXML2.h b/libs/IO/TinyXML2.h new file mode 100644 index 000000000..ae237adde --- /dev/null +++ b/libs/IO/TinyXML2.h @@ -0,0 +1,2368 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#ifndef TINYXML2_INCLUDED +#define TINYXML2_INCLUDED + +#if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) +# include +# include +# include +# include +# include +# if defined(__PS3__) +# include +# endif +#else +# include +# include +# include +# include +# include +#endif +#include + +/* + TODO: intern strings instead of allocation. +*/ +/* + gcc: + g++ -Wall -DTINYXML2_DEBUG tinyxml2.cpp xmltest.cpp -o gccxmltest.exe + + Formatting, Artistic Style: + AStyle.exe --style=1tbs --indent-switches --break-closing-brackets --indent-preprocessor tinyxml2.cpp tinyxml2.h +*/ + +#if defined( _DEBUG ) || defined (__DEBUG__) +# ifndef TINYXML2_DEBUG +# define TINYXML2_DEBUG +# endif +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4251) +#endif + +#ifndef TINYXML2_LIB +#define TINYXML2_LIB IO_API +#endif + +#if !defined(TIXMLASSERT) +#if defined(TINYXML2_DEBUG) +# if defined(_MSC_VER) +# define TIXMLASSERT ASSERT +# elif defined (ANDROID_NDK) +# include +# define TIXMLASSERT( x ) if ( !(x)) { __android_log_assert( "assert", "grinliz", "ASSERT in '%s' at %d.", __FILE__, __LINE__ ); } +# else +# include +# define TIXMLASSERT ASSERT +# endif +#else +# define TIXMLASSERT( x ) {} +#endif +#endif + +/* Versioning, past 1.0.14: + http://semver.org/ +*/ +static const int TIXML2_MAJOR_VERSION = 9; +static const int TIXML2_MINOR_VERSION = 0; +static const int TIXML2_PATCH_VERSION = 0; + +#define TINYXML2_MAJOR_VERSION 9 +#define TINYXML2_MINOR_VERSION 0 +#define TINYXML2_PATCH_VERSION 0 + +// A fixed element depth limit is problematic. There needs to be a +// limit to avoid a stack overflow. However, that limit varies per +// system, and the capacity of the stack. On the other hand, it's a trivial +// attack that can result from ill, malicious, or even correctly formed XML, +// so there needs to be a limit in place. +static const int TINYXML2_MAX_ELEMENT_DEPTH = 100; + +namespace tinyxml2 +{ +class XMLDocument; +class XMLElement; +class XMLAttribute; +class XMLComment; +class XMLText; +class XMLDeclaration; +class XMLUnknown; +class XMLPrinter; + +/* + A class that wraps strings. Normally stores the start and end + pointers into the XML file itself, and will apply normalization + and entity translation if actually read. Can also store (and memory + manage) a traditional char[] + + Isn't clear why TINYXML2_LIB is needed; but seems to fix #719 +*/ +class TINYXML2_LIB StrPair +{ +public: + enum Mode { + NEEDS_ENTITY_PROCESSING = 0x01, + NEEDS_NEWLINE_NORMALIZATION = 0x02, + NEEDS_WHITESPACE_COLLAPSING = 0x04, + + TEXT_ELEMENT = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + TEXT_ELEMENT_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_NAME = 0, + ATTRIBUTE_VALUE = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_VALUE_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + COMMENT = NEEDS_NEWLINE_NORMALIZATION + }; + + StrPair() : _flags( 0 ), _start( 0 ), _end( 0 ) {} + ~StrPair(); + + void Set( char* start, char* end, int flags ) { + TIXMLASSERT( start ); + TIXMLASSERT( end ); + Reset(); + _start = start; + _end = end; + _flags = flags | NEEDS_FLUSH; + } + + const char* GetStr(); + + bool Empty() const { + return _start == _end; + } + + void SetInternedStr( const char* str ) { + Reset(); + _start = const_cast(str); + } + + void SetStr( const char* str, int flags=0 ); + + char* ParseText( char* in, const char* endTag, int strFlags, int* curLineNumPtr ); + char* ParseName( char* in ); + + void TransferTo( StrPair* other ); + void Reset(); + +private: + void CollapseWhitespace(); + + enum { + NEEDS_FLUSH = 0x100, + NEEDS_DELETE = 0x200 + }; + + int _flags; + char* _start; + char* _end; + + StrPair( const StrPair& other ); // not supported + void operator=( const StrPair& other ); // not supported, use TransferTo() +}; + + +/* + A dynamic array of Plain Old Data. Doesn't support constructors, etc. + Has a small initial memory pool, so that low or no usage will not + cause a call to new/delete +*/ +template +class DynArray +{ +public: + DynArray() : + _mem( _pool ), + _allocated( INITIAL_SIZE ), + _size( 0 ) + { + } + + ~DynArray() { + if ( _mem != _pool ) { + delete [] _mem; + } + } + + void Clear() { + _size = 0; + } + + void Push( T t ) { + TIXMLASSERT( _size < INT_MAX ); + EnsureCapacity( _size+1 ); + _mem[_size] = t; + ++_size; + } + + T* PushArr( int count ) { + TIXMLASSERT( count >= 0 ); + TIXMLASSERT( _size <= INT_MAX - count ); + EnsureCapacity( _size+count ); + T* ret = &_mem[_size]; + _size += count; + return ret; + } + + T Pop() { + TIXMLASSERT( _size > 0 ); + --_size; + return _mem[_size]; + } + + void PopArr( int count ) { + TIXMLASSERT( _size >= count ); + _size -= count; + } + + bool Empty() const { + return _size == 0; + } + + T& operator[](int i) { + TIXMLASSERT( i>= 0 && i < _size ); + return _mem[i]; + } + + const T& operator[](int i) const { + TIXMLASSERT( i>= 0 && i < _size ); + return _mem[i]; + } + + const T& PeekTop() const { + TIXMLASSERT( _size > 0 ); + return _mem[ _size - 1]; + } + + int Size() const { + TIXMLASSERT( _size >= 0 ); + return _size; + } + + int Capacity() const { + TIXMLASSERT( _allocated >= INITIAL_SIZE ); + return _allocated; + } + + void SwapRemove(int i) { + TIXMLASSERT(i >= 0 && i < _size); + TIXMLASSERT(_size > 0); + _mem[i] = _mem[_size - 1]; + --_size; + } + + const T* Mem() const { + TIXMLASSERT( _mem ); + return _mem; + } + + T* Mem() { + TIXMLASSERT( _mem ); + return _mem; + } + +private: + DynArray( const DynArray& ); // not supported + void operator=( const DynArray& ); // not supported + + void EnsureCapacity( int cap ) { + TIXMLASSERT( cap > 0 ); + if ( cap > _allocated ) { + TIXMLASSERT( cap <= INT_MAX / 2 ); + const int newAllocated = cap * 2; + T* newMem = new T[newAllocated]; + TIXMLASSERT( newAllocated >= _size ); + memcpy( newMem, _mem, sizeof(T)*_size ); // warning: not using constructors, only works for PODs + if ( _mem != _pool ) { + delete [] _mem; + } + _mem = newMem; + _allocated = newAllocated; + } + } + + T* _mem; + T _pool[INITIAL_SIZE]; + int _allocated; // objects allocated + int _size; // number objects in use +}; + + +/* + Parent virtual class of a pool for fast allocation + and deallocation of objects. +*/ +class MemPool +{ +public: + MemPool() {} + virtual ~MemPool() {} + + virtual int ItemSize() const = 0; + virtual void* Alloc() = 0; + virtual void Free( void* ) = 0; + virtual void SetTracked() = 0; +}; + + +/* + Template child class to create pools of the correct type. +*/ +template< int ITEM_SIZE > +class MemPoolT : public MemPool +{ +public: + MemPoolT() : _blockPtrs(), _root(0), _currentAllocs(0), _nAllocs(0), _maxAllocs(0), _nUntracked(0) {} + ~MemPoolT() { + MemPoolT< ITEM_SIZE >::Clear(); + } + + void Clear() { + // Delete the blocks. + while( !_blockPtrs.Empty()) { + Block* lastBlock = _blockPtrs.Pop(); + delete lastBlock; + } + _root = 0; + _currentAllocs = 0; + _nAllocs = 0; + _maxAllocs = 0; + _nUntracked = 0; + } + + virtual int ItemSize() const { + return ITEM_SIZE; + } + int CurrentAllocs() const { + return _currentAllocs; + } + + virtual void* Alloc() { + if ( !_root ) { + // Need a new block. + Block* block = new Block(); + _blockPtrs.Push( block ); + + Item* blockItems = block->items; + for( int i = 0; i < ITEMS_PER_BLOCK - 1; ++i ) { + blockItems[i].next = &(blockItems[i + 1]); + } + blockItems[ITEMS_PER_BLOCK - 1].next = 0; + _root = blockItems; + } + Item* const result = _root; + TIXMLASSERT( result != 0 ); + _root = _root->next; + + ++_currentAllocs; + if ( _currentAllocs > _maxAllocs ) { + _maxAllocs = _currentAllocs; + } + ++_nAllocs; + ++_nUntracked; + return result; + } + + virtual void Free( void* mem ) { + if ( !mem ) { + return; + } + --_currentAllocs; + Item* item = static_cast( mem ); +#ifdef TINYXML2_DEBUG + memset( item, 0xfe, sizeof( *item ) ); +#endif + item->next = _root; + _root = item; + } + void Trace( const char* name ) { + printf( "Mempool %s watermark=%d [%dk] current=%d size=%d nAlloc=%d blocks=%d\n", + name, _maxAllocs, _maxAllocs * ITEM_SIZE / 1024, _currentAllocs, + ITEM_SIZE, _nAllocs, _blockPtrs.Size() ); + } + + void SetTracked() { + --_nUntracked; + } + + int Untracked() const { + return _nUntracked; + } + + // This number is perf sensitive. 4k seems like a good tradeoff on my machine. + // The test file is large, 170k. + // Release: VS2010 gcc(no opt) + // 1k: 4000 + // 2k: 4000 + // 4k: 3900 21000 + // 16k: 5200 + // 32k: 4300 + // 64k: 4000 21000 + // Declared public because some compilers do not accept to use ITEMS_PER_BLOCK + // in private part if ITEMS_PER_BLOCK is private + enum { ITEMS_PER_BLOCK = (4 * 1024) / ITEM_SIZE }; + +private: + MemPoolT( const MemPoolT& ); // not supported + void operator=( const MemPoolT& ); // not supported + + union Item { + Item* next; + char itemData[ITEM_SIZE]; + }; + struct Block { + Item items[ITEMS_PER_BLOCK]; + }; + DynArray< Block*, 10 > _blockPtrs; + Item* _root; + + int _currentAllocs; + int _nAllocs; + int _maxAllocs; + int _nUntracked; +}; + + + +/** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a XMLVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leafs + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its siblings will be visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the XMLDocument, although all nodes support visiting. + + You should never change the document from a callback. + + @sa XMLNode::Accept() +*/ +class TINYXML2_LIB XMLVisitor +{ +public: + virtual ~XMLVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const XMLDocument& /*doc*/ ) { + return true; + } + /// Visit a document. + virtual bool VisitExit( const XMLDocument& /*doc*/ ) { + return true; + } + + /// Visit an element. + virtual bool VisitEnter( const XMLElement& /*element*/, const XMLAttribute* /*firstAttribute*/ ) { + return true; + } + /// Visit an element. + virtual bool VisitExit( const XMLElement& /*element*/ ) { + return true; + } + + /// Visit a declaration. + virtual bool Visit( const XMLDeclaration& /*declaration*/ ) { + return true; + } + /// Visit a text node. + virtual bool Visit( const XMLText& /*text*/ ) { + return true; + } + /// Visit a comment node. + virtual bool Visit( const XMLComment& /*comment*/ ) { + return true; + } + /// Visit an unknown node. + virtual bool Visit( const XMLUnknown& /*unknown*/ ) { + return true; + } +}; + +// WARNING: must match XMLDocument::_errorNames[] +enum XMLError { + XML_SUCCESS = 0, + XML_NO_ATTRIBUTE, + XML_WRONG_ATTRIBUTE_TYPE, + XML_ERROR_FILE_NOT_FOUND, + XML_ERROR_FILE_COULD_NOT_BE_OPENED, + XML_ERROR_FILE_READ_ERROR, + XML_ERROR_PARSING_ELEMENT, + XML_ERROR_PARSING_ATTRIBUTE, + XML_ERROR_PARSING_TEXT, + XML_ERROR_PARSING_CDATA, + XML_ERROR_PARSING_COMMENT, + XML_ERROR_PARSING_DECLARATION, + XML_ERROR_PARSING_UNKNOWN, + XML_ERROR_EMPTY_DOCUMENT, + XML_ERROR_MISMATCHED_ELEMENT, + XML_ERROR_PARSING, + XML_CAN_NOT_CONVERT_TEXT, + XML_NO_TEXT_NODE, + XML_ELEMENT_DEPTH_EXCEEDED, + + XML_ERROR_COUNT +}; + + +/* + Utility functionality. +*/ +class TINYXML2_LIB XMLUtil +{ +public: + static const char* SkipWhiteSpace( const char* p, int* curLineNumPtr ) { + TIXMLASSERT( p ); + + while( IsWhiteSpace(*p) ) { + if (curLineNumPtr && *p == '\n') { + ++(*curLineNumPtr); + } + ++p; + } + TIXMLASSERT( p ); + return p; + } + static char* SkipWhiteSpace( char* const p, int* curLineNumPtr ) { + return const_cast( SkipWhiteSpace( const_cast(p), curLineNumPtr ) ); + } + + // Anything in the high order range of UTF-8 is assumed to not be whitespace. This isn't + // correct, but simple, and usually works. + static bool IsWhiteSpace( char p ) { + return !IsUTF8Continuation(p) && isspace( static_cast(p) ); + } + + inline static bool IsNameStartChar( unsigned char ch ) { + if ( ch >= 128 ) { + // This is a heuristic guess in attempt to not implement Unicode-aware isalpha() + return true; + } + if ( isalpha( ch ) ) { + return true; + } + return ch == ':' || ch == '_'; + } + + inline static bool IsNameChar( unsigned char ch ) { + return IsNameStartChar( ch ) + || isdigit( ch ) + || ch == '.' + || ch == '-'; + } + + inline static bool IsPrefixHex( const char* p) { + p = SkipWhiteSpace(p, 0); + return p && *p == '0' && ( *(p + 1) == 'x' || *(p + 1) == 'X'); + } + + inline static bool StringEqual( const char* p, const char* q, int nChar=INT_MAX ) { + if ( p == q ) { + return true; + } + TIXMLASSERT( p ); + TIXMLASSERT( q ); + TIXMLASSERT( nChar >= 0 ); + return strncmp( p, q, nChar ) == 0; + } + + inline static bool IsUTF8Continuation( const char p ) { + return ( p & 0x80 ) != 0; + } + + static const char* ReadBOM( const char* p, bool* hasBOM ); + // p is the starting location, + // the UTF-8 value of the entity will be placed in value, and length filled in. + static const char* GetCharacterRef( const char* p, char* value, int* length ); + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); + + // converts primitive types to strings + static void ToStr( int v, char* buffer, int bufferSize ); + static void ToStr( unsigned v, char* buffer, int bufferSize ); + static void ToStr( bool v, char* buffer, int bufferSize ); + static void ToStr( float v, char* buffer, int bufferSize ); + static void ToStr( double v, char* buffer, int bufferSize ); + static void ToStr(int64_t v, char* buffer, int bufferSize); + static void ToStr(uint64_t v, char* buffer, int bufferSize); + + // converts strings to primitive types + static bool ToInt( const char* str, int* value ); + static bool ToUnsigned( const char* str, unsigned* value ); + static bool ToBool( const char* str, bool* value ); + static bool ToFloat( const char* str, float* value ); + static bool ToDouble( const char* str, double* value ); + static bool ToInt64(const char* str, int64_t* value); + static bool ToUnsigned64(const char* str, uint64_t* value); + // Changes what is serialized for a boolean value. + // Default to "true" and "false". Shouldn't be changed + // unless you have a special testing or compatibility need. + // Be careful: static, global, & not thread safe. + // Be sure to set static const memory as parameters. + static void SetBoolSerialization(const char* writeTrue, const char* writeFalse); + +private: + static const char* writeBoolTrue; + static const char* writeBoolFalse; +}; + + +/** XMLNode is a base class for every object that is in the + XML Document Object Model (DOM), except XMLAttributes. + Nodes have siblings, a parent, and children which can + be navigated. A node is always in a XMLDocument. + The type of a XMLNode can be queried, and it can + be cast to its more defined type. + + A XMLDocument allocates memory for all its Nodes. + When the XMLDocument gets deleted, all its Nodes + will also be deleted. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + @endverbatim +*/ +class TINYXML2_LIB XMLNode +{ + friend class XMLDocument; + friend class XMLElement; +public: + + /// Get the XMLDocument that owns this XMLNode. + const XMLDocument* GetDocument() const { + TIXMLASSERT( _document ); + return _document; + } + /// Get the XMLDocument that owns this XMLNode. + XMLDocument* GetDocument() { + TIXMLASSERT( _document ); + return _document; + } + + /// Safely cast to an Element, or null. + virtual XMLElement* ToElement() { + return 0; + } + /// Safely cast to Text, or null. + virtual XMLText* ToText() { + return 0; + } + /// Safely cast to a Comment, or null. + virtual XMLComment* ToComment() { + return 0; + } + /// Safely cast to a Document, or null. + virtual XMLDocument* ToDocument() { + return 0; + } + /// Safely cast to a Declaration, or null. + virtual XMLDeclaration* ToDeclaration() { + return 0; + } + /// Safely cast to an Unknown, or null. + virtual XMLUnknown* ToUnknown() { + return 0; + } + + virtual const XMLElement* ToElement() const { + return 0; + } + virtual const XMLText* ToText() const { + return 0; + } + virtual const XMLComment* ToComment() const { + return 0; + } + virtual const XMLDocument* ToDocument() const { + return 0; + } + virtual const XMLDeclaration* ToDeclaration() const { + return 0; + } + virtual const XMLUnknown* ToUnknown() const { + return 0; + } + + /** The meaning of 'value' changes for the specific type. + @verbatim + Document: empty (NULL is returned, not an empty string) + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + const char* Value() const; + + /** Set the Value of an XML node. + @sa Value() + */ + void SetValue( const char* val, bool staticMem=false ); + + /// Gets the line number the node is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + + /// Get the parent of this node on the DOM. + const XMLNode* Parent() const { + return _parent; + } + + XMLNode* Parent() { + return _parent; + } + + /// Returns true if this node has no children. + bool NoChildren() const { + return !_firstChild; + } + + /// Get the first child node, or null if none exists. + const XMLNode* FirstChild() const { + return _firstChild; + } + + XMLNode* FirstChild() { + return _firstChild; + } + + /** Get the first child element, or optionally the first child + element with the specified name. + */ + const XMLElement* FirstChildElement( const char* name = 0 ) const; + + XMLElement* FirstChildElement( const char* name = 0 ) { + return const_cast(const_cast(this)->FirstChildElement( name )); + } + + /// Get the last child node, or null if none exists. + const XMLNode* LastChild() const { + return _lastChild; + } + + XMLNode* LastChild() { + return _lastChild; + } + + /** Get the last child element or optionally the last child + element with the specified name. + */ + const XMLElement* LastChildElement( const char* name = 0 ) const; + + XMLElement* LastChildElement( const char* name = 0 ) { + return const_cast(const_cast(this)->LastChildElement(name) ); + } + + /// Get the previous (left) sibling node of this node. + const XMLNode* PreviousSibling() const { + return _prev; + } + + XMLNode* PreviousSibling() { + return _prev; + } + + /// Get the previous (left) sibling element of this node, with an optionally supplied name. + const XMLElement* PreviousSiblingElement( const char* name = 0 ) const ; + + XMLElement* PreviousSiblingElement( const char* name = 0 ) { + return const_cast(const_cast(this)->PreviousSiblingElement( name ) ); + } + + /// Get the next (right) sibling node of this node. + const XMLNode* NextSibling() const { + return _next; + } + + XMLNode* NextSibling() { + return _next; + } + + /// Get the next (right) sibling element of this node, with an optionally supplied name. + const XMLElement* NextSiblingElement( const char* name = 0 ) const; + + XMLElement* NextSiblingElement( const char* name = 0 ) { + return const_cast(const_cast(this)->NextSiblingElement( name ) ); + } + + /** + Add a child node as the last (right) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertEndChild( XMLNode* addThis ); + + XMLNode* LinkEndChild( XMLNode* addThis ) { + return InsertEndChild( addThis ); + } + /** + Add a child node as the first (left) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertFirstChild( XMLNode* addThis ); + /** + Add a node after the specified child node. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the afterThis node + is not a child of this node, or if the node does not + belong to the same document. + */ + XMLNode* InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ); + + /** + Delete all the children of this node. + */ + void DeleteChildren(); + + /** + Delete a child of this node. + */ + void DeleteChild( XMLNode* node ); + + /** + Make a copy of this node, but not its children. + You may pass in a Document pointer that will be + the owner of the new Node. If the 'document' is + null, then the node returned will be allocated + from the current Document. (this->GetDocument()) + + Note: if called on a XMLDocument, this will return null. + */ + virtual XMLNode* ShallowClone( XMLDocument* document ) const = 0; + + /** + Make a copy of this node and all its children. + + If the 'target' is null, then the nodes will + be allocated in the current document. If 'target' + is specified, the memory will be allocated is the + specified XMLDocument. + + NOTE: This is probably not the correct tool to + copy a document, since XMLDocuments can have multiple + top level XMLNodes. You probably want to use + XMLDocument::DeepCopy() + */ + XMLNode* DeepClone( XMLDocument* target ) const; + + /** + Test if 2 nodes are the same, but don't test children. + The 2 nodes do not need to be in the same Document. + + Note: if called on a XMLDocument, this will return false. + */ + virtual bool ShallowEqual( const XMLNode* compare ) const = 0; + + /** Accept a hierarchical visit of the nodes in the TinyXML-2 DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the XMLVisitor interface. + + This is essentially a SAX interface for TinyXML-2. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML-2 is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + XMLPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( XMLVisitor* visitor ) const = 0; + + /** + Set user data into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void SetUserData(void* userData) { _userData = userData; } + + /** + Get user data set into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void* GetUserData() const { return _userData; } + +protected: + explicit XMLNode( XMLDocument* ); + virtual ~XMLNode(); + + virtual char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr); + + XMLDocument* _document; + XMLNode* _parent; + mutable StrPair _value; + int _parseLineNum; + + XMLNode* _firstChild; + XMLNode* _lastChild; + + XMLNode* _prev; + XMLNode* _next; + + void* _userData; + +private: + MemPool* _memPool; + void Unlink( XMLNode* child ); + static void DeleteNode( XMLNode* node ); + void InsertChildPreamble( XMLNode* insertThis ) const; + const XMLElement* ToElementWithName( const char* name ) const; + + XMLNode( const XMLNode& ); // not supported + XMLNode& operator=( const XMLNode& ); // not supported +}; + + +/** XML text. + + Note that a text node can have child element nodes, for example: + @verbatim + This is bold + @endverbatim + + A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCData() and query it with CData(). +*/ +class TINYXML2_LIB XMLText : public XMLNode +{ + friend class XMLDocument; +public: + virtual bool Accept( XMLVisitor* visitor ) const; + + virtual XMLText* ToText() { + return this; + } + virtual const XMLText* ToText() const { + return this; + } + + /// Declare whether this should be CDATA or standard text. + void SetCData( bool isCData ) { + _isCData = isCData; + } + /// Returns true if this is a CDATA text element. + bool CData() const { + return _isCData; + } + + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + explicit XMLText( XMLDocument* doc ) : XMLNode( doc ), _isCData( false ) {} + virtual ~XMLText() {} + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ); + +private: + bool _isCData; + + XMLText( const XMLText& ); // not supported + XMLText& operator=( const XMLText& ); // not supported +}; + + +/** An XML Comment. */ +class TINYXML2_LIB XMLComment : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLComment* ToComment() { + return this; + } + virtual const XMLComment* ToComment() const { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const; + + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + explicit XMLComment( XMLDocument* doc ); + virtual ~XMLComment(); + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr); + +private: + XMLComment( const XMLComment& ); // not supported + XMLComment& operator=( const XMLComment& ); // not supported +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXML-2 will happily read or write files without a declaration, + however. + + The text of the declaration isn't interpreted. It is parsed + and written as a string. +*/ +class TINYXML2_LIB XMLDeclaration : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLDeclaration* ToDeclaration() { + return this; + } + virtual const XMLDeclaration* ToDeclaration() const { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const; + + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + explicit XMLDeclaration( XMLDocument* doc ); + virtual ~XMLDeclaration(); + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ); + +private: + XMLDeclaration( const XMLDeclaration& ); // not supported + XMLDeclaration& operator=( const XMLDeclaration& ); // not supported +}; + + +/** Any tag that TinyXML-2 doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into XMLUnknowns. +*/ +class TINYXML2_LIB XMLUnknown : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLUnknown* ToUnknown() { + return this; + } + virtual const XMLUnknown* ToUnknown() const { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const; + + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + explicit XMLUnknown( XMLDocument* doc ); + virtual ~XMLUnknown(); + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ); + +private: + XMLUnknown( const XMLUnknown& ); // not supported + XMLUnknown& operator=( const XMLUnknown& ); // not supported +}; + + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not XMLNodes. You may only query the + Next() attribute in a list. +*/ +class TINYXML2_LIB XMLAttribute +{ + friend class XMLElement; +public: + /// The name of the attribute. + const char* Name() const; + + /// The value of the attribute. + const char* Value() const; + + /// Gets the line number the attribute is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + + /// The next attribute in the list. + const XMLAttribute* Next() const { + return _next; + } + + /** IntValue interprets the attribute as an integer, and returns the value. + If the value isn't an integer, 0 will be returned. There is no error checking; + use QueryIntValue() if you need error checking. + */ + int IntValue() const { + int i = 0; + QueryIntValue(&i); + return i; + } + + int64_t Int64Value() const { + int64_t i = 0; + QueryInt64Value(&i); + return i; + } + + uint64_t Unsigned64Value() const { + uint64_t i = 0; + QueryUnsigned64Value(&i); + return i; + } + + /// Query as an unsigned integer. See IntValue() + unsigned UnsignedValue() const { + unsigned i=0; + QueryUnsignedValue( &i ); + return i; + } + /// Query as a boolean. See IntValue() + bool BoolValue() const { + bool b=false; + QueryBoolValue( &b ); + return b; + } + /// Query as a double. See IntValue() + double DoubleValue() const { + double d=0; + QueryDoubleValue( &d ); + return d; + } + /// Query as a float. See IntValue() + float FloatValue() const { + float f=0; + QueryFloatValue( &f ); + return f; + } + + /** QueryIntValue interprets the attribute as an integer, and returns the value + in the provided parameter. The function will return XML_SUCCESS on success, + and XML_WRONG_ATTRIBUTE_TYPE if the conversion is not successful. + */ + XMLError QueryIntValue( int* value ) const; + /// See QueryIntValue + XMLError QueryUnsignedValue( unsigned int* value ) const; + /// See QueryIntValue + XMLError QueryInt64Value(int64_t* value) const; + /// See QueryIntValue + XMLError QueryUnsigned64Value(uint64_t* value) const; + /// See QueryIntValue + XMLError QueryBoolValue( bool* value ) const; + /// See QueryIntValue + XMLError QueryDoubleValue( double* value ) const; + /// See QueryIntValue + XMLError QueryFloatValue( float* value ) const; + + /// Set the attribute to a string value. + void SetAttribute( const char* value ); + /// Set the attribute to value. + void SetAttribute( int value ); + /// Set the attribute to value. + void SetAttribute( unsigned value ); + /// Set the attribute to value. + void SetAttribute(int64_t value); + /// Set the attribute to value. + void SetAttribute(uint64_t value); + /// Set the attribute to value. + void SetAttribute( bool value ); + /// Set the attribute to value. + void SetAttribute( double value ); + /// Set the attribute to value. + void SetAttribute( float value ); + +private: + enum { BUF_SIZE = 200 }; + + XMLAttribute() : _name(), _value(),_parseLineNum( 0 ), _next( 0 ), _memPool( 0 ) {} + virtual ~XMLAttribute() {} + + XMLAttribute( const XMLAttribute& ); // not supported + void operator=( const XMLAttribute& ); // not supported + void SetName( const char* name ); + + char* ParseDeep( char* p, bool processEntities, int* curLineNumPtr ); + + mutable StrPair _name; + mutable StrPair _value; + int _parseLineNum; + XMLAttribute* _next; + MemPool* _memPool; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TINYXML2_LIB XMLElement : public XMLNode +{ + friend class XMLDocument; +public: + /// Get the name of an element (which is the Value() of the node.) + const char* Name() const { + return Value(); + } + /// Set the name of the element. + void SetName( const char* str, bool staticMem=false ) { + SetValue( str, staticMem ); + } + + virtual XMLElement* ToElement() { + return this; + } + virtual const XMLElement* ToElement() const { + return this; + } + virtual bool Accept( XMLVisitor* visitor ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none + exists. For example: + + @verbatim + const char* value = ele->Attribute( "foo" ); + @endverbatim + + The 'value' parameter is normally null. However, if specified, + the attribute will only be returned if the 'name' and 'value' + match. This allow you to write code: + + @verbatim + if ( ele->Attribute( "foo", "bar" ) ) callFooIsBar(); + @endverbatim + + rather than: + @verbatim + if ( ele->Attribute( "foo" ) ) { + if ( strcmp( ele->Attribute( "foo" ), "bar" ) == 0 ) callFooIsBar(); + } + @endverbatim + */ + const char* Attribute( const char* name, const char* value=0 ) const; + + /** Given an attribute name, IntAttribute() returns the value + of the attribute interpreted as an integer. The default + value will be returned if the attribute isn't present, + or if there is an error. (For a method with error + checking, see QueryIntAttribute()). + */ + int IntAttribute(const char* name, int defaultValue = 0) const; + /// See IntAttribute() + unsigned UnsignedAttribute(const char* name, unsigned defaultValue = 0) const; + /// See IntAttribute() + int64_t Int64Attribute(const char* name, int64_t defaultValue = 0) const; + /// See IntAttribute() + uint64_t Unsigned64Attribute(const char* name, uint64_t defaultValue = 0) const; + /// See IntAttribute() + bool BoolAttribute(const char* name, bool defaultValue = false) const; + /// See IntAttribute() + double DoubleAttribute(const char* name, double defaultValue = 0) const; + /// See IntAttribute() + float FloatAttribute(const char* name, float defaultValue = 0) const; + + /** Given an attribute name, QueryIntAttribute() returns + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryIntAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + XMLError QueryIntAttribute( const char* name, int* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryIntValue( value ); + } + + /// See QueryIntAttribute() + XMLError QueryUnsignedAttribute( const char* name, unsigned int* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryUnsignedValue( value ); + } + + /// See QueryIntAttribute() + XMLError QueryInt64Attribute(const char* name, int64_t* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryInt64Value(value); + } + + /// See QueryIntAttribute() + XMLError QueryUnsigned64Attribute(const char* name, uint64_t* value) const { + const XMLAttribute* a = FindAttribute(name); + if(!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryUnsigned64Value(value); + } + + /// See QueryIntAttribute() + XMLError QueryBoolAttribute( const char* name, bool* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryBoolValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryDoubleAttribute( const char* name, double* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryDoubleValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryFloatAttribute( const char* name, float* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryFloatValue( value ); + } + + /// See QueryIntAttribute() + XMLError QueryStringAttribute(const char* name, const char** value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + *value = a->Value(); + return XML_SUCCESS; + } + + + + /** Given an attribute name, QueryAttribute() returns + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. It is overloaded for the primitive types, + and is a generally more convenient replacement of + QueryIntAttribute() and related functions. + + If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + XMLError QueryAttribute( const char* name, int* value ) const { + return QueryIntAttribute( name, value ); + } + + XMLError QueryAttribute( const char* name, unsigned int* value ) const { + return QueryUnsignedAttribute( name, value ); + } + + XMLError QueryAttribute(const char* name, int64_t* value) const { + return QueryInt64Attribute(name, value); + } + + XMLError QueryAttribute(const char* name, uint64_t* value) const { + return QueryUnsigned64Attribute(name, value); + } + + XMLError QueryAttribute( const char* name, bool* value ) const { + return QueryBoolAttribute( name, value ); + } + + XMLError QueryAttribute( const char* name, double* value ) const { + return QueryDoubleAttribute( name, value ); + } + + XMLError QueryAttribute( const char* name, float* value ) const { + return QueryFloatAttribute( name, value ); + } + + XMLError QueryAttribute(const char* name, const char** value) const { + return QueryStringAttribute(name, value); + } + + /// Sets the named attribute to value. + void SetAttribute( const char* name, const char* value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, int value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, unsigned value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + + /// Sets the named attribute to value. + void SetAttribute(const char* name, int64_t value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /// Sets the named attribute to value. + void SetAttribute(const char* name, uint64_t value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /// Sets the named attribute to value. + void SetAttribute( const char* name, bool value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, double value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, float value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + + /** + Delete an attribute. + */ + void DeleteAttribute( const char* name ); + + /// Return the first attribute in the list. + const XMLAttribute* FirstAttribute() const { + return _rootAttribute; + } + /// Query a specific attribute in the list. + const XMLAttribute* FindAttribute( const char* name ) const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the XMLText child + and accessing it directly. + + If the first child of 'this' is a XMLText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + */ + const char* GetText() const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, SetText() is limited compared to creating an XMLText child + and mutating it directly. + + If the first child of 'this' is a XMLText, SetText() sets its value to + the given string, otherwise it will create a first child that is an XMLText. + + This is a convenient method for setting the text of simple contained text: + @verbatim + This is text + fooElement->SetText( "Hullaballoo!" ); + Hullaballoo! + @endverbatim + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then it will not change "This is text", but rather prefix it with a text element: + @verbatim + Hullaballoo!This is text + @endverbatim + + For this XML: + @verbatim + + @endverbatim + SetText() will generate + @verbatim + Hullaballoo! + @endverbatim + */ + void SetText( const char* inText ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( int value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( unsigned value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(int64_t value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(uint64_t value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( bool value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( double value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( float value ); + + /** + Convenience method to query the value of a child text node. This is probably best + shown by example. Given you have a document is this form: + @verbatim + + 1 + 1.4 + + @endverbatim + + The QueryIntText() and similar functions provide a safe and easier way to get to the + "value" of x and y. + + @verbatim + int x = 0; + float y = 0; // types of x and y are contrived for example + const XMLElement* xElement = pointElement->FirstChildElement( "x" ); + const XMLElement* yElement = pointElement->FirstChildElement( "y" ); + xElement->QueryIntText( &x ); + yElement->QueryFloatText( &y ); + @endverbatim + + @returns XML_SUCCESS (0) on success, XML_CAN_NOT_CONVERT_TEXT if the text cannot be converted + to the requested type, and XML_NO_TEXT_NODE if there is no child text to query. + + */ + XMLError QueryIntText( int* ival ) const; + /// See QueryIntText() + XMLError QueryUnsignedText( unsigned* uval ) const; + /// See QueryIntText() + XMLError QueryInt64Text(int64_t* uval) const; + /// See QueryIntText() + XMLError QueryUnsigned64Text(uint64_t* uval) const; + /// See QueryIntText() + XMLError QueryBoolText( bool* bval ) const; + /// See QueryIntText() + XMLError QueryDoubleText( double* dval ) const; + /// See QueryIntText() + XMLError QueryFloatText( float* fval ) const; + + int IntText(int defaultValue = 0) const; + + /// See QueryIntText() + unsigned UnsignedText(unsigned defaultValue = 0) const; + /// See QueryIntText() + int64_t Int64Text(int64_t defaultValue = 0) const; + /// See QueryIntText() + uint64_t Unsigned64Text(uint64_t defaultValue = 0) const; + /// See QueryIntText() + bool BoolText(bool defaultValue = false) const; + /// See QueryIntText() + double DoubleText(double defaultValue = 0) const; + /// See QueryIntText() + float FloatText(float defaultValue = 0) const; + + /** + Convenience method to create a new XMLElement and add it as last (right) + child of this node. Returns the created and inserted element. + */ + XMLElement* InsertNewChildElement(const char* name); + /// See InsertNewChildElement() + XMLComment* InsertNewComment(const char* comment); + /// See InsertNewChildElement() + XMLText* InsertNewText(const char* text); + /// See InsertNewChildElement() + XMLDeclaration* InsertNewDeclaration(const char* text); + /// See InsertNewChildElement() + XMLUnknown* InsertNewUnknown(const char* text); + + + // internal: + enum ElementClosingType { + OPEN, // + CLOSED, // + CLOSING // + }; + ElementClosingType ClosingType() const { + return _closingType; + } + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ); + +private: + XMLElement( XMLDocument* doc ); + virtual ~XMLElement(); + XMLElement( const XMLElement& ); // not supported + void operator=( const XMLElement& ); // not supported + + XMLAttribute* FindOrCreateAttribute( const char* name ); + char* ParseAttributes( char* p, int* curLineNumPtr ); + static void DeleteAttribute( XMLAttribute* attribute ); + XMLAttribute* CreateAttribute(); + + enum { BUF_SIZE = 200 }; + ElementClosingType _closingType; + // The attribute list is ordered; there is no 'lastAttribute' + // because the list needs to be scanned for dupes before adding + // a new attribute. + XMLAttribute* _rootAttribute; +}; + + +enum Whitespace { + PRESERVE_WHITESPACE, + COLLAPSE_WHITESPACE +}; + + +/** A Document binds together all the functionality. + It can be saved, loaded, and printed to the screen. + All Nodes are connected and allocated to a Document. + If the Document is deleted, all its Nodes are also deleted. +*/ +class TINYXML2_LIB XMLDocument : public XMLNode +{ + friend class XMLElement; + // Gives access to SetError and Push/PopDepth, but over-access for everything else. + // Wishing C++ had "internal" scope. + friend class XMLNode; + friend class XMLText; + friend class XMLComment; + friend class XMLDeclaration; + friend class XMLUnknown; +public: + /// constructor + XMLDocument( bool processEntities = true, Whitespace whitespaceMode = PRESERVE_WHITESPACE ); + ~XMLDocument(); + + virtual XMLDocument* ToDocument() { + TIXMLASSERT( this == _document ); + return this; + } + virtual const XMLDocument* ToDocument() const { + TIXMLASSERT( this == _document ); + return this; + } + + /** + Parse an XML file from a character string. + Returns XML_SUCCESS (0) on success, or + an errorID. + + You may optionally pass in the 'nBytes', which is + the number of bytes which will be parsed. If not + specified, TinyXML-2 will assume 'xml' points to a + null terminated string. + */ + XMLError Parse( const char* xml, size_t nBytes=static_cast(-1) ); + + /** + Load an XML file from disk. + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError LoadFile( const char* filename ); + + /** + Load an XML file from disk. You are responsible + for providing and closing the FILE*. + + NOTE: The file should be opened as binary ("rb") + not text in order for TinyXML-2 to correctly + do newline normalization. + + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError LoadFile( FILE* ); + + /** + Save the XML file to disk. + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError SaveFile( const char* filename, bool compact = false ); + + /** + Save the XML file to disk. You are responsible + for providing and closing the FILE*. + + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError SaveFile( FILE* fp, bool compact = false ); + + bool ProcessEntities() const { + return _processEntities; + } + Whitespace WhitespaceMode() const { + return _whitespaceMode; + } + + /** + Returns true if this document has a leading Byte Order Mark of UTF8. + */ + bool HasBOM() const { + return _writeBOM; + } + /** Sets whether to write the BOM when writing the file. + */ + void SetBOM( bool useBOM ) { + _writeBOM = useBOM; + } + + /** Return the root element of DOM. Equivalent to FirstChildElement(). + To get the first node, use FirstChild(). + */ + XMLElement* RootElement() { + return FirstChildElement(); + } + const XMLElement* RootElement() const { + return FirstChildElement(); + } + + /** Print the Document. If the Printer is not provided, it will + print to stdout. If you provide Printer, this can print to a file: + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Or you can use a printer to print to memory: + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + // printer.CStr() has a const char* to the XML + @endverbatim + */ + void Print( XMLPrinter* streamer=0 ) const; + virtual bool Accept( XMLVisitor* visitor ) const; + + /** + Create a new Element associated with + this Document. The memory for the Element + is managed by the Document. + */ + XMLElement* NewElement( const char* name ); + /** + Create a new Comment associated with + this Document. The memory for the Comment + is managed by the Document. + */ + XMLComment* NewComment( const char* comment ); + /** + Create a new Text associated with + this Document. The memory for the Text + is managed by the Document. + */ + XMLText* NewText( const char* text ); + /** + Create a new Declaration associated with + this Document. The memory for the object + is managed by the Document. + + If the 'text' param is null, the standard + declaration is used.: + @verbatim + + @endverbatim + */ + XMLDeclaration* NewDeclaration( const char* text=0 ); + /** + Create a new Unknown associated with + this Document. The memory for the object + is managed by the Document. + */ + XMLUnknown* NewUnknown( const char* text ); + + /** + Delete a node associated with this document. + It will be unlinked from the DOM. + */ + void DeleteNode( XMLNode* node ); + + /// Clears the error flags. + void ClearError(); + + /// Return true if there was an error parsing the document. + bool Error() const { + return _errorID != XML_SUCCESS; + } + /// Return the errorID. + XMLError ErrorID() const { + return _errorID; + } + const char* ErrorName() const; + static const char* ErrorIDToName(XMLError errorID); + + /** Returns a "long form" error description. A hopefully helpful + diagnostic with location, line number, and/or additional info. + */ + const char* ErrorStr() const; + + /// A (trivial) utility function that prints the ErrorStr() to stdout. + void PrintError() const; + + /// Return the line where the error occurred, or zero if unknown. + int ErrorLineNum() const + { + return _errorLineNum; + } + + /// Clear the document, resetting it to the initial state. + void Clear(); + + /** + Copies this document to a target document. + The target will be completely cleared before the copy. + If you want to copy a sub-tree, see XMLNode::DeepClone(). + + NOTE: that the 'target' must be non-null. + */ + void DeepCopy(XMLDocument* target) const; + + // internal + char* Identify( char* p, XMLNode** node ); + + // internal + void MarkInUse(const XMLNode* const); + + virtual XMLNode* ShallowClone( XMLDocument* /*document*/ ) const { + return 0; + } + virtual bool ShallowEqual( const XMLNode* /*compare*/ ) const { + return false; + } + +private: + XMLDocument( const XMLDocument& ); // not supported + void operator=( const XMLDocument& ); // not supported + + bool _writeBOM; + bool _processEntities; + XMLError _errorID; + Whitespace _whitespaceMode; + mutable StrPair _errorStr; + int _errorLineNum; + char* _charBuffer; + int _parseCurLineNum; + int _parsingDepth; + // Memory tracking does add some overhead. + // However, the code assumes that you don't + // have a bunch of unlinked nodes around. + // Therefore it takes less memory to track + // in the document vs. a linked list in the XMLNode, + // and the performance is the same. + DynArray _unlinked; + + MemPoolT< sizeof(XMLElement) > _elementPool; + MemPoolT< sizeof(XMLAttribute) > _attributePool; + MemPoolT< sizeof(XMLText) > _textPool; + MemPoolT< sizeof(XMLComment) > _commentPool; + + static const char* _errorNames[XML_ERROR_COUNT]; + + void Parse(); + + void SetError( XMLError error, int lineNum, const char* format, ... ); + + // Something of an obvious security hole, once it was discovered. + // Either an ill-formed XML or an excessively deep one can overflow + // the stack. Track stack depth, and error out if needed. + class DepthTracker { + public: + explicit DepthTracker(XMLDocument * document) { + this->_document = document; + document->PushDepth(); + } + ~DepthTracker() { + _document->PopDepth(); + } + private: + XMLDocument * _document; + }; + void PushDepth(); + void PopDepth(); + + template + NodeType* CreateUnlinkedNode( MemPoolT& pool ); +}; + +template +inline NodeType* XMLDocument::CreateUnlinkedNode( MemPoolT& pool ) +{ + TIXMLASSERT( sizeof( NodeType ) == PoolElementSize ); + TIXMLASSERT( sizeof( NodeType ) == pool.ItemSize() ); + NodeType* returnNode = new (pool.Alloc()) NodeType( this ); + TIXMLASSERT( returnNode ); + returnNode->_memPool = &pool; + + _unlinked.Push(returnNode); + return returnNode; +} + +/** + A XMLHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that XMLHandle is not part of the TinyXML-2 + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + XMLElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + XMLElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + XMLElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + XMLElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. XMLHandle addresses the verbosity + of such code. A XMLHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + XMLHandle docHandle( &document ); + XMLElement* child2 = docHandle.FirstChildElement( "Document" ).FirstChildElement( "Element" ).FirstChildElement().NextSiblingElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + XMLHandle handleCopy = handle; + @endverbatim + + See also XMLConstHandle, which is the same as XMLHandle, but operates on const objects. +*/ +class TINYXML2_LIB XMLHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + explicit XMLHandle( XMLNode* node ) : _node( node ) { + } + /// Create a handle from a node. + explicit XMLHandle( XMLNode& node ) : _node( &node ) { + } + /// Copy constructor + XMLHandle( const XMLHandle& ref ) : _node( ref._node ) { + } + /// Assignment + XMLHandle& operator=( const XMLHandle& ref ) { + _node = ref._node; + return *this; + } + + /// Get the first child of this handle. + XMLHandle FirstChild() { + return XMLHandle( _node ? _node->FirstChild() : 0 ); + } + /// Get the first child element of this handle. + XMLHandle FirstChildElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->FirstChildElement( name ) : 0 ); + } + /// Get the last child of this handle. + XMLHandle LastChild() { + return XMLHandle( _node ? _node->LastChild() : 0 ); + } + /// Get the last child element of this handle. + XMLHandle LastChildElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->LastChildElement( name ) : 0 ); + } + /// Get the previous sibling of this handle. + XMLHandle PreviousSibling() { + return XMLHandle( _node ? _node->PreviousSibling() : 0 ); + } + /// Get the previous sibling element of this handle. + XMLHandle PreviousSiblingElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->PreviousSiblingElement( name ) : 0 ); + } + /// Get the next sibling of this handle. + XMLHandle NextSibling() { + return XMLHandle( _node ? _node->NextSibling() : 0 ); + } + /// Get the next sibling element of this handle. + XMLHandle NextSiblingElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->NextSiblingElement( name ) : 0 ); + } + + /// Safe cast to XMLNode. This can return null. + XMLNode* ToNode() { + return _node; + } + /// Safe cast to XMLElement. This can return null. + XMLElement* ToElement() { + return ( _node ? _node->ToElement() : 0 ); + } + /// Safe cast to XMLText. This can return null. + XMLText* ToText() { + return ( _node ? _node->ToText() : 0 ); + } + /// Safe cast to XMLUnknown. This can return null. + XMLUnknown* ToUnknown() { + return ( _node ? _node->ToUnknown() : 0 ); + } + /// Safe cast to XMLDeclaration. This can return null. + XMLDeclaration* ToDeclaration() { + return ( _node ? _node->ToDeclaration() : 0 ); + } + +private: + XMLNode* _node; +}; + + +/** + A variant of the XMLHandle class for working with const XMLNodes and Documents. It is the + same in all regards, except for the 'const' qualifiers. See XMLHandle for API. +*/ +class TINYXML2_LIB XMLConstHandle +{ +public: + explicit XMLConstHandle( const XMLNode* node ) : _node( node ) { + } + explicit XMLConstHandle( const XMLNode& node ) : _node( &node ) { + } + XMLConstHandle( const XMLConstHandle& ref ) : _node( ref._node ) { + } + + XMLConstHandle& operator=( const XMLConstHandle& ref ) { + _node = ref._node; + return *this; + } + + const XMLConstHandle FirstChild() const { + return XMLConstHandle( _node ? _node->FirstChild() : 0 ); + } + const XMLConstHandle FirstChildElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->FirstChildElement( name ) : 0 ); + } + const XMLConstHandle LastChild() const { + return XMLConstHandle( _node ? _node->LastChild() : 0 ); + } + const XMLConstHandle LastChildElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->LastChildElement( name ) : 0 ); + } + const XMLConstHandle PreviousSibling() const { + return XMLConstHandle( _node ? _node->PreviousSibling() : 0 ); + } + const XMLConstHandle PreviousSiblingElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->PreviousSiblingElement( name ) : 0 ); + } + const XMLConstHandle NextSibling() const { + return XMLConstHandle( _node ? _node->NextSibling() : 0 ); + } + const XMLConstHandle NextSiblingElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->NextSiblingElement( name ) : 0 ); + } + + + const XMLNode* ToNode() const { + return _node; + } + const XMLElement* ToElement() const { + return ( _node ? _node->ToElement() : 0 ); + } + const XMLText* ToText() const { + return ( _node ? _node->ToText() : 0 ); + } + const XMLUnknown* ToUnknown() const { + return ( _node ? _node->ToUnknown() : 0 ); + } + const XMLDeclaration* ToDeclaration() const { + return ( _node ? _node->ToDeclaration() : 0 ); + } + +private: + const XMLNode* _node; +}; + + +/** + Printing functionality. The XMLPrinter gives you more + options than the XMLDocument::Print() method. + + It can: + -# Print to memory. + -# Print to a file you provide. + -# Print XML without a XMLDocument. + + Print to Memory + + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + SomeFunction( printer.CStr() ); + @endverbatim + + Print to a File + + You provide the file pointer. + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Print without a XMLDocument + + When loading, an XML parser is very useful. However, sometimes + when saving, it just gets in the way. The code is often set up + for streaming, and constructing the DOM is just overhead. + + The Printer supports the streaming case. The following code + prints out a trivially simple XML file without ever creating + an XML document. + + @verbatim + XMLPrinter printer( fp ); + printer.OpenElement( "foo" ); + printer.PushAttribute( "foo", "bar" ); + printer.CloseElement(); + @endverbatim +*/ +class TINYXML2_LIB XMLPrinter : public XMLVisitor +{ +public: + /** Construct the printer. If the FILE* is specified, + this will print to the FILE. Else it will print + to memory, and the result is available in CStr(). + If 'compact' is set to true, then output is created + with only required whitespace and newlines. + */ + XMLPrinter( FILE* file=0, bool compact = false, int depth = 0 ); + virtual ~XMLPrinter() {} + + /** If streaming, write the BOM and declaration. */ + void PushHeader( bool writeBOM, bool writeDeclaration ); + /** If streaming, start writing an element. + The element must be closed with CloseElement() + */ + void OpenElement( const char* name, bool compactMode=false ); + /// If streaming, add an attribute to an open element. + void PushAttribute( const char* name, const char* value ); + void PushAttribute( const char* name, int value ); + void PushAttribute( const char* name, unsigned value ); + void PushAttribute( const char* name, int64_t value ); + void PushAttribute( const char* name, uint64_t value ); + void PushAttribute( const char* name, bool value ); + void PushAttribute( const char* name, double value ); + /// If streaming, close the Element. + virtual void CloseElement( bool compactMode=false ); + + /// Add a text node. + void PushText( const char* text, bool cdata=false ); + /// Add a text node from an integer. + void PushText( int value ); + /// Add a text node from an unsigned. + void PushText( unsigned value ); + /// Add a text node from a signed 64bit integer. + void PushText( int64_t value ); + /// Add a text node from an unsigned 64bit integer. + void PushText( uint64_t value ); + /// Add a text node from a bool. + void PushText( bool value ); + /// Add a text node from a float. + void PushText( float value ); + /// Add a text node from a double. + void PushText( double value ); + + /// Add a comment + void PushComment( const char* comment ); + + void PushDeclaration( const char* value ); + void PushUnknown( const char* value ); + + virtual bool VisitEnter( const XMLDocument& /*doc*/ ); + virtual bool VisitExit( const XMLDocument& /*doc*/ ) { + return true; + } + + virtual bool VisitEnter( const XMLElement& element, const XMLAttribute* attribute ); + virtual bool VisitExit( const XMLElement& element ); + + virtual bool Visit( const XMLText& text ); + virtual bool Visit( const XMLComment& comment ); + virtual bool Visit( const XMLDeclaration& declaration ); + virtual bool Visit( const XMLUnknown& unknown ); + + /** + If in print to memory mode, return a pointer to + the XML file in memory. + */ + const char* CStr() const { + return _buffer.Mem(); + } + /** + If in print to memory mode, return the size + of the XML file in memory. (Note the size returned + includes the terminating null.) + */ + int CStrSize() const { + return _buffer.Size(); + } + /** + If in print to memory mode, reset the buffer to the + beginning. + */ + void ClearBuffer( bool resetToFirstElement = true ) { + _buffer.Clear(); + _buffer.Push(0); + _firstElement = resetToFirstElement; + } + +protected: + virtual bool CompactMode( const XMLElement& ) { return _compactMode; } + + /** Prints out the space before an element. You may override to change + the space and tabs used. A PrintSpace() override should call Print(). + */ + virtual void PrintSpace( int depth ); + virtual void Print( const char* format, ... ); + virtual void Write( const char* data, size_t size ); + virtual void Putc( char ch ); + + inline void Write(const char* data) { Write(data, strlen(data)); } + + void SealElementIfJustOpened(); + bool _elementJustOpened; + DynArray< const char*, 10 > _stack; + +private: + /** + Prepares to write a new node. This includes sealing an element that was + just opened, and writing any whitespace necessary if not in compact mode. + */ + void PrepareForNewNode( bool compactMode ); + void PrintString( const char*, bool restrictedEntitySet ); // prints out, after detecting entities. + + bool _firstElement; + FILE* _fp; + int _depth; + int _textDepth; + bool _processEntities; + bool _compactMode; + + enum { + ENTITY_RANGE = 64, + BUF_SIZE = 200 + }; + bool _entityFlag[ENTITY_RANGE]; + bool _restrictedEntityFlag[ENTITY_RANGE]; + + DynArray< char, 20 > _buffer; + + // Prohibit cloning, intentionally not implemented + XMLPrinter( const XMLPrinter& ); + XMLPrinter& operator=( const XMLPrinter& ); +}; + + +} // tinyxml2 + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // TINYXML2_INCLUDED diff --git a/libs/IO/json.hpp b/libs/IO/json.hpp new file mode 100644 index 000000000..c9af0bed3 --- /dev/null +++ b/libs/IO/json.hpp @@ -0,0 +1,20406 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.5.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2018 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 5 +#define NLOHMANN_JSON_VERSION_PATCH 0 + +#include // all_of, find, for_each +#include // assert +#include // and, not, or +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // random_access_iterator_tag +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} // namespace nlohmann + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// manual branch prediction +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) + #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define JSON_LIKELY(x) x + #define JSON_UNLIKELY(x) x +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// #include + + +#include // not +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type + +namespace nlohmann +{ +namespace detail +{ +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // not +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval + +// #include + +// #include + + +#include // random_access_iterator_tag + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; +} +} + +// #include + +// #include + + +#include + +// #include + + +// http://en.cppreference.com/w/cpp/experimental/is_detected +namespace nlohmann +{ +namespace detail +{ +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + void operator=(nonesuch const&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template