diff --git a/.editorconfig b/.editorconfig index 8a938ecd..08f28f44 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,4 +5,8 @@ root = true [*] indent_size = 4 -indent_style = tab \ No newline at end of file +indent_style = tab + +[*.yml] +indent_size = 2 +indent_style = space diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml new file mode 100644 index 00000000..008b58ca --- /dev/null +++ b/.github/workflows/Build.yaml @@ -0,0 +1,221 @@ +name: Build + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ "**" ] + +jobs: + macOS-premake: + strategy: + matrix: + conf: + - Debug + - Release + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v3 + + - name: Download & install SDL + run: | + curl -L -o SDL2.dmg https://github.com/libsdl-org/SDL/releases/download/release-2.24.2/SDL2-2.24.2.dmg + hdiutil attach SDL2.dmg + cp -r /Volumes/SDL2/SDL2.framework RecastDemo/Bin + hdiutil detach /Volumes/SDL2 + rm SDL2.dmg + + - name: Download & install premake + working-directory: RecastDemo + run: | + curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-macosx.tar.gz + tar -xzf premake.tar.gz + rm premake.tar.gz + + - name: Run premake + working-directory: RecastDemo + run: ./premake5 xcode4 + + - name: Build With Xcode + working-directory: RecastDemo/Build/xcode4/ + run: xcodebuild -scheme RecastDemo -configuration ${{matrix.conf}} -project RecastDemo.xcodeproj build + + - name: Build Unit Tests With Xcode + working-directory: RecastDemo/Build/xcode4/ + run: xcodebuild -scheme Tests -configuration ${{matrix.conf}} -project Tests.xcodeproj build + + macos-cmake: + strategy: + matrix: + conf: + - Debug + - Release + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v3 + - name: Download & install SDL + run: | + curl -L -o SDL2.dmg https://github.com/libsdl-org/SDL/releases/download/release-2.24.2/SDL2-2.24.2.dmg + hdiutil attach SDL2.dmg + cp -r /Volumes/SDL2/SDL2.framework ${{github.workspace}}/RecastDemo/Bin/ + hdiutil detach /Volumes/SDL2 + rm SDL2.dmg + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.conf}} -DSDL2_LIBRARY=${{github.workspace}}/RecastDemo/Bin/SDL2.framework + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{matrix.conf}} + + linux-premake: + strategy: + matrix: + conf: + - debug + - release + compiler: + - clang + - gcc + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install opengl and SDL + run: | + sudo apt-get update + sudo apt-get install -y libgl1-mesa-dev libsdl2-dev + + - name: Download & Install premake + working-directory: RecastDemo + run: | + curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-linux.tar.gz + tar -xzf premake.tar.gz + rm premake.tar.gz + + - name: Run premake + working-directory: RecastDemo + run: ./premake5 --cc=${{matrix.compiler}} gmake2 + + - name: Build + working-directory: RecastDemo/Build/gmake2 + run: make config=${{matrix.conf}} verbose=true + + linux-cmake: + strategy: + matrix: + conf: + - Debug + - Release + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install opengl and SDL + run: | + sudo apt-get update + sudo apt-get install -y libgl1-mesa-dev libsdl2-dev + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.conf}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{matrix.conf}} + + windows-premake: + strategy: + matrix: + conf: + - Debug + - Release + vs-version: + - vs2019 + - vs2022 + include: + - vs-version: vs2019 + version-range: '16.0' + runner: windows-2019 + - vs-version: vs2022 + version-range: '17.0' + runner: windows-2022 + + runs-on: ${{matrix.runner}} + + steps: + - uses: actions/checkout@v3 + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + with: + vs-version: ${{matrix.version-range}} + + - name: Download and Install SDL + working-directory: RecastDemo/Contrib + shell: pwsh + run: | + (new-object System.Net.WebClient).DownloadFile("https://github.com/libsdl-org/SDL/releases/download/release-2.24.2/SDL2-devel-2.24.2-VC.zip","${{github.workspace}}/RecastDemo/Contrib/SDL.zip") + tar -xf SDL.zip + ren SDL2-2.24.2 SDL + del SDL.zip + + - name: Download and Install Premake + working-directory: RecastDemo + shell: pwsh + run: | + (new-object System.Net.WebClient).DownloadFile("https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-windows.zip","${{github.workspace}}/RecastDemo/premake.zip") + tar -xf premake.zip + del premake.zip + + - name: Run Premake + working-directory: RecastDemo + run: ./premake5.exe ${{matrix.vs-version}} + + - name: Build + working-directory: RecastDemo/Build/${{matrix.vs-version}} + run: msbuild RecastDemo.vcxproj -property:Configuration=${{matrix.conf}} + + windows-cmake: + strategy: + matrix: + conf: + - Debug + - Release + vs-version: + - vs2019 + - vs2022 + include: + - vs-version: vs2019 + cmake-generator: Visual Studio 16 2019 + runner: windows-2019 + - vs-version: vs2022 + cmake-generator: Visual Studio 17 2022 + runner: windows-2022 + + runs-on: ${{matrix.runner}} + + steps: + - uses: actions/checkout@v3 + + - name: Download and Install SDL + working-directory: RecastDemo/Contrib + shell: pwsh + run: | + (new-object System.Net.WebClient).DownloadFile("https://github.com/libsdl-org/SDL/releases/download/release-2.24.2/SDL2-devel-2.24.2-VC.zip","${{github.workspace}}/RecastDemo/Contrib/SDL.zip") + tar -xf SDL.zip + ren SDL2-2.24.2 SDL + del SDL.zip + + - name: Configure CMake + run: cmake -G "${{matrix.cmake-generator}}" -B ${{github.workspace}}/build -D CMAKE_BUILD_TYPE=${{matrix.conf}} -D CMAKE_INSTALL_PREFIX=${{github.workspace}}/build + + - name: Build with CMake + run: cmake --build ${{github.workspace}}/build --config ${{matrix.conf}} diff --git a/.github/workflows/Tests.yaml b/.github/workflows/Tests.yaml new file mode 100644 index 00000000..66e64f1f --- /dev/null +++ b/.github/workflows/Tests.yaml @@ -0,0 +1,109 @@ +name: Tests + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ "**" ] + +jobs: + macos-tests: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v3 + + - name: Download & install SDL + run: | + curl -L -o SDL2.dmg https://github.com/libsdl-org/SDL/releases/download/release-2.24.2/SDL2-2.24.2.dmg + hdiutil attach SDL2.dmg + cp -r /Volumes/SDL2/SDL2.framework RecastDemo/Bin + hdiutil detach /Volumes/SDL2 + rm SDL2.dmg + + - name: Download & install premake + working-directory: RecastDemo + run: | + curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-macosx.tar.gz + tar -xzf premake.tar.gz + rm premake.tar.gz + + - name: Run premake + working-directory: RecastDemo + run: ./premake5 xcode4 + + - name: Build Unit Tests With Xcode + working-directory: RecastDemo/Build/xcode4/ + run: xcodebuild -scheme Tests -configuration Debug -project Tests.xcodeproj build + + - name: Run unit tests + working-directory: RecastDemo/Bin + run: ./Tests + + linux-tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install opengl and SDL + run: | + sudo apt-get update + sudo apt-get install -y libgl1-mesa-dev libsdl2-dev + + - name: Download & Install premake + working-directory: RecastDemo + run: | + curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-linux.tar.gz + tar -xzf premake.tar.gz + rm premake.tar.gz + + - name: Run premake + working-directory: RecastDemo + run: ./premake5 --cc=clang gmake2 + + - name: Build + working-directory: RecastDemo/Build/gmake2 + run: make config=debug verbose=true + + - name: Run Tests + working-directory: RecastDemo/Bin + run: ./Tests + + windows-tests: + runs-on: windows-2022 + + steps: + - uses: actions/checkout@v3 + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + + - name: Download and Install SDL + working-directory: RecastDemo/Contrib + shell: pwsh + run: | + (new-object System.Net.WebClient).DownloadFile("https://github.com/libsdl-org/SDL/releases/download/release-2.24.2/SDL2-devel-2.24.2-VC.zip","${{github.workspace}}/RecastDemo/Contrib/SDL.zip") + tar -xf SDL.zip + ren SDL2-2.24.2 SDL + del SDL.zip + + - name: Download and Install Premake + working-directory: RecastDemo + shell: pwsh + run: | + (new-object System.Net.WebClient).DownloadFile("https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-windows.zip","${{github.workspace}}/RecastDemo/premake.zip") + tar -xf premake.zip + del premake.zip + + - name: Run Premake + working-directory: RecastDemo + run: ./premake5.exe vs2022 + + - name: Build + working-directory: RecastDemo/Build/vs2022 + run: msbuild Tests.vcxproj -property:Configuration=Debug + + - name: Run Tests + working-directory: RecastDemo/Bin/ + run: ./Tests.exe \ No newline at end of file diff --git a/.gitignore b/.gitignore index c4a9ba04..ccd8a58e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ RecastDemo/Bin/Tests # Build directory RecastDemo/Build +# XCode debug symbols archive +RecastDemo/Bin/*.dSYM + # Ignore meshes RecastDemo/Bin/Meshes/* @@ -43,3 +46,10 @@ RecastDemo/Contrib/SDL/* ## Generated doc files Docs/html + +## CMake build cache +build + +## IDE files +.idea/ +cmake-build-*/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6b504e3c..00000000 --- a/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ -sudo: false - -language: cpp - -# Build with gcc and clang. -compiler: - - gcc -# Disable clang for now since it seems there's a problem using it with fast fp math; it's likely Travis' ancient environment that's at fault. -# - clang - -# Build both debug and release configurations, through use of an environment variable in the build matrix. -env: - - CONFIGURATION=debug - - CONFIGURATION=release - -install: - # Download and build SDL2 from source. - - mkdir dummyprefix - - export PREFIX=$PWD/dummyprefix - - export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig - - export PATH=$PATH:$PWD/dummyprefix/bin - - wget https://www.libsdl.org/release/SDL2-2.0.4.tar.gz -O SDL2.tar.gz - - tar -xzf SDL2.tar.gz - - cd SDL2-2.0.4 - - ./configure --prefix=$PREFIX - - make -j5 - - make install - - cd .. - # Download and build premake5 from source; the Travis environment doesn't have the right version of glibc6 for the prebuilt binaries to work. - - wget https://github.com/premake/premake-core/releases/download/v5.0.0-alpha6/premake-5.0.0-alpha6-src.zip -O premake.zip - - unzip premake.zip - - cd premake-5.0.0-alpha6/build/gmake.unix - - make config=release - - cd ../../.. - - mv premake-5.0.0-alpha6/bin/release/premake5 premake5 - -# Run premake to generate makefiles. -# Have to cd into directory and back out since premake5 doesn't appear to accept a directory argument. -before_script: - - cd RecastDemo && ../premake5 gmake && cd .. - -# Run make in the directory containing generated makefiles, on the configuration specified by the environment variable. -script: - - make -C RecastDemo/Build/gmake config=$CONFIGURATION - - RecastDemo/Bin/Tests diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..d6fc2169 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.0) + +project(RecastNavigation) + +# lib versions +SET(SOVERSION 1) +set(LIB_VERSION 1.5.1) +string(REPLACE "." "," LIB_VERSION_NUM "${LIB_VERSION}.0") + +set_property(GLOBAL PROPERTY CXX_STANDARD 98) + +option(RECASTNAVIGATION_DEMO "Build demo" ON) +option(RECASTNAVIGATION_TESTS "Build tests" ON) +option(RECASTNAVIGATION_EXAMPLES "Build examples" ON) + +if(MSVC AND BUILD_SHARED_LIBS) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +configure_file( + "${RecastNavigation_SOURCE_DIR}/version.h.in" + "${RecastNavigation_BINARY_DIR}/version.h") +install(FILES "${RecastNavigation_BINARY_DIR}/version.h" DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) + +# Needed for recastnavigation.pc.in +set(prefix ${CMAKE_INSTALL_PREFIX}) +set(exec_prefix "\${prefix}") +set(libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") +set(bindir "\${exec_prefix}/${CMAKE_INSTALL_BINDIR}") +set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") +set(PACKAGE_VERSION "${LIB_VERSION}") +configure_file( + "${RecastNavigation_SOURCE_DIR}/recastnavigation.pc.in" + "${RecastNavigation_BINARY_DIR}/recastnavigation.pc" + @ONLY) +install(FILES "${RecastNavigation_BINARY_DIR}/recastnavigation.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + +add_subdirectory(DebugUtils) +add_subdirectory(Detour) +add_subdirectory(DetourCrowd) +add_subdirectory(DetourTileCache) +add_subdirectory(Recast) + +configure_package_config_file( + ${PROJECT_SOURCE_DIR}/recastnavigation-config.cmake.in + recastnavigation-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/recastnavigation +) + +write_basic_package_version_file( + recastnavigation-config-version.cmake + VERSION ${LIB_VERSION} + COMPATIBILITY AnyNewerVersion +) + +export( + EXPORT recastnavigation-targets + NAMESPACE RecastNavigation:: + FILE recastnavigation-targets.cmake +) + +install( + EXPORT recastnavigation-targets + NAMESPACE RecastNavigation:: + FILE recastnavigation-targets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/recastnavigation +) + +install( + FILES + ${PROJECT_BINARY_DIR}/recastnavigation-config.cmake + ${PROJECT_BINARY_DIR}/recastnavigation-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/recastnavigation +) + +if (RECASTNAVIGATION_DEMO) + add_subdirectory(RecastDemo) +endif () + +if (RECASTNAVIGATION_TESTS) + enable_testing() + add_subdirectory(Tests) +endif () diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3cfdc216..d46d138e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,30 +3,23 @@ We'd love for you to contribute to our source code and to make Recast and Detour even better than they are today! Here are the guidelines we'd like you to follow: - - [Code of Conduct](#coc) - - [Question or Problem?](#question) - - [Issues and Bugs](#issue) - - [Feature Requests](#feature) - - [Submission Guidelines](#submission-guidelines) - - [Git Commit Guidelines](#git-commit-guidelines) - -## Code of Conduct +## Code of Conduct This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. -## Got a Question or Problem? +## Got a Question or Problem? If you have questions about how to use Recast or Detour, please direct these to the [Google Group][groups] discussion list. We are also available on [Gitter][gitter]. -## Found an Issue? +## Found an Issue? If you find a bug in the source code or a mistake in the documentation, you can help us by submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request with a fix. **Please see the Submission Guidelines below**. -## Want a Feature? +## Want a Feature? You can request a new feature by submitting an issue to our [GitHub Repository][github]. If you would like to implement a new feature then consider what kind of change it is: @@ -78,8 +71,7 @@ Before you submit your pull request consider the following guidelines: ``` * Implement your changes, **including appropriate tests if appropriate/possible**. -* Commit your changes using a descriptive commit message that follows our - [commit message conventions](#commit-message-format). +* Commit your changes using a descriptive commit message that follows our commit message conventions (see below). ```shell git commit -a @@ -108,10 +100,10 @@ Before you submit your pull request consider the following guidelines: If you have rebased to squash commits together, you will need to force push to update the PR: - ```shell - git rebase master -i - git push origin my-fix-branch -f - ``` +```shell +git rebase master -i +git push origin my-fix-branch -f +``` That's it! Thank you for your contribution! diff --git a/DebugUtils/CMakeLists.txt b/DebugUtils/CMakeLists.txt new file mode 100644 index 00000000..b04e67d5 --- /dev/null +++ b/DebugUtils/CMakeLists.txt @@ -0,0 +1,39 @@ +file(GLOB SOURCES Source/*.cpp) +add_library(DebugUtils ${SOURCES}) + +add_library(RecastNavigation::DebugUtils ALIAS DebugUtils) +set_target_properties(DebugUtils PROPERTIES DEBUG_POSTFIX -d) + +set(DebugUtils_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") + +target_include_directories(DebugUtils PUBLIC + "$" +) + +target_link_libraries(DebugUtils + Recast + Detour + DetourTileCache +) + +set_target_properties(DebugUtils PROPERTIES + SOVERSION ${SOVERSION} + VERSION ${LIB_VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "DebugUtils-d" + ) + +install(TARGETS DebugUtils + EXPORT recastnavigation-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation + ) + +file(GLOB INCLUDES Include/*.h) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +if(MSVC) + install(FILES "$/DebugUtils-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") +endif() diff --git a/DebugUtils/Include/DebugDraw.h b/DebugUtils/Include/DebugDraw.h index 00b544d1..8ebe9eb2 100644 --- a/DebugUtils/Include/DebugDraw.h +++ b/DebugUtils/Include/DebugDraw.h @@ -27,7 +27,7 @@ enum duDebugDrawPrimitives DU_DRAW_POINTS, DU_DRAW_LINES, DU_DRAW_TRIS, - DU_DRAW_QUADS, + DU_DRAW_QUADS }; /// Abstract debug draw interface. @@ -57,11 +57,13 @@ struct duDebugDraw /// Submit a vertex /// @param pos [in] position of the verts. /// @param color [in] color of the verts. + /// @param uv [in] the uv coordinates of the verts. virtual void vertex(const float* pos, unsigned int color, const float* uv) = 0; /// Submit a vertex /// @param x,y,z [in] position of the verts. /// @param color [in] color of the verts. + /// @param u,v [in] the uv coordinates of the verts. virtual void vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v) = 0; /// End drawing primitives. @@ -197,15 +199,15 @@ class duDisplayList : public duDebugDraw int m_size; int m_cap; - bool m_depthMask; duDebugDrawPrimitives m_prim; float m_primSize; + bool m_depthMask; void resize(int cap); public: duDisplayList(int cap = 512); - ~duDisplayList(); + virtual ~duDisplayList(); virtual void depthMask(bool state); virtual void begin(duDebugDrawPrimitives prim, float size = 1.0f); virtual void vertex(const float x, const float y, const float z, unsigned int color); diff --git a/DebugUtils/Include/DetourDebugDraw.h b/DebugUtils/Include/DetourDebugDraw.h index ff2ca2f9..d1a808f1 100755 --- a/DebugUtils/Include/DetourDebugDraw.h +++ b/DebugUtils/Include/DetourDebugDraw.h @@ -27,7 +27,7 @@ enum DrawNavMeshFlags { DU_DRAWNAVMESH_OFFMESHCONS = 0x01, DU_DRAWNAVMESH_CLOSEDLIST = 0x02, - DU_DRAWNAVMESH_COLOR_TILES = 0x04, + DU_DRAWNAVMESH_COLOR_TILES = 0x04 }; void duDebugDrawNavMesh(struct duDebugDraw* dd, const dtNavMesh& mesh, unsigned char flags); diff --git a/DebugUtils/Include/RecastDump.h b/DebugUtils/Include/RecastDump.h index 6a722fda..18cc891a 100644 --- a/DebugUtils/Include/RecastDump.h +++ b/DebugUtils/Include/RecastDump.h @@ -21,7 +21,7 @@ struct duFileIO { - virtual ~duFileIO() = 0; + virtual ~duFileIO(); virtual bool isWriting() const = 0; virtual bool isReading() const = 0; virtual bool write(const void* ptr, const size_t size) = 0; @@ -39,5 +39,4 @@ bool duReadCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io); void duLogBuildTimes(rcContext& ctx, const int totalTileUsec); - #endif // RECAST_DUMP_H diff --git a/DebugUtils/Source/DebugDraw.cpp b/DebugUtils/Source/DebugDraw.cpp index d0179bca..655695d1 100644 --- a/DebugUtils/Source/DebugDraw.cpp +++ b/DebugUtils/Source/DebugDraw.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include "DebugDraw.h" #include "DetourMath.h" @@ -530,9 +529,9 @@ duDisplayList::duDisplayList(int cap) : m_color(0), m_size(0), m_cap(0), - m_depthMask(true), m_prim(DU_DRAW_LINES), - m_primSize(1.0f) + m_primSize(1.0f), + m_depthMask(true) { if (cap < 8) cap = 8; diff --git a/DebugUtils/Source/DetourDebugDraw.cpp b/DebugUtils/Source/DetourDebugDraw.cpp index dd4bad3f..4ca0581c 100644 --- a/DebugUtils/Source/DetourDebugDraw.cpp +++ b/DebugUtils/Source/DetourDebugDraw.cpp @@ -101,7 +101,9 @@ static void drawPolyBoundaries(duDebugDraw* dd, const dtMeshTile* tile, } for (int m = 0, n = 2; m < 3; n=m++) { - if (((t[3] >> (n*2)) & 0x3) == 0) continue; // Skip inner detail edges. + if ((dtGetDetailTriEdgeFlags(t[3], n) & DT_DETAIL_EDGE_BOUNDARY) == 0) + continue; + if (distancePtLine2d(tv[n],v0,v1) < thr && distancePtLine2d(tv[m],v0,v1) < thr) { diff --git a/DebugUtils/Source/RecastDebugDraw.cpp b/DebugUtils/Source/RecastDebugDraw.cpp index c1a73a16..466c5b17 100644 --- a/DebugUtils/Source/RecastDebugDraw.cpp +++ b/DebugUtils/Source/RecastDebugDraw.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include "DebugDraw.h" #include "RecastDebugDraw.h" diff --git a/DebugUtils/Source/RecastDump.cpp b/DebugUtils/Source/RecastDump.cpp index 20938251..69880955 100644 --- a/DebugUtils/Source/RecastDump.cpp +++ b/DebugUtils/Source/RecastDump.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -25,10 +24,9 @@ #include "RecastAlloc.h" #include "RecastDump.h" - duFileIO::~duFileIO() { - // Empty + // Defined out of line to fix the weak v-tables warning } static void ioprintf(duFileIO* io, const char* format, ...) diff --git a/Detour/CMakeLists.txt b/Detour/CMakeLists.txt new file mode 100644 index 00000000..0c126831 --- /dev/null +++ b/Detour/CMakeLists.txt @@ -0,0 +1,33 @@ +file(GLOB SOURCES Source/*.cpp) +add_library(Detour ${SOURCES}) + +add_library(RecastNavigation::Detour ALIAS Detour) +set_target_properties(Detour PROPERTIES DEBUG_POSTFIX -d) + +set(Detour_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") + +target_include_directories(Detour PUBLIC + "$" +) + +set_target_properties(Detour PROPERTIES + SOVERSION ${SOVERSION} + VERSION ${LIB_VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "Detour-d" + ) + +install(TARGETS Detour + EXPORT recastnavigation-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation + ) + +file(GLOB INCLUDES Include/*.h) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +if(MSVC) + install(FILES "$/Detour-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") +endif() diff --git a/Detour/Include/DetourCommon.h b/Detour/Include/DetourCommon.h index 739858cd..d54bc7ed 100644 --- a/Detour/Include/DetourCommon.h +++ b/Detour/Include/DetourCommon.h @@ -37,7 +37,6 @@ feature to find minor members. /// Used to ignore a function parameter. VS complains about unused parameters /// and this silences the warning. -/// @param [in] _ Unused parameter template void dtIgnoreUnused(const T&) { } /// Swaps the values of the two parameters. @@ -283,6 +282,28 @@ inline bool dtVequal(const float* p0, const float* p1) return d < thr; } +/// Checks that the specified vector's components are all finite. +/// @param[in] v A point. [(x, y, z)] +/// @return True if all of the point's components are finite, i.e. not NaN +/// or any of the infinities. +inline bool dtVisfinite(const float* v) +{ + bool result = + dtMathIsfinite(v[0]) && + dtMathIsfinite(v[1]) && + dtMathIsfinite(v[2]); + + return result; +} + +/// Checks that the specified vector's 2D components are finite. +/// @param[in] v A point. [(x, y, z)] +inline bool dtVisfinite2D(const float* v) +{ + bool result = dtMathIsfinite(v[0]) && dtMathIsfinite(v[2]); + return result; +} + /// Derives the dot product of two vectors on the xz-plane. (@p u . @p v) /// @param[in] u A vector [(x, y, z)] /// @param[in] v A vector [(x, y, z)] @@ -297,7 +318,7 @@ inline float dtVdot2D(const float* u, const float* v) /// Derives the xz-plane 2D perp product of the two vectors. (uz*vx - ux*vz) /// @param[in] u The LHV vector [(x, y, z)] /// @param[in] v The RHV vector [(x, y, z)] -/// @return The dot product on the xz-plane. +/// @return The perp dot product on the xz-plane. /// /// The vectors are projected onto the xz-plane, so the y-values are ignored. inline float dtVperp2D(const float* u, const float* v) diff --git a/Detour/Include/DetourMath.h b/Detour/Include/DetourMath.h index 95e14f88..54af8af0 100644 --- a/Detour/Include/DetourMath.h +++ b/Detour/Include/DetourMath.h @@ -8,6 +8,9 @@ Members in this module are wrappers around the standard math library #define DETOURMATH_H #include +// This include is required because libstdc++ has problems with isfinite +// if cmath is included before math.h. +#include inline float dtMathFabsf(float x) { return fabsf(x); } inline float dtMathSqrtf(float x) { return sqrtf(x); } @@ -16,5 +19,6 @@ inline float dtMathCeilf(float x) { return ceilf(x); } inline float dtMathCosf(float x) { return cosf(x); } inline float dtMathSinf(float x) { return sinf(x); } inline float dtMathAtan2f(float y, float x) { return atan2f(y, x); } +inline bool dtMathIsfinite(float x) { return std::isfinite(x); } #endif diff --git a/Detour/Include/DetourNavMesh.h b/Detour/Include/DetourNavMesh.h index 164e152f..d7dff9b0 100644 --- a/Detour/Include/DetourNavMesh.h +++ b/Detour/Include/DetourNavMesh.h @@ -92,7 +92,7 @@ static const int DT_MAX_AREAS = 64; enum dtTileFlags { /// The navigation mesh owns the tile memory and is responsible for freeing it. - DT_TILE_FREE_DATA = 0x01, + DT_TILE_FREE_DATA = 0x01 }; /// Vertex flags returned by dtNavMeshQuery::findStraightPath. @@ -100,27 +100,32 @@ enum dtStraightPathFlags { DT_STRAIGHTPATH_START = 0x01, ///< The vertex is the start position in the path. DT_STRAIGHTPATH_END = 0x02, ///< The vertex is the end position in the path. - DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04, ///< The vertex is the start of an off-mesh connection. + DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04 ///< The vertex is the start of an off-mesh connection. }; /// Options for dtNavMeshQuery::findStraightPath. enum dtStraightPathOptions { DT_STRAIGHTPATH_AREA_CROSSINGS = 0x01, ///< Add a vertex at every polygon edge crossing where area changes. - DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02, ///< Add a vertex at every polygon edge crossing. + DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02 ///< Add a vertex at every polygon edge crossing. }; /// Options for dtNavMeshQuery::initSlicedFindPath and updateSlicedFindPath enum dtFindPathOptions { - DT_FINDPATH_ANY_ANGLE = 0x02, ///< use raycasts during pathfind to "shortcut" (raycast still consider costs) + DT_FINDPATH_ANY_ANGLE = 0x02 ///< use raycasts during pathfind to "shortcut" (raycast still consider costs) }; /// Options for dtNavMeshQuery::raycast enum dtRaycastOptions { - DT_RAYCAST_USE_COSTS = 0x01, ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost + DT_RAYCAST_USE_COSTS = 0x01 ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost +}; + +enum dtDetailTriEdgeFlags +{ + DT_DETAIL_EDGE_BOUNDARY = 0x01 ///< Detail triangle edge is part of the poly boundary }; @@ -134,7 +139,7 @@ enum dtPolyTypes /// The polygon is a standard convex polygon that is part of the surface of the mesh. DT_POLYTYPE_GROUND = 0, /// The polygon is an off-mesh connection consisting of two vertices. - DT_POLYTYPE_OFFMESH_CONNECTION = 1, + DT_POLYTYPE_OFFMESH_CONNECTION = 1 }; @@ -273,14 +278,15 @@ struct dtMeshTile unsigned int linksFreeList; ///< Index to the next free link. dtMeshHeader* header; ///< The tile header. dtPoly* polys; ///< The tile polygons. [Size: dtMeshHeader::polyCount] - float* verts; ///< The tile vertices. [Size: dtMeshHeader::vertCount] + float* verts; ///< The tile vertices. [(x, y, z) * dtMeshHeader::vertCount] dtLink* links; ///< The tile links. [Size: dtMeshHeader::maxLinkCount] dtPolyDetail* detailMeshes; ///< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount] /// The detail mesh's unique vertices. [(x, y, z) * dtMeshHeader::detailVertCount] float* detailVerts; - /// The detail mesh's triangles. [(vertA, vertB, vertC) * dtMeshHeader::detailTriCount] + /// The detail mesh's triangles. [(vertA, vertB, vertC, triFlags) * dtMeshHeader::detailTriCount]. + /// See dtDetailTriEdgeFlags and dtGetDetailTriEdgeFlags. unsigned char* detailTris; /// The tile bounding volume nodes. [Size: dtMeshHeader::bvNodeCount] @@ -298,6 +304,15 @@ private: dtMeshTile& operator=(const dtMeshTile&); }; +/// Get flags for edge in detail triangle. +/// @param[in] triFlags The flags for the triangle (last component of detail vertices above). +/// @param[in] edgeIndex The index of the first vertex of the edge. For instance, if 0, +/// returns flags for edge AB. +inline int dtGetDetailTriEdgeFlags(unsigned char triFlags, int edgeIndex) +{ + return (triFlags >> (edgeIndex * 2)) & 0x3; +} + /// Configuration parameters used to define multi-tile navigation meshes. /// The values are used to allocate space during the initialization of a navigation mesh. /// @see dtNavMesh::init() @@ -307,8 +322,8 @@ struct dtNavMeshParams float orig[3]; ///< The world space origin of the navigation mesh's tile space. [(x, y, z)] float tileWidth; ///< The width of each tile. (Along the x-axis.) float tileHeight; ///< The height of each tile. (Along the z-axis.) - int maxTiles; ///< The maximum number of tiles the navigation mesh can contain. - int maxPolys; ///< The maximum number of polygons each tile can contain. + int maxTiles; ///< The maximum number of tiles the navigation mesh can contain. This and maxPolys are used to calculate how many bits are needed to identify tiles and polygons uniquely. + int maxPolys; ///< The maximum number of polygons each tile can contain. This and maxTiles are used to calculate how many bits are needed to identify tiles and polygons uniquely. }; /// A navigation mesh based on tiles of convex polygons. @@ -628,7 +643,9 @@ private: dtPolyRef* polys, const int maxPolys) const; /// Find nearest polygon within a tile. dtPolyRef findNearestPolyInTile(const dtMeshTile* tile, const float* center, - const float* extents, float* nearestPt) const; + const float* halfExtents, float* nearestPt) const; + /// Returns whether position is over the poly and the height at the position if so. + bool getPolyHeight(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* height) const; /// Returns closest point on polygon. void closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const; @@ -648,6 +665,8 @@ private: unsigned int m_tileBits; ///< Number of tile bits in the tile ID. unsigned int m_polyBits; ///< Number of poly bits in the tile ID. #endif + + friend class dtNavMeshQuery; }; /// Allocates a navigation mesh object using the Detour allocator. diff --git a/Detour/Include/DetourNavMeshQuery.h b/Detour/Include/DetourNavMeshQuery.h index 0b0467b9..1fafb972 100644 --- a/Detour/Include/DetourNavMeshQuery.h +++ b/Detour/Include/DetourNavMeshQuery.h @@ -112,8 +112,6 @@ public: }; - - /// Provides information about raycast hit /// filled by dtNavMeshQuery::raycast /// @ingroup detour @@ -147,7 +145,7 @@ struct dtRaycastHit class dtPolyQuery { public: - virtual ~dtPolyQuery() { } + virtual ~dtPolyQuery(); /// Called for each batch of unique polygons touched by the search area in dtNavMeshQuery::queryPolygons. /// This can be called multiple times for a single query. @@ -170,7 +168,7 @@ public: dtStatus init(const dtNavMesh* nav, const int maxNodes); /// @name Standard Pathfinding Functions - // /@{ + /// @{ /// Finds a path from the start polygon to the end polygon. /// @param[in] startRef The refrence id of the start polygon. @@ -308,34 +306,50 @@ public: ///@{ /// Finds the polygon nearest to the specified center point. + /// [opt] means the specified parameter can be a null pointer, in that case the output parameter will not be set. + /// /// @param[in] center The center of the search box. [(x, y, z)] - /// @param[in] extents The search distance along each axis. [(x, y, z)] + /// @param[in] halfExtents The search distance along each axis. [(x, y, z)] /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] nearestRef The reference id of the nearest polygon. - /// @param[out] nearestPt The nearest point on the polygon. [opt] [(x, y, z)] + /// @param[out] nearestRef The reference id of the nearest polygon. Will be set to 0 if no polygon is found. + /// @param[out] nearestPt The nearest point on the polygon. Unchanged if no polygon is found. [opt] [(x, y, z)] /// @returns The status flags for the query. - dtStatus findNearestPoly(const float* center, const float* extents, + dtStatus findNearestPoly(const float* center, const float* halfExtents, const dtQueryFilter* filter, dtPolyRef* nearestRef, float* nearestPt) const; + + /// Finds the polygon nearest to the specified center point. + /// [opt] means the specified parameter can be a null pointer, in that case the output parameter will not be set. + /// + /// @param[in] center The center of the search box. [(x, y, z)] + /// @param[in] halfExtents The search distance along each axis. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. + /// @param[out] nearestRef The reference id of the nearest polygon. Will be set to 0 if no polygon is found. + /// @param[out] nearestPt The nearest point on the polygon. Unchanged if no polygon is found. [opt] [(x, y, z)] + /// @param[out] isOverPoly Set to true if the point's X/Z coordinate lies inside the polygon, false otherwise. Unchanged if no polygon is found. [opt] + /// @returns The status flags for the query. + dtStatus findNearestPoly(const float* center, const float* halfExtents, + const dtQueryFilter* filter, + dtPolyRef* nearestRef, float* nearestPt, bool* isOverPoly) const; /// Finds polygons that overlap the search box. /// @param[in] center The center of the search box. [(x, y, z)] - /// @param[in] extents The search distance along each axis. [(x, y, z)] + /// @param[in] halfExtents The search distance along each axis. [(x, y, z)] /// @param[in] filter The polygon filter to apply to the query. /// @param[out] polys The reference ids of the polygons that overlap the query box. /// @param[out] polyCount The number of polygons in the search result. /// @param[in] maxPolys The maximum number of polygons the search result can hold. /// @returns The status flags for the query. - dtStatus queryPolygons(const float* center, const float* extents, + dtStatus queryPolygons(const float* center, const float* halfExtents, const dtQueryFilter* filter, dtPolyRef* polys, int* polyCount, const int maxPolys) const; /// Finds polygons that overlap the search box. /// @param[in] center The center of the search box. [(x, y, z)] - /// @param[in] extents The search distance along each axis. [(x, y, z)] + /// @param[in] halfExtents The search distance along each axis. [(x, y, z)] /// @param[in] filter The polygon filter to apply to the query. /// @param[in] query The query. Polygons found will be batched together and passed to this query. - dtStatus queryPolygons(const float* center, const float* extents, + dtStatus queryPolygons(const float* center, const float* halfExtents, const dtQueryFilter* filter, dtPolyQuery* query) const; /// Finds the non-overlapping navigation polygons in the local neighbourhood around the center position. @@ -375,9 +389,9 @@ public: /// @param[in] startPos A position within the start polygon representing /// the start of the ray. [(x, y, z)] /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. /// @param[out] t The hit parameter. (FLT_MAX if no wall hit.) /// @param[out] hitNormal The normal of the nearest wall hit. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. /// @param[out] path The reference ids of the visited polygons. [opt] /// @param[out] pathCount The number of visited polygons. [opt] /// @param[in] maxPath The maximum number of polygons the @p path array can hold. @@ -393,7 +407,7 @@ public: /// the start of the ray. [(x, y, z)] /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] /// @param[in] filter The polygon filter to apply to the query. - /// @param[in] flags govern how the raycast behaves. See dtRaycastOptions + /// @param[in] options govern how the raycast behaves. See dtRaycastOptions /// @param[out] hit Pointer to a raycast hit structure which will be filled by the results. /// @param[in] prevRef parent of start ref. Used during for cost calculation [opt] /// @returns The status flags for the query. @@ -444,6 +458,7 @@ public: /// The location is not exactly constrained by the circle, but it limits the visited polygons. /// @param[in] startRef The reference id of the polygon where the search starts. /// @param[in] centerPos The center of the search circle. [(x, y, z)] + /// @param[in] maxRadius The radius of the search circle. [Units: wu] /// @param[in] filter The polygon filter to apply to the query. /// @param[in] frand Function returning a random number [0..1). /// @param[out] randomRef The reference id of the random location. diff --git a/Detour/Include/DetourNode.h b/Detour/Include/DetourNode.h index 3345deb0..793e1a9b 100644 --- a/Detour/Include/DetourNode.h +++ b/Detour/Include/DetourNode.h @@ -26,7 +26,7 @@ enum dtNodeFlags { DT_NODE_OPEN = 0x01, DT_NODE_CLOSED = 0x02, - DT_NODE_PARENT_DETACHED = 0x04, // parent of the node is not adjacent. Found using raycast. + DT_NODE_PARENT_DETACHED = 0x04 // parent of the node is not adjacent. Found using raycast. }; static const dtNodeIndex DT_NULL_IDX = (dtNodeIndex)~0; diff --git a/Detour/Include/DetourStatus.h b/Detour/Include/DetourStatus.h index af822c4a..8e1bb44b 100644 --- a/Detour/Include/DetourStatus.h +++ b/Detour/Include/DetourStatus.h @@ -35,6 +35,7 @@ static const unsigned int DT_INVALID_PARAM = 1 << 3; // An input parameter was i static const unsigned int DT_BUFFER_TOO_SMALL = 1 << 4; // Result buffer for the query was too small to store all results. static const unsigned int DT_OUT_OF_NODES = 1 << 5; // Query ran out of nodes during search. static const unsigned int DT_PARTIAL_RESULT = 1 << 6; // Query did not reach the end location, returning best guess. +static const unsigned int DT_ALREADY_OCCUPIED = 1 << 7; // A tile has already been assigned to the given x,y coordinate // Returns true of status is success. diff --git a/Detour/Source/DetourCommon.cpp b/Detour/Source/DetourCommon.cpp index 26fe65c1..b89d7512 100644 --- a/Detour/Source/DetourCommon.cpp +++ b/Detour/Source/DetourCommon.cpp @@ -203,33 +203,32 @@ void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const floa bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b, const float* c, float& h) { + const float EPS = 1e-6f; float v0[3], v1[3], v2[3]; - dtVsub(v0, c,a); - dtVsub(v1, b,a); - dtVsub(v2, p,a); - - const float dot00 = dtVdot2D(v0, v0); - const float dot01 = dtVdot2D(v0, v1); - const float dot02 = dtVdot2D(v0, v2); - const float dot11 = dtVdot2D(v1, v1); - const float dot12 = dtVdot2D(v1, v2); - - // Compute barycentric coordinates - const float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); - const float u = (dot11 * dot02 - dot01 * dot12) * invDenom; - const float v = (dot00 * dot12 - dot01 * dot02) * invDenom; - // The (sloppy) epsilon is needed to allow to get height of points which - // are interpolated along the edges of the triangles. - static const float EPS = 1e-4f; - + dtVsub(v0, c, a); + dtVsub(v1, b, a); + dtVsub(v2, p, a); + + // Compute scaled barycentric coordinates + float denom = v0[0] * v1[2] - v0[2] * v1[0]; + if (fabsf(denom) < EPS) + return false; + + float u = v1[2] * v2[0] - v1[0] * v2[2]; + float v = v0[0] * v2[2] - v0[2] * v2[0]; + + if (denom < 0) { + denom = -denom; + u = -u; + v = -v; + } + // If point lies inside the triangle, return interpolated ycoord. - if (u >= -EPS && v >= -EPS && (u+v) <= 1+EPS) - { - h = a[1] + v0[1]*u + v1[1]*v; + if (u >= 0.0f && v >= 0.0f && (u + v) <= denom) { + h = a[1] + (v0[1] * u + v1[1] * v) / denom; return true; } - return false; } @@ -342,8 +341,8 @@ void dtRandomPointInConvexPoly(const float* pts, const int npts, float* areas, // Find sub triangle weighted by area. const float thr = s*areasum; float acc = 0.0f; - float u = 0.0f; - int tri = 0; + float u = 1.0f; + int tri = npts - 1; for (int i = 2; i < npts; i++) { const float dacc = areas[i]; if (thr >= acc && thr < (acc+dacc)) diff --git a/Detour/Source/DetourNavMesh.cpp b/Detour/Source/DetourNavMesh.cpp index a6557f9e..2240526e 100644 --- a/Detour/Source/DetourNavMesh.cpp +++ b/Detour/Source/DetourNavMesh.cpp @@ -433,8 +433,8 @@ void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) float tmax = (neia[k*2+1]-va[2]) / (vb[2]-va[2]); if (tmin > tmax) dtSwap(tmin,tmax); - link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); - link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); + link->bmin = (unsigned char)roundf(dtClamp(tmin, 0.0f, 1.0f)*255.0f); + link->bmax = (unsigned char)roundf(dtClamp(tmax, 0.0f, 1.0f)*255.0f); } else if (dir == 2 || dir == 6) { @@ -442,8 +442,8 @@ void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) float tmax = (neia[k*2+1]-va[0]) / (vb[0]-va[0]); if (tmin > tmax) dtSwap(tmin,tmax); - link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); - link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); + link->bmin = (unsigned char)roundf(dtClamp(tmin, 0.0f, 1.0f)*255.0f); + link->bmax = (unsigned char)roundf(dtClamp(tmax, 0.0f, 1.0f)*255.0f); } } } @@ -470,12 +470,12 @@ void dtNavMesh::connectExtOffMeshLinks(dtMeshTile* tile, dtMeshTile* target, int if (targetPoly->firstLink == DT_NULL_LINK) continue; - const float ext[3] = { targetCon->rad, target->header->walkableClimb, targetCon->rad }; + const float halfExtents[3] = { targetCon->rad, target->header->walkableClimb, targetCon->rad }; // Find polygon to connect to. const float* p = &targetCon->pos[3]; float nearestPt[3]; - dtPolyRef ref = findNearestPolyInTile(tile, p, ext, nearestPt); + dtPolyRef ref = findNearestPolyInTile(tile, p, halfExtents, nearestPt); if (!ref) continue; // findNearestPoly may return too optimistic results, further check to make sure. @@ -570,12 +570,12 @@ void dtNavMesh::baseOffMeshLinks(dtMeshTile* tile) dtOffMeshConnection* con = &tile->offMeshCons[i]; dtPoly* poly = &tile->polys[con->poly]; - const float ext[3] = { con->rad, tile->header->walkableClimb, con->rad }; + const float halfExtents[3] = { con->rad, tile->header->walkableClimb, con->rad }; // Find polygon to connect to. const float* p = &con->pos[0]; // First vertex float nearestPt[3]; - dtPolyRef ref = findNearestPolyInTile(tile, p, ext, nearestPt); + dtPolyRef ref = findNearestPolyInTile(tile, p, halfExtents, nearestPt); if (!ref) continue; // findNearestPoly may return too optimistic results, further check to make sure. if (dtSqr(nearestPt[0]-p[0])+dtSqr(nearestPt[2]-p[2]) > dtSqr(con->rad)) @@ -616,63 +616,84 @@ void dtNavMesh::baseOffMeshLinks(dtMeshTile* tile) } } -void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const +namespace { - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - getTileAndPolyByRefUnsafe(ref, &tile, &poly); - - // Off-mesh connections don't have detail polygons. - if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) + template + void closestPointOnDetailEdges(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* closest) { - const float* v0 = &tile->verts[poly->verts[0]*3]; - const float* v1 = &tile->verts[poly->verts[1]*3]; - const float d0 = dtVdist(pos, v0); - const float d1 = dtVdist(pos, v1); - const float u = d0 / (d0+d1); - dtVlerp(closest, v0, v1, u); - if (posOverPoly) - *posOverPoly = false; - return; + const unsigned int ip = (unsigned int)(poly - tile->polys); + const dtPolyDetail* pd = &tile->detailMeshes[ip]; + + float dmin = FLT_MAX; + float tmin = 0; + const float* pmin = 0; + const float* pmax = 0; + + for (int i = 0; i < pd->triCount; i++) + { + const unsigned char* tris = &tile->detailTris[(pd->triBase + i) * 4]; + const int ANY_BOUNDARY_EDGE = + (DT_DETAIL_EDGE_BOUNDARY << 0) | + (DT_DETAIL_EDGE_BOUNDARY << 2) | + (DT_DETAIL_EDGE_BOUNDARY << 4); + if (onlyBoundary && (tris[3] & ANY_BOUNDARY_EDGE) == 0) + continue; + + const float* v[3]; + for (int j = 0; j < 3; ++j) + { + if (tris[j] < poly->vertCount) + v[j] = &tile->verts[poly->verts[tris[j]] * 3]; + else + v[j] = &tile->detailVerts[(pd->vertBase + (tris[j] - poly->vertCount)) * 3]; + } + + for (int k = 0, j = 2; k < 3; j = k++) + { + if ((dtGetDetailTriEdgeFlags(tris[3], j) & DT_DETAIL_EDGE_BOUNDARY) == 0 && + (onlyBoundary || tris[j] < tris[k])) + { + // Only looking at boundary edges and this is internal, or + // this is an inner edge that we will see again or have already seen. + continue; + } + + float t; + float d = dtDistancePtSegSqr2D(pos, v[j], v[k], t); + if (d < dmin) + { + dmin = d; + tmin = t; + pmin = v[j]; + pmax = v[k]; + } + } + } + + dtVlerp(closest, pmin, pmax, tmin); } - +} + +bool dtNavMesh::getPolyHeight(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* height) const +{ + // Off-mesh connections do not have detail polys and getting height + // over them does not make sense. + if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) + return false; + const unsigned int ip = (unsigned int)(poly - tile->polys); const dtPolyDetail* pd = &tile->detailMeshes[ip]; - // Clamp point to be inside the polygon. float verts[DT_VERTS_PER_POLYGON*3]; - float edged[DT_VERTS_PER_POLYGON]; - float edget[DT_VERTS_PER_POLYGON]; const int nv = poly->vertCount; for (int i = 0; i < nv; ++i) dtVcopy(&verts[i*3], &tile->verts[poly->verts[i]*3]); - dtVcopy(closest, pos); - if (!dtDistancePtPolyEdgesSqr(pos, verts, nv, edged, edget)) - { - // Point is outside the polygon, dtClamp to nearest edge. - float dmin = edged[0]; - int imin = 0; - for (int i = 1; i < nv; ++i) - { - if (edged[i] < dmin) - { - dmin = edged[i]; - imin = i; - } - } - const float* va = &verts[imin*3]; - const float* vb = &verts[((imin+1)%nv)*3]; - dtVlerp(closest, va, vb, edget[imin]); - - if (posOverPoly) - *posOverPoly = false; - } - else - { - if (posOverPoly) - *posOverPoly = true; - } + if (!dtPointInPolygon(pos, verts, nv)) + return false; + + if (!height) + return true; // Find height at the location. for (int j = 0; j < pd->triCount; ++j) @@ -687,21 +708,62 @@ void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* close v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3]; } float h; - if (dtClosestHeightPointTriangle(closest, v[0], v[1], v[2], h)) + if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h)) { - closest[1] = h; - break; + *height = h; + return true; } } + + // If all triangle checks failed above (can happen with degenerate triangles + // or larger floating point values) the point is on an edge, so just select + // closest. This should almost never happen so the extra iteration here is + // ok. + float closest[3]; + closestPointOnDetailEdges(tile, poly, pos, closest); + *height = closest[1]; + return true; +} + +void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const +{ + const dtMeshTile* tile = 0; + const dtPoly* poly = 0; + getTileAndPolyByRefUnsafe(ref, &tile, &poly); + + dtVcopy(closest, pos); + if (getPolyHeight(tile, poly, pos, &closest[1])) + { + if (posOverPoly) + *posOverPoly = true; + return; + } + + if (posOverPoly) + *posOverPoly = false; + + // Off-mesh connections don't have detail polygons. + if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) + { + const float* v0 = &tile->verts[poly->verts[0]*3]; + const float* v1 = &tile->verts[poly->verts[1]*3]; + float t; + dtDistancePtSegSqr2D(pos, v0, v1, t); + dtVlerp(closest, v0, v1, t); + return; + } + + // Outside poly that is not an offmesh connection. + closestPointOnDetailEdges(tile, poly, pos, closest); } dtPolyRef dtNavMesh::findNearestPolyInTile(const dtMeshTile* tile, - const float* center, const float* extents, + const float* center, const float* halfExtents, float* nearestPt) const { float bmin[3], bmax[3]; - dtVsub(bmin, center, extents); - dtVadd(bmax, center, extents); + dtVsub(bmin, center, halfExtents); + dtVadd(bmax, center, halfExtents); // Get nearby polygons from proximity grid. dtPolyRef polys[128]; @@ -852,10 +914,17 @@ dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags, return DT_FAILURE | DT_WRONG_MAGIC; if (header->version != DT_NAVMESH_VERSION) return DT_FAILURE | DT_WRONG_VERSION; + +#ifndef DT_POLYREF64 + // Do not allow adding more polygons than specified in the NavMesh's maxPolys constraint. + // Otherwise, the poly ID cannot be represented with the given number of bits. + if (m_polyBits < dtIlog2(dtNextPow2((unsigned int)header->polyCount))) + return DT_FAILURE | DT_INVALID_PARAM; +#endif // Make sure the location is free. if (getTileAt(header->x, header->y, header->layer)) - return DT_FAILURE; + return DT_FAILURE | DT_ALREADY_OCCUPIED; // Allocate a tile. dtMeshTile* tile = 0; diff --git a/Detour/Source/DetourNavMeshQuery.cpp b/Detour/Source/DetourNavMeshQuery.cpp index 10bd904e..b78807fa 100644 --- a/Detour/Source/DetourNavMeshQuery.cpp +++ b/Detour/Source/DetourNavMeshQuery.cpp @@ -117,6 +117,11 @@ void dtFreeNavMeshQuery(dtNavMeshQuery* navmesh) dtFree(navmesh); } +dtPolyQuery::~dtPolyQuery() +{ + // Defined out of line to fix the weak v-tables warning +} + ////////////////////////////////////////////////////////////////////////////////////////// /// @class dtNavMeshQuery @@ -222,7 +227,10 @@ dtStatus dtNavMeshQuery::findRandomPoint(const dtQueryFilter* filter, float (*fr dtPolyRef* randomRef, float* randomPt) const { dtAssert(m_nav); - + + if (!filter || !frand || !randomRef || !randomPt) + return DT_FAILURE | DT_INVALID_PARAM; + // Randomly pick one tile. Assume that all tiles cover roughly the same area. const dtMeshTile* tile = 0; float tsum = 0.0f; @@ -298,11 +306,7 @@ dtStatus dtNavMeshQuery::findRandomPoint(const dtQueryFilter* filter, float (*fr float pt[3]; dtRandomPointInConvexPoly(verts, poly->vertCount, areas, s, t, pt); - float h = 0.0f; - dtStatus status = getPolyHeight(polyRef, pt, &h); - if (dtStatusFailed(status)) - return status; - pt[1] = h; + closestPointOnPoly(polyRef, pt, pt, NULL); dtVcopy(randomPt, pt); *randomRef = polyRef; @@ -319,8 +323,13 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f dtAssert(m_openList); // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) + if (!m_nav->isValidPolyRef(startRef) || + !centerPos || !dtVisfinite(centerPos) || + maxRadius < 0 || !dtMathIsfinite(maxRadius) || + !filter || !frand || !randomRef || !randomPt) + { return DT_FAILURE | DT_INVALID_PARAM; + } const dtMeshTile* startTile = 0; const dtPoly* startPoly = 0; @@ -480,16 +489,12 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f float pt[3]; dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); - float h = 0.0f; - dtStatus stat = getPolyHeight(randomPolyRef, pt, &h); - if (dtStatusFailed(status)) - return stat; - pt[1] = h; + closestPointOnPoly(randomPolyRef, pt, pt, NULL); dtVcopy(randomPt, pt); *randomRef = randomPolyRef; - return DT_SUCCESS; + return status; } @@ -506,85 +511,14 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f dtStatus dtNavMeshQuery::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const { dtAssert(m_nav); - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) + if (!m_nav->isValidPolyRef(ref) || + !pos || !dtVisfinite(pos) || + !closest) + { return DT_FAILURE | DT_INVALID_PARAM; - if (!tile) - return DT_FAILURE | DT_INVALID_PARAM; - - // Off-mesh connections don't have detail polygons. - if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - { - const float* v0 = &tile->verts[poly->verts[0]*3]; - const float* v1 = &tile->verts[poly->verts[1]*3]; - const float d0 = dtVdist(pos, v0); - const float d1 = dtVdist(pos, v1); - const float u = d0 / (d0+d1); - dtVlerp(closest, v0, v1, u); - if (posOverPoly) - *posOverPoly = false; - return DT_SUCCESS; } - const unsigned int ip = (unsigned int)(poly - tile->polys); - const dtPolyDetail* pd = &tile->detailMeshes[ip]; - - // Clamp point to be inside the polygon. - float verts[DT_VERTS_PER_POLYGON*3]; - float edged[DT_VERTS_PER_POLYGON]; - float edget[DT_VERTS_PER_POLYGON]; - const int nv = poly->vertCount; - for (int i = 0; i < nv; ++i) - dtVcopy(&verts[i*3], &tile->verts[poly->verts[i]*3]); - - dtVcopy(closest, pos); - if (!dtDistancePtPolyEdgesSqr(pos, verts, nv, edged, edget)) - { - // Point is outside the polygon, dtClamp to nearest edge. - float dmin = edged[0]; - int imin = 0; - for (int i = 1; i < nv; ++i) - { - if (edged[i] < dmin) - { - dmin = edged[i]; - imin = i; - } - } - const float* va = &verts[imin*3]; - const float* vb = &verts[((imin+1)%nv)*3]; - dtVlerp(closest, va, vb, edget[imin]); - - if (posOverPoly) - *posOverPoly = false; - } - else - { - if (posOverPoly) - *posOverPoly = true; - } - - // Find height at the location. - for (int j = 0; j < pd->triCount; ++j) - { - const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; - const float* v[3]; - for (int k = 0; k < 3; ++k) - { - if (t[k] < poly->vertCount) - v[k] = &tile->verts[poly->verts[t[k]]*3]; - else - v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3]; - } - float h; - if (dtClosestHeightPointTriangle(closest, v[0], v[1], v[2], h)) - { - closest[1] = h; - break; - } - } - + m_nav->closestPointOnPoly(ref, pos, closest, posOverPoly); return DT_SUCCESS; } @@ -607,6 +541,9 @@ dtStatus dtNavMeshQuery::closestPointOnPolyBoundary(dtPolyRef ref, const float* const dtPoly* poly = 0; if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) return DT_FAILURE | DT_INVALID_PARAM; + + if (!pos || !dtVisfinite(pos) || !closest) + return DT_FAILURE | DT_INVALID_PARAM; // Collect vertices. float verts[DT_VERTS_PER_POLYGON*3]; @@ -648,7 +585,7 @@ dtStatus dtNavMeshQuery::closestPointOnPolyBoundary(dtPolyRef ref, const float* /// @par /// -/// Will return #DT_FAILURE if the provided position is outside the xz-bounds +/// Will return #DT_FAILURE | DT_INVALID_PARAM if the provided position is outside the xz-bounds /// of the polygon. /// dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* height) const @@ -659,44 +596,28 @@ dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* h const dtPoly* poly = 0; if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) return DT_FAILURE | DT_INVALID_PARAM; - + + if (!pos || !dtVisfinite2D(pos)) + return DT_FAILURE | DT_INVALID_PARAM; + + // We used to return success for offmesh connections, but the + // getPolyHeight in DetourNavMesh does not do this, so special + // case it here. if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) { const float* v0 = &tile->verts[poly->verts[0]*3]; const float* v1 = &tile->verts[poly->verts[1]*3]; - const float d0 = dtVdist2D(pos, v0); - const float d1 = dtVdist2D(pos, v1); - const float u = d0 / (d0+d1); + float t; + dtDistancePtSegSqr2D(pos, v0, v1, t); if (height) - *height = v0[1] + (v1[1] - v0[1]) * u; + *height = v0[1] + (v1[1] - v0[1])*t; + return DT_SUCCESS; } - else - { - const unsigned int ip = (unsigned int)(poly - tile->polys); - const dtPolyDetail* pd = &tile->detailMeshes[ip]; - for (int j = 0; j < pd->triCount; ++j) - { - const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; - const float* v[3]; - for (int k = 0; k < 3; ++k) - { - if (t[k] < poly->vertCount) - v[k] = &tile->verts[poly->verts[t[k]]*3]; - else - v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3]; - } - float h; - if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h)) - { - if (height) - *height = h; - return DT_SUCCESS; - } - } - } - - return DT_FAILURE | DT_INVALID_PARAM; + + return m_nav->getPolyHeight(tile, poly, pos, height) + ? DT_SUCCESS + : DT_FAILURE | DT_INVALID_PARAM; } class dtFindNearestPolyQuery : public dtPolyQuery @@ -706,15 +627,19 @@ class dtFindNearestPolyQuery : public dtPolyQuery float m_nearestDistanceSqr; dtPolyRef m_nearestRef; float m_nearestPoint[3]; + bool m_overPoly; public: dtFindNearestPolyQuery(const dtNavMeshQuery* query, const float* center) - : m_query(query), m_center(center), m_nearestDistanceSqr(FLT_MAX), m_nearestRef(0), m_nearestPoint() + : m_query(query), m_center(center), m_nearestDistanceSqr(FLT_MAX), m_nearestRef(0), m_nearestPoint(), m_overPoly(false) { } + virtual ~dtFindNearestPolyQuery(); + dtPolyRef nearestRef() const { return m_nearestRef; } const float* nearestPoint() const { return m_nearestPoint; } + bool isOverPoly() const { return m_overPoly; } void process(const dtMeshTile* tile, dtPoly** polys, dtPolyRef* refs, int count) { @@ -748,29 +673,46 @@ public: m_nearestDistanceSqr = d; m_nearestRef = ref; + m_overPoly = posOverPoly; } } } }; +dtFindNearestPolyQuery::~dtFindNearestPolyQuery() +{ + // Defined out of line to fix the weak v-tables warning +} + /// @par /// /// @note If the search box does not intersect any polygons the search will /// return #DT_SUCCESS, but @p nearestRef will be zero. So if in doubt, check /// @p nearestRef before using @p nearestPt. /// -dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* extents, +dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* halfExtents, const dtQueryFilter* filter, dtPolyRef* nearestRef, float* nearestPt) const +{ + return findNearestPoly(center, halfExtents, filter, nearestRef, nearestPt, NULL); +} + +// If center and nearestPt point to an equal position, isOverPoly will be true; +// however there's also a special case of climb height inside the polygon (see dtFindNearestPolyQuery) +dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* halfExtents, + const dtQueryFilter* filter, + dtPolyRef* nearestRef, float* nearestPt, bool* isOverPoly) const { dtAssert(m_nav); if (!nearestRef) return DT_FAILURE | DT_INVALID_PARAM; + + // queryPolygons below will check rest of params dtFindNearestPolyQuery query(this, center); - dtStatus status = queryPolygons(center, extents, filter, &query); + dtStatus status = queryPolygons(center, halfExtents, filter, &query); if (dtStatusFailed(status)) return status; @@ -778,7 +720,11 @@ dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* exten // Only override nearestPt if we actually found a poly so the nearest point // is valid. if (nearestPt && *nearestRef) + { dtVcopy(nearestPt, query.nearestPoint()); + if (isOverPoly) + *isOverPoly = query.isOverPoly(); + } return DT_SUCCESS; } @@ -913,6 +859,8 @@ public: { } + virtual ~dtCollectPolysQuery(); + int numCollected() const { return m_numCollected; } bool overflowed() const { return m_overflow; } @@ -934,6 +882,11 @@ public: } }; +dtCollectPolysQuery::~dtCollectPolysQuery() +{ + // Defined out of line to fix the weak v-tables warning +} + /// @par /// /// If no polygons are found, the function will return #DT_SUCCESS with a @@ -943,7 +896,7 @@ public: /// be filled to capacity. The method of choosing which polygons from the /// full set are included in the partial result set is undefined. /// -dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* extents, +dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* halfExtents, const dtQueryFilter* filter, dtPolyRef* polys, int* polyCount, const int maxPolys) const { @@ -952,7 +905,7 @@ dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* extents dtCollectPolysQuery collector(polys, maxPolys); - dtStatus status = queryPolygons(center, extents, filter, &collector); + dtStatus status = queryPolygons(center, halfExtents, filter, &collector); if (dtStatusFailed(status)) return status; @@ -963,21 +916,25 @@ dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* extents /// @par /// /// The query will be invoked with batches of polygons. Polygons passed -/// to the query have bounding boxes that overlap with the center and extents +/// to the query have bounding boxes that overlap with the center and halfExtents /// passed to this function. The dtPolyQuery::process function is invoked multiple /// times until all overlapping polygons have been processed. /// -dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* extents, +dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* halfExtents, const dtQueryFilter* filter, dtPolyQuery* query) const { dtAssert(m_nav); - if (!center || !extents || !filter || !query) + if (!center || !dtVisfinite(center) || + !halfExtents || !dtVisfinite(halfExtents) || + !filter || !query) + { return DT_FAILURE | DT_INVALID_PARAM; + } float bmin[3], bmax[3]; - dtVsub(bmin, center, extents); - dtVadd(bmax, center, extents); + dtVsub(bmin, center, halfExtents); + dtVadd(bmax, center, halfExtents); // Find tiles the query touches. int minx, miny, maxx, maxy; @@ -1021,14 +978,20 @@ dtStatus dtNavMeshQuery::findPath(dtPolyRef startRef, dtPolyRef endRef, dtAssert(m_nav); dtAssert(m_nodePool); dtAssert(m_openList); - - if (pathCount) - *pathCount = 0; + + if (!pathCount) + return DT_FAILURE | DT_INVALID_PARAM; + + *pathCount = 0; // Validate input if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef) || - !startPos || !endPos || !filter || maxPath <= 0 || !path || !pathCount) + !startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || + !filter || !path || maxPath <= 0) + { return DT_FAILURE | DT_INVALID_PARAM; + } if (startRef == endRef) { @@ -1263,18 +1226,21 @@ dtStatus dtNavMeshQuery::initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef m_query.status = DT_FAILURE; m_query.startRef = startRef; m_query.endRef = endRef; - dtVcopy(m_query.startPos, startPos); - dtVcopy(m_query.endPos, endPos); + if (startPos) + dtVcopy(m_query.startPos, startPos); + if (endPos) + dtVcopy(m_query.endPos, endPos); m_query.filter = filter; m_query.options = options; m_query.raycastLimitSqr = FLT_MAX; - if (!startRef || !endRef) - return DT_FAILURE | DT_INVALID_PARAM; - // Validate input - if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef)) + if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef) || + !startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || !filter) + { return DT_FAILURE | DT_INVALID_PARAM; + } // trade quality with performance? if (options & DT_FINDPATH_ANY_ANGLE) @@ -1530,7 +1496,13 @@ dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters) dtStatus dtNavMeshQuery::finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, const int maxPath) { + if (!pathCount) + return DT_FAILURE | DT_INVALID_PARAM; + *pathCount = 0; + + if (!path || maxPath <= 0) + return DT_FAILURE | DT_INVALID_PARAM; if (dtStatusFailed(m_query.status)) { @@ -1615,12 +1587,13 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, dtStatus dtNavMeshQuery::finalizeSlicedFindPathPartial(const dtPolyRef* existing, const int existingSize, dtPolyRef* path, int* pathCount, const int maxPath) { + if (!pathCount) + return DT_FAILURE | DT_INVALID_PARAM; + *pathCount = 0; - - if (existingSize == 0) - { - return DT_FAILURE; - } + + if (!existing || existingSize <= 0 || !path || !pathCount || maxPath <= 0) + return DT_FAILURE | DT_INVALID_PARAM; if (dtStatusFailed(m_query.status)) { @@ -1823,14 +1796,19 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en int* straightPathCount, const int maxStraightPath, const int options) const { dtAssert(m_nav); - + + if (!straightPathCount) + return DT_FAILURE | DT_INVALID_PARAM; + *straightPathCount = 0; - - if (!maxStraightPath) - return DT_FAILURE | DT_INVALID_PARAM; - - if (!path[0]) + + if (!startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || + !path || pathSize <= 0 || !path[0] || + maxStraightPath <= 0) + { return DT_FAILURE | DT_INVALID_PARAM; + } dtStatus stat = 0; @@ -2070,13 +2048,19 @@ dtStatus dtNavMeshQuery::moveAlongSurface(dtPolyRef startRef, const float* start dtAssert(m_nav); dtAssert(m_tinyNodePool); + if (!visitedCount) + return DT_FAILURE | DT_INVALID_PARAM; + *visitedCount = 0; - - // Validate input - if (!startRef) - return DT_FAILURE | DT_INVALID_PARAM; - if (!m_nav->isValidPolyRef(startRef)) + + if (!m_nav->isValidPolyRef(startRef) || + !startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || + !filter || !resultPos || !visited || + maxVisitedSize <= 0) + { return DT_FAILURE | DT_INVALID_PARAM; + } dtStatus status = DT_SUCCESS; @@ -2484,16 +2468,23 @@ dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, cons dtRaycastHit* hit, dtPolyRef prevRef) const { dtAssert(m_nav); - + + if (!hit) + return DT_FAILURE | DT_INVALID_PARAM; + hit->t = 0; hit->pathCount = 0; hit->pathCost = 0; // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) - return DT_FAILURE | DT_INVALID_PARAM; - if (prevRef && !m_nav->isValidPolyRef(prevRef)) + if (!m_nav->isValidPolyRef(startRef) || + !startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || + !filter || + (prevRef && !m_nav->isValidPolyRef(prevRef))) + { return DT_FAILURE | DT_INVALID_PARAM; + } float dir[3], curPos[3], lastPos[3]; float verts[DT_VERTS_PER_POLYGON*3+3]; @@ -2735,11 +2726,18 @@ dtStatus dtNavMeshQuery::findPolysAroundCircle(dtPolyRef startRef, const float* dtAssert(m_nodePool); dtAssert(m_openList); - *resultCount = 0; - - // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) + if (!resultCount) return DT_FAILURE | DT_INVALID_PARAM; + + *resultCount = 0; + + if (!m_nav->isValidPolyRef(startRef) || + !centerPos || !dtVisfinite(centerPos) || + radius < 0 || !dtMathIsfinite(radius) || + !filter || maxResult < 0) + { + return DT_FAILURE | DT_INVALID_PARAM; + } m_nodePool->clear(); m_openList->clear(); @@ -2901,8 +2899,18 @@ dtStatus dtNavMeshQuery::findPolysAroundShape(dtPolyRef startRef, const float* v dtAssert(m_nav); dtAssert(m_nodePool); dtAssert(m_openList); - + + if (!resultCount) + return DT_FAILURE | DT_INVALID_PARAM; + *resultCount = 0; + + if (!m_nav->isValidPolyRef(startRef) || + !verts || nverts < 3 || + !filter || maxResult < 0) + { + return DT_FAILURE | DT_INVALID_PARAM; + } // Validate input if (!startRef || !m_nav->isValidPolyRef(startRef)) @@ -3088,13 +3096,20 @@ dtStatus dtNavMeshQuery::findLocalNeighbourhood(dtPolyRef startRef, const float* { dtAssert(m_nav); dtAssert(m_tinyNodePool); - + + if (!resultCount) + return DT_FAILURE | DT_INVALID_PARAM; + *resultCount = 0; - // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) + if (!m_nav->isValidPolyRef(startRef) || + !centerPos || !dtVisfinite(centerPos) || + radius < 0 || !dtMathIsfinite(radius) || + !filter || maxResult < 0) + { return DT_FAILURE | DT_INVALID_PARAM; - + } + static const int MAX_STACK = 48; dtNode* stack[MAX_STACK]; int nstack = 0; @@ -3301,13 +3316,19 @@ dtStatus dtNavMeshQuery::getPolyWallSegments(dtPolyRef ref, const dtQueryFilter* const int maxSegments) const { dtAssert(m_nav); + + if (!segmentCount) + return DT_FAILURE | DT_INVALID_PARAM; *segmentCount = 0; - + const dtMeshTile* tile = 0; const dtPoly* poly = 0; if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) return DT_FAILURE | DT_INVALID_PARAM; + + if (!filter || !segmentVerts || maxSegments < 0) + return DT_FAILURE | DT_INVALID_PARAM; int n = 0; static const int MAX_INTERVAL = 16; @@ -3455,8 +3476,13 @@ dtStatus dtNavMeshQuery::findDistanceToWall(dtPolyRef startRef, const float* cen dtAssert(m_openList); // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) + if (!m_nav->isValidPolyRef(startRef) || + !centerPos || !dtVisfinite(centerPos) || + maxRadius < 0 || !dtMathIsfinite(maxRadius) || + !filter || !hitDist || !hitPos || !hitNormal) + { return DT_FAILURE | DT_INVALID_PARAM; + } m_nodePool->clear(); m_openList->clear(); diff --git a/DetourCrowd/CMakeLists.txt b/DetourCrowd/CMakeLists.txt new file mode 100644 index 00000000..6ecd0dde --- /dev/null +++ b/DetourCrowd/CMakeLists.txt @@ -0,0 +1,37 @@ +file(GLOB SOURCES Source/*.cpp) +add_library(DetourCrowd ${SOURCES}) + +add_library(RecastNavigation::DetourCrowd ALIAS DetourCrowd) +set_target_properties(DetourCrowd PROPERTIES DEBUG_POSTFIX -d) + +set(DetourCrowd_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") + +target_include_directories(DetourCrowd PUBLIC + "$" +) + +target_link_libraries(DetourCrowd + Detour +) + +set_target_properties(DetourCrowd PROPERTIES + SOVERSION ${SOVERSION} + VERSION ${LIB_VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "DetourCrowd-d" + ) + +install(TARGETS DetourCrowd + EXPORT recastnavigation-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation + ) + +file(GLOB INCLUDES Include/*.h) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +if(MSVC) + install(FILES "$/DetourCrowd-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") +endif() diff --git a/DetourCrowd/Include/DetourCrowd.h b/DetourCrowd/Include/DetourCrowd.h index 045fc216..854546fc 100644 --- a/DetourCrowd/Include/DetourCrowd.h +++ b/DetourCrowd/Include/DetourCrowd.h @@ -66,7 +66,7 @@ enum CrowdAgentState { DT_CROWDAGENT_STATE_INVALID, ///< The agent is not in a valid state. DT_CROWDAGENT_STATE_WALKING, ///< The agent is traversing a normal navigation mesh polygon. - DT_CROWDAGENT_STATE_OFFMESH, ///< The agent is traversing an off-mesh connection. + DT_CROWDAGENT_STATE_OFFMESH ///< The agent is traversing an off-mesh connection. }; /// Configuration parameters for a crowd agent. @@ -108,7 +108,7 @@ enum MoveRequestState DT_CROWDAGENT_TARGET_REQUESTING, DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE, DT_CROWDAGENT_TARGET_WAITING_FOR_PATH, - DT_CROWDAGENT_TARGET_VELOCITY, + DT_CROWDAGENT_TARGET_VELOCITY }; /// Represents an agent managed by a #dtCrowd object. @@ -188,7 +188,7 @@ enum UpdateFlags DT_CROWD_OBSTACLE_AVOIDANCE = 2, DT_CROWD_SEPARATION = 4, DT_CROWD_OPTIMIZE_VIS = 8, ///< Use #dtPathCorridor::optimizePathVisibility() to optimize the agent path. - DT_CROWD_OPTIMIZE_TOPO = 16, ///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path. + DT_CROWD_OPTIMIZE_TOPO = 16 ///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path. }; struct dtCrowdAgentDebugInfo @@ -217,7 +217,7 @@ class dtCrowd dtPolyRef* m_pathResult; int m_maxPathResult; - float m_ext[3]; + float m_agentPlacementHalfExtents[3]; dtQueryFilter m_filters[DT_CROWD_MAX_QUERY_FILTER_TYPE]; @@ -325,9 +325,13 @@ public: /// @return The filter used by the crowd. inline dtQueryFilter* getEditableFilter(const int i) { return (i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE) ? &m_filters[i] : 0; } - /// Gets the search extents [(x, y, z)] used by the crowd for query operations. - /// @return The search extents used by the crowd. [(x, y, z)] - const float* getQueryExtents() const { return m_ext; } + /// Gets the search halfExtents [(x, y, z)] used by the crowd for query operations. + /// @return The search halfExtents used by the crowd. [(x, y, z)] + const float* getQueryHalfExtents() const { return m_agentPlacementHalfExtents; } + + /// Same as getQueryHalfExtents. Left to maintain backwards compatibility. + /// @return The search halfExtents used by the crowd. [(x, y, z)] + const float* getQueryExtents() const { return m_agentPlacementHalfExtents; } /// Gets the velocity sample count. /// @return The velocity sample count. diff --git a/DetourCrowd/Source/DetourCrowd.cpp b/DetourCrowd/Source/DetourCrowd.cpp index f9f43607..d7cdae0b 100644 --- a/DetourCrowd/Source/DetourCrowd.cpp +++ b/DetourCrowd/Source/DetourCrowd.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -386,7 +385,8 @@ bool dtCrowd::init(const int maxAgents, const float maxAgentRadius, dtNavMesh* n m_maxAgents = maxAgents; m_maxAgentRadius = maxAgentRadius; - dtVset(m_ext, m_maxAgentRadius*2.0f,m_maxAgentRadius*1.5f,m_maxAgentRadius*2.0f); + // Larger than agent radius because it is also used for agent recovery. + dtVset(m_agentPlacementHalfExtents, m_maxAgentRadius*2.0f, m_maxAgentRadius*1.5f, m_maxAgentRadius*2.0f); m_grid = dtAllocProximityGrid(); if (!m_grid) @@ -531,7 +531,7 @@ int dtCrowd::addAgent(const float* pos, const dtCrowdAgentParams* params) float nearest[3]; dtPolyRef ref = 0; dtVcopy(nearest, pos); - dtStatus status = m_navquery->findNearestPoly(pos, m_ext, &m_filters[ag->params.queryFilterType], &ref, nearest); + dtStatus status = m_navquery->findNearestPoly(pos, m_agentPlacementHalfExtents, &m_filters[ag->params.queryFilterType], &ref, nearest); if (dtStatusFailed(status)) { dtVcopy(nearest, pos); @@ -965,7 +965,7 @@ void dtCrowd::checkPathValidity(dtCrowdAgent** agents, const int nagents, const float nearest[3]; dtVcopy(nearest, agentPos); agentRef = 0; - m_navquery->findNearestPoly(ag->npos, m_ext, &m_filters[ag->params.queryFilterType], &agentRef, nearest); + m_navquery->findNearestPoly(ag->npos, m_agentPlacementHalfExtents, &m_filters[ag->params.queryFilterType], &agentRef, nearest); dtVcopy(agentPos, nearest); if (!agentRef) @@ -1001,7 +1001,7 @@ void dtCrowd::checkPathValidity(dtCrowdAgent** agents, const int nagents, const float nearest[3]; dtVcopy(nearest, ag->targetPos); ag->targetRef = 0; - m_navquery->findNearestPoly(ag->targetPos, m_ext, &m_filters[ag->params.queryFilterType], &ag->targetRef, nearest); + m_navquery->findNearestPoly(ag->targetPos, m_agentPlacementHalfExtents, &m_filters[ag->params.queryFilterType], &ag->targetRef, nearest); dtVcopy(ag->targetPos, nearest); replan = true; } @@ -1408,12 +1408,14 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) } // Update agents using off-mesh connection. - for (int i = 0; i < m_maxAgents; ++i) + for (int i = 0; i < nagents; ++i) { - dtCrowdAgentAnimation* anim = &m_agentAnims[i]; + dtCrowdAgent* ag = agents[i]; + const int idx = (int)(ag - m_agents); + dtCrowdAgentAnimation* anim = &m_agentAnims[idx]; if (!anim->active) continue; - dtCrowdAgent* ag = agents[i]; + anim->t += dt; if (anim->t > anim->tmax) diff --git a/DetourCrowd/Source/DetourPathCorridor.cpp b/DetourCrowd/Source/DetourPathCorridor.cpp index 54a2ab8b..e54d4637 100644 --- a/DetourCrowd/Source/DetourPathCorridor.cpp +++ b/DetourCrowd/Source/DetourPathCorridor.cpp @@ -431,7 +431,7 @@ Behavior: - The new position will be located in the adjusted corridor's first polygon. The expected use case is that the desired position will be 'near' the current corridor. What is considered 'near' -depends on local polygon density, query search extents, etc. +depends on local polygon density, query search half extents, etc. The resulting position will differ from the desired position if the desired position is not on the navigation mesh, or it can't be reached using a local search. @@ -470,7 +470,7 @@ Behavior: - The corridor is automatically adjusted (shorted or lengthened) in order to remain valid. - The new target will be located in the adjusted corridor's last polygon. -The expected use case is that the desired target will be 'near' the current corridor. What is considered 'near' depends on local polygon density, query search extents, etc. +The expected use case is that the desired target will be 'near' the current corridor. What is considered 'near' depends on local polygon density, query search half extents, etc. The resulting target will differ from the desired target if the desired target is not on the navigation mesh, or it can't be reached using a local search. */ diff --git a/DetourTileCache/CMakeLists.txt b/DetourTileCache/CMakeLists.txt new file mode 100644 index 00000000..fcd23d5b --- /dev/null +++ b/DetourTileCache/CMakeLists.txt @@ -0,0 +1,38 @@ +file(GLOB SOURCES Source/*.cpp) +add_library(DetourTileCache ${SOURCES}) + +add_library(RecastNavigation::DetourTileCache ALIAS DetourTileCache) +set_target_properties(DetourTileCache PROPERTIES DEBUG_POSTFIX -d) + +set(DetourTileCache_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") + +target_include_directories(DetourTileCache PUBLIC + "$" +) + +target_link_libraries(DetourTileCache + Detour +) + +set_target_properties(DetourTileCache PROPERTIES + SOVERSION ${SOVERSION} + VERSION ${LIB_VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "DetourTileCache-d" + ) + + +install(TARGETS DetourTileCache + EXPORT recastnavigation-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation + ) + +file(GLOB INCLUDES Include/*.h) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +if(MSVC) + install(FILES "$/DetourTileCache-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") +endif() diff --git a/DetourTileCache/Include/DetourTileCache.h b/DetourTileCache/Include/DetourTileCache.h index c45910cd..0d346f17 100644 --- a/DetourTileCache/Include/DetourTileCache.h +++ b/DetourTileCache/Include/DetourTileCache.h @@ -3,16 +3,13 @@ #include "DetourStatus.h" - - typedef unsigned int dtObstacleRef; - typedef unsigned int dtCompressedTileRef; /// Flags for addTile enum dtCompressedTileFlags { - DT_COMPRESSEDTILE_FREE_DATA = 0x01, ///< Navmesh owns the tile memory and should free it. + DT_COMPRESSEDTILE_FREE_DATA = 0x01 ///< Navmesh owns the tile memory and should free it. }; struct dtCompressedTile @@ -32,13 +29,14 @@ enum ObstacleState DT_OBSTACLE_EMPTY, DT_OBSTACLE_PROCESSING, DT_OBSTACLE_PROCESSED, - DT_OBSTACLE_REMOVING, + DT_OBSTACLE_REMOVING }; enum ObstacleType { DT_OBSTACLE_CYLINDER, - DT_OBSTACLE_BOX, + DT_OBSTACLE_BOX, // AABB + DT_OBSTACLE_ORIENTED_BOX // OBB }; struct dtObstacleCylinder @@ -54,6 +52,13 @@ struct dtObstacleBox float bmax[ 3 ]; }; +struct dtObstacleOrientedBox +{ + float center[ 3 ]; + float halfExtents[ 3 ]; + float rotAux[ 2 ]; //{ cos(0.5f*angle)*sin(-0.5f*angle); cos(0.5f*angle)*cos(0.5f*angle) - 0.5 } +}; + static const int DT_MAX_TOUCHED_TILES = 8; struct dtTileCacheObstacle { @@ -61,6 +66,7 @@ struct dtTileCacheObstacle { dtObstacleCylinder cylinder; dtObstacleBox box; + dtObstacleOrientedBox orientedBox; }; dtCompressedTileRef touched[DT_MAX_TOUCHED_TILES]; @@ -88,13 +94,10 @@ struct dtTileCacheParams struct dtTileCacheMeshProcess { - virtual ~dtTileCacheMeshProcess() { } - - virtual void process(struct dtNavMeshCreateParams* params, - unsigned char* polyAreas, unsigned short* polyFlags) = 0; + virtual ~dtTileCacheMeshProcess(); + virtual void process(struct dtNavMeshCreateParams* params, unsigned char* polyAreas, unsigned short* polyFlags) = 0; }; - class dtTileCache { public: @@ -130,8 +133,14 @@ public: dtStatus removeTile(dtCompressedTileRef ref, unsigned char** data, int* dataSize); + // Cylinder obstacle. dtStatus addObstacle(const float* pos, const float radius, const float height, dtObstacleRef* result); + + // Aabb obstacle. dtStatus addBoxObstacle(const float* bmin, const float* bmax, dtObstacleRef* result); + + // Box obstacle: can be rotated in Y. + dtStatus addBoxObstacle(const float* center, const float* halfExtents, const float yRadians, dtObstacleRef* result); dtStatus removeObstacle(const dtObstacleRef ref); @@ -204,7 +213,7 @@ private: enum ObstacleRequestAction { REQUEST_ADD, - REQUEST_REMOVE, + REQUEST_REMOVE }; struct ObstacleRequest diff --git a/DetourTileCache/Include/DetourTileCacheBuilder.h b/DetourTileCache/Include/DetourTileCacheBuilder.h index d1ef80e1..9a6844c6 100644 --- a/DetourTileCache/Include/DetourTileCacheBuilder.h +++ b/DetourTileCache/Include/DetourTileCacheBuilder.h @@ -78,7 +78,7 @@ struct dtTileCachePolyMesh struct dtTileCacheAlloc { - virtual ~dtTileCacheAlloc() {} + virtual ~dtTileCacheAlloc(); virtual void reset() {} @@ -95,7 +95,7 @@ struct dtTileCacheAlloc struct dtTileCacheCompressor { - virtual ~dtTileCacheCompressor() { } + virtual ~dtTileCacheCompressor(); virtual int maxCompressedSize(const int bufferSize) = 0; virtual dtStatus compress(const unsigned char* buffer, const int bufferSize, @@ -130,6 +130,9 @@ dtStatus dtMarkCylinderArea(dtTileCacheLayer& layer, const float* orig, const fl dtStatus dtMarkBoxArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch, const float* bmin, const float* bmax, const unsigned char areaId); +dtStatus dtMarkBoxArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch, + const float* center, const float* halfExtents, const float* rotAux, const unsigned char areaId); + dtStatus dtBuildTileCacheRegions(dtTileCacheAlloc* alloc, dtTileCacheLayer& layer, const int walkableClimb); diff --git a/DetourTileCache/Source/DetourTileCache.cpp b/DetourTileCache/Source/DetourTileCache.cpp index cf575046..6f97ab5b 100644 --- a/DetourTileCache/Source/DetourTileCache.cpp +++ b/DetourTileCache/Source/DetourTileCache.cpp @@ -239,6 +239,11 @@ const dtTileCacheObstacle* dtTileCache::getObstacleByRef(dtObstacleRef ref) return ob; } +dtTileCacheMeshProcess::~dtTileCacheMeshProcess() +{ + // Defined out of line to fix the weak v-tables warning +} + dtStatus dtTileCache::addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result) { // Make sure the data is in right format. @@ -420,6 +425,45 @@ dtStatus dtTileCache::addBoxObstacle(const float* bmin, const float* bmax, dtObs return DT_SUCCESS; } +dtStatus dtTileCache::addBoxObstacle(const float* center, const float* halfExtents, const float yRadians, dtObstacleRef* result) +{ + if (m_nreqs >= MAX_REQUESTS) + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + + dtTileCacheObstacle* ob = 0; + if (m_nextFreeObstacle) + { + ob = m_nextFreeObstacle; + m_nextFreeObstacle = ob->next; + ob->next = 0; + } + if (!ob) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + unsigned short salt = ob->salt; + memset(ob, 0, sizeof(dtTileCacheObstacle)); + ob->salt = salt; + ob->state = DT_OBSTACLE_PROCESSING; + ob->type = DT_OBSTACLE_ORIENTED_BOX; + dtVcopy(ob->orientedBox.center, center); + dtVcopy(ob->orientedBox.halfExtents, halfExtents); + + float coshalf= cosf(0.5f*yRadians); + float sinhalf = sinf(-0.5f*yRadians); + ob->orientedBox.rotAux[0] = coshalf*sinhalf; + ob->orientedBox.rotAux[1] = coshalf*coshalf - 0.5f; + + ObstacleRequest* req = &m_reqs[m_nreqs++]; + memset(req, 0, sizeof(ObstacleRequest)); + req->action = REQUEST_ADD; + req->ref = getObstacleRef(ob); + + if (result) + *result = req->ref; + + return DT_SUCCESS; +} + dtStatus dtTileCache::removeObstacle(const dtObstacleRef ref) { if (!ref) @@ -650,7 +694,12 @@ dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* else if (ob->type == DT_OBSTACLE_BOX) { dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch, - ob->box.bmin, ob->box.bmax, 0); + ob->box.bmin, ob->box.bmax, 0); + } + else if (ob->type == DT_OBSTACLE_ORIENTED_BOX) + { + dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch, + ob->orientedBox.center, ob->orientedBox.halfExtents, ob->orientedBox.rotAux, 0); } } } @@ -662,7 +711,7 @@ dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* bc.lcset = dtAllocTileCacheContourSet(m_talloc); if (!bc.lcset) - return status; + return DT_FAILURE | DT_OUT_OF_MEMORY; status = dtBuildTileCacheContours(m_talloc, *bc.layer, walkableClimbVx, m_params.maxSimplificationError, *bc.lcset); if (dtStatusFailed(status)) @@ -670,7 +719,7 @@ dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* bc.lmesh = dtAllocTileCachePolyMesh(m_talloc); if (!bc.lmesh) - return status; + return DT_FAILURE | DT_OUT_OF_MEMORY; status = dtBuildTileCachePolyMesh(m_talloc, *bc.lcset, *bc.lmesh); if (dtStatusFailed(status)) return status; @@ -761,4 +810,16 @@ void dtTileCache::getObstacleBounds(const struct dtTileCacheObstacle* ob, float* dtVcopy(bmin, ob->box.bmin); dtVcopy(bmax, ob->box.bmax); } + else if (ob->type == DT_OBSTACLE_ORIENTED_BOX) + { + const dtObstacleOrientedBox &orientedBox = ob->orientedBox; + + float maxr = 1.41f*dtMax(orientedBox.halfExtents[0], orientedBox.halfExtents[2]); + bmin[0] = orientedBox.center[0] - maxr; + bmax[0] = orientedBox.center[0] + maxr; + bmin[1] = orientedBox.center[1] - orientedBox.halfExtents[1]; + bmax[1] = orientedBox.center[1] + orientedBox.halfExtents[1]; + bmin[2] = orientedBox.center[2] - maxr; + bmax[2] = orientedBox.center[2] + maxr; + } } diff --git a/DetourTileCache/Source/DetourTileCacheBuilder.cpp b/DetourTileCache/Source/DetourTileCacheBuilder.cpp index a81a7b43..796e165d 100644 --- a/DetourTileCache/Source/DetourTileCacheBuilder.cpp +++ b/DetourTileCache/Source/DetourTileCacheBuilder.cpp @@ -23,6 +23,15 @@ #include "DetourTileCacheBuilder.h" #include +dtTileCacheAlloc::~dtTileCacheAlloc() +{ + // Defined out of line to fix the weak v-tables warning +} + +dtTileCacheCompressor::~dtTileCacheCompressor() +{ + // Defined out of line to fix the weak v-tables warning +} template class dtFixedArray { @@ -1399,7 +1408,6 @@ static void pushBack(unsigned short v, unsigned short* arr, int& an) static bool canRemoveVertex(dtTileCachePolyMesh& mesh, const unsigned short rem) { // Count number of polygons to remove. - int numRemovedVerts = 0; int numTouchedVerts = 0; int numRemainingEdges = 0; for (int i = 0; i < mesh.npolys; ++i) @@ -1419,7 +1427,6 @@ static bool canRemoveVertex(dtTileCachePolyMesh& mesh, const unsigned short rem) } if (numRemoved) { - numRemovedVerts += numRemoved; numRemainingEdges += numVerts-(numRemoved+1); } } @@ -2041,6 +2048,60 @@ dtStatus dtMarkBoxArea(dtTileCacheLayer& layer, const float* orig, const float c return DT_SUCCESS; } +dtStatus dtMarkBoxArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch, + const float* center, const float* halfExtents, const float* rotAux, const unsigned char areaId) +{ + const int w = (int)layer.header->width; + const int h = (int)layer.header->height; + const float ics = 1.0f/cs; + const float ich = 1.0f/ch; + + float cx = (center[0] - orig[0])*ics; + float cz = (center[2] - orig[2])*ics; + + float maxr = 1.41f*dtMax(halfExtents[0], halfExtents[2]); + int minx = (int)floorf(cx - maxr*ics); + int maxx = (int)floorf(cx + maxr*ics); + int minz = (int)floorf(cz - maxr*ics); + int maxz = (int)floorf(cz + maxr*ics); + int miny = (int)floorf((center[1]-halfExtents[1]-orig[1])*ich); + int maxy = (int)floorf((center[1]+halfExtents[1]-orig[1])*ich); + + if (maxx < 0) return DT_SUCCESS; + if (minx >= w) return DT_SUCCESS; + if (maxz < 0) return DT_SUCCESS; + if (minz >= h) return DT_SUCCESS; + + if (minx < 0) minx = 0; + if (maxx >= w) maxx = w-1; + if (minz < 0) minz = 0; + if (maxz >= h) maxz = h-1; + + float xhalf = halfExtents[0]*ics + 0.5f; + float zhalf = halfExtents[2]*ics + 0.5f; + + for (int z = minz; z <= maxz; ++z) + { + for (int x = minx; x <= maxx; ++x) + { + float x2 = 2.0f*(float(x) - cx); + float z2 = 2.0f*(float(z) - cz); + float xrot = rotAux[1]*x2 + rotAux[0]*z2; + if (xrot > xhalf || xrot < -xhalf) + continue; + float zrot = rotAux[1]*z2 - rotAux[0]*x2; + if (zrot > zhalf || zrot < -zhalf) + continue; + const int y = layer.heights[x+z*w]; + if (y < miny || y > maxy) + continue; + layer.areas[x+z*w] = areaId; + } + } + + return DT_SUCCESS; +} + dtStatus dtBuildTileCacheLayer(dtTileCacheCompressor* comp, dtTileCacheLayerHeader* header, const unsigned char* heights, @@ -2142,7 +2203,7 @@ dtStatus dtDecompressTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheCompress grids, gridsSize, &size); if (dtStatusFailed(status)) { - dtFree(buffer); + alloc->free(buffer); return status; } diff --git a/Docs/Conceptual/license_c.txt b/Docs/Conceptual/license_c.txt deleted file mode 100644 index c2a3c1e8..00000000 --- a/Docs/Conceptual/license_c.txt +++ /dev/null @@ -1,27 +0,0 @@ - -/** -@page License License - -
-Copyright (c) 2009-2011 Mikko Mononen memon@inside.org
-
-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.
-
- -*/ \ No newline at end of file diff --git a/Docs/Conceptual/mainpage_c.txt b/Docs/Conceptual/mainpage_c.txt deleted file mode 100644 index 14dd1cf8..00000000 --- a/Docs/Conceptual/mainpage_c.txt +++ /dev/null @@ -1,109 +0,0 @@ -/// @mainpage Recast Navigation -/// -/// @image html recast_intro.png -/// -///

Recast

-/// -/// _Recast_ is a state of the art navigation mesh construction toolset for -/// games. -/// -/// - It is automatic, which means that you can throw any level -/// geometry at it and you will get a robust mesh out. -/// - It is fast, which means swift turnaround times for level designers. -/// - It is open source, so it comes with full source and you can -/// customize it to your hearts content. -/// -/// The latest version can be found on -/// GitHub. -/// -/// The _Recast_ process starts with constructing a voxel mold from level -/// geometry and then casting a navigation mesh over it. The process -/// consists of three steps: building the voxel mold, partitioning the -/// mold into simple regions, and triangulating the regions as simple polygons. -/// -/// -# The voxel mold is built from the input triangle mesh by -/// rasterizing the triangles into a multi-layer heightfield. Some -/// simple filters are then applied to the mold to prune out locations -/// where the character would not be able to move. -/// -# The walkable areas described by the mold are divided into simple -/// overlayed 2D regions. The resulting regions have only one -/// non-overlapping contour, which simplifies the final step of the -/// process tremendously. -/// -# The navigation polygons are generated from the regions by first -/// tracing the boundaries and then simplifying them. The resulting -/// polygons are finally converted to convex polygons which makes them -/// perfect for pathfinding and spatial reasoning about the level. -/// -///

Detour

-/// -/// _Recast_ is accompanied by _Detour_, a path-finding and spatial reasoning -/// toolkit. You can use any navigation mesh with _Detour_, but of course -/// the data generated by _Recast_ fits perfectly. -/// -/// _Detour_ offers a simple static navigation mesh that is suitable for -/// many simple cases, as well as a tiled navigation mesh that allows you -/// to add and remove pieces of the mesh. The tiled mesh allows you to -/// create systems where you stream new navigation data in and out as -/// the player progresses the level, or regenerate tiles as the -/// world changes. -/// -///

Recast Demo

-/// -/// You can find a comprehensive demo project in the `RecastDemo` folder. It -/// is a kitchen sink demo containing all the major functionality of the library. -/// If you are new to _Recast_ & _Detour_, check out -/// -/// Sample_SoloMesh.cpp to get started with building navmeshes and -/// -/// NavMeshTesterTool.cpp to see how _Detour_ can be used to find paths. -/// -///

Building RecastDemo

-/// -/// RecastDemo uses [premake5](http://premake.github.io/) to build platform specific projects. -/// Download it and make sure it's available on your path, or specify the path to it. -/// -///

Linux

-/// -/// - Install SDl2 and its dependencies according to your distro's guidelines. -/// - run `premake5 gmake` from the `RecastDemo` folder. -/// - `cd Build/gmake` then `make` -/// - Run `RecastDemo\Bin\RecastDemo` -/// -///

OSX

-/// -/// - Grab the latest SDL2 development library dmg from [here](https://www.libsdl.org/download-2.0.php) and place `SDL2.framework` in `/Library/Frameworks/` -/// - Navigate to the `RecastDemo` folder and run `premake5 xcode4` -/// - Open `Build/xcode4/recastnavigation.xcworkspace` -/// - Select the "RecastDemo" project in the left pane, go to the "BuildPhases" tab and expand "Link Binary With Libraries" -/// - Remove the existing entry for SDL2 (it should have a white box icon) and re-add it by hitting the plus, selecting "Add Other", and selecting `/Library/Frameworks/SDL2.framework`. It should now have a suitcase icon. -/// - Set the RecastDemo project as the target and build. -/// -///

Windows

-/// -/// - Grab the latest SDL2 development library release from [here](https://www.libsdl.org/download-2.0.php) and unzip it `RecastDemo\Contrib`. Rename the SDL folder such that the path `RecastDemo\Contrib\SDL\lib\x86` is valid. -/// - Run `"premake5" vs2015` from the `RecastDemo` folder -/// - Open the solution, build, and run. -/// -///

Integrating With Your Own Project

-/// -/// It is recommended to add the source directories `DebugUtils`, `Detour`, -/// `DetourCrowd`, `DetourTileCache`, and `Recast` into your own project -/// depending on which parts of the project you need. For example your -/// level building tool could include `DebugUtils`, `Recast`, and `Detour`, -/// and your game runtime could just include `Detour`. -/// -///

Contributing

-/// All development is centralized in github. Check out the Contributing Guidelines for more information. -/// -///

Discuss

-/// -/// - Discuss _Recast_ and _Detour_: -/// -/// Recast Navigation Group -/// - Development Blog: -/// Digesting Duck -/// -///

License

-/// -/// _Recast Navigation_ is licensed under the ZLib license. -/// diff --git a/Docs/DoxygenLayout.xml b/Docs/DoxygenLayout.xml index b20079c9..db963bba 100644 --- a/Docs/DoxygenLayout.xml +++ b/Docs/DoxygenLayout.xml @@ -107,7 +107,7 @@ - + diff --git a/Docs/Extern/Recast_api.txt b/Docs/Extern/Recast_api.txt index f6ff0644..6cd4c63f 100644 --- a/Docs/Extern/Recast_api.txt +++ b/Docs/Extern/Recast_api.txt @@ -28,9 +28,6 @@ The general life-cycle of the main classes is as follows: -# Use the object as part of the pipeline. -# Free the object using the Recast allocator. (E.g. #rcFreeHeightField) -@note This is a summary list of members. Use the index or search -feature to find minor members. - @struct rcConfig @par @@ -51,6 +48,10 @@ the platform's floating point accuraccy as well as interdependencies between the values of multiple parameters. See the individual parameter documentation for details. +@note First you should decide the size of your agent's logical cylinder. +If your game world uses meters as units, a reasonable starting point for +a human-sized agent might be a radius of `0.4` and a height of `2.0`. + @var rcConfig::borderSize @par @@ -66,7 +67,24 @@ This field is only used when building multi-tile meshes. @var rcConfig::cs @par -@p cs and #ch define voxel/grid/cell size. So their values have significant +The voxelization cell size #cs defines the voxel size along both axes of +the ground plane: x and z in Recast. This value is usually derived from the +character radius `r`. A recommended starting value for #cs is either `r/2` +or `r/3`. Smaller values of #cs will increase rasterization resolution and +navmesh detail, but total generation time will increase exponentially. In +outdoor environments, `r/2` is often good enough. For indoor scenes with +tight spaces you might want the extra precision, so a value of `r/3` or +smaller may give better results. + +The initial instinct is to reduce this value to something very close to zero +to maximize the detail of the generated navmesh. This quickly becomes a case +of diminishing returns, however. Beyond a certain point there's usually not +much perceptable difference in the generated navmesh, but huge increases in +generation time. This hinders your ability to quickly iterate on level +designs and provides little benefit. The general recommendation here is to +use as large a value for #cs as you can get away with. + +#cs and #ch define voxel/grid/cell size. So their values have significant side effects on all parameters defined in voxel units. The minimum value for this parameter depends on the platform's floating point @@ -75,7 +93,15 @@ accuracy, with the practical minimum usually around 0.05. @var rcConfig::ch @par -#cs and @p ch define voxel/grid/cell size. So their values have significant +The voxelization cell height #ch is defined separately in order to allow for +greater precision in height tests. A good starting point for #ch is half the +#cs value. Smaller #ch values ensure that the navmesh properly connects areas +that are only separated by a small curb or ditch. If small holes are generated +in your navmesh around where there are discontinuities in height (for example, +stairs or curbs), you may want to decrease the cell height value to increase +the vertical rasterization precision of Recast. + +#cs and #ch define voxel/grid/cell size. So their values have significant side effects on all parameters defined in voxel units. The minimum value for this parameter depends on the platform's floating point @@ -84,44 +110,106 @@ accuracy, with the practical minimum usually around 0.05. @var rcConfig::walkableSlopeAngle @par +The parameter #walkableSlopeAngle is to filter out areas of the world where +the ground slope would be too steep for an agent to traverse. This value is +defined as a maximum angle in degrees that the surface normal of a polgyon +can differ from the world's up vector. This value must be within the range +`[0, 90]`. + The practical upper limit for this parameter is usually around 85 degrees. @var rcConfig::walkableHeight @par +This value defines the worldspace height `h` of the agent in voxels. Th value +of #walkableHeight should be calculated as `ceil(h / ch)`. Note this is based +on #ch not #cs since it's a height value. + Permits detection of overhangs in the source geometry that make the geometry below un-walkable. The value is usually set to the maximum agent height. @var rcConfig::walkableClimb @par +The #walkableClimb value defines the maximum height of ledges and steps that +the agent can walk up. Given a designer-defined `maxClimb` distance in world +units, the value of #walkableClimb should be calculated as `ceil(maxClimb / ch)`. +Note that this is using #ch not #cs because it's a height-based value. + Allows the mesh to flow over low lying obstructions such as curbs and up/down stairways. The value is usually set to how far up/down an agent can step. @var rcConfig::walkableRadius @par +The parameter #walkableRadius defines the worldspace agent radius `r` in voxels. +Most often, this value of #walkableRadius should be calculated as `ceil(r / cs)`. +Note this is based on #cs since the agent radius is always parallel to the ground +plane. + +If the #walkableRadius value is greater than zero, the edges of the navmesh will +be pushed away from all obstacles by this amount. + +A non-zero #walkableRadius allows for much simpler runtime navmesh collision checks. +The game only needs to check that the center point of the agent is contained within +a navmesh polygon. Without this erosion, runtime navigation checks need to collide +the geometric projection of the agent's logical cylinder onto the navmesh with the +boundary edges of the navmesh polygons. + In general, this is the closest any part of the final mesh should get to an obstruction in the source geometry. It is usually set to the maximum agent radius. -While a value of zero is legal, it is not recommended and can result in -odd edge case issues. +If you want to have tight-fitting navmesh, or want to reuse the same navmesh for +multiple agents with differing radii, you can use a `walkableRadius` value of zero. +Be advised though that you will need to perform your own collisions with the navmesh +edges, and odd edge cases issues in the mesh generation can potentially occur. For +these reasons, specifying a radius of zero is allowed but is not recommended. @var rcConfig::maxEdgeLen @par +In certain cases, long outer edges may decrease the quality of the resulting +triangulation, creating very long thin triangles. This can sometimes be +remedied by limiting the maximum edge length, causing the problematic long +edges to be broken up into smaller segments. + +The parameter #maxEdgeLen defines the maximum edge length and is defined in +terms of voxels. A good value for #maxEdgeLen is something like +`walkableRadius * 8`. A good way to adjust this value is to first set it really +high and see if your data creates long edges. If it does, decrease #maxEdgeLen +until you find the largest value which improves the resulting tesselation. + Extra vertices will be inserted as needed to keep contour edges below this length. A value of zero effectively disables this feature. @var rcConfig::maxSimplificationError @par +When the rasterized areas are converted back to a vectorized representation, +the #maxSimplificationError describes how loosely the simplification is done. +The simplification process uses the +Ramer–Douglas-Peucker algorithm, +and this value describes the max deviation in voxels. + +Good values for #maxSimplificationError are in the range `[1.1, 1.5]`. +A value of `1.3` is a good starting point and usually yields good results. +If the value is less than `1.1`, some sawtoothing starts to appear at the +generated edges. If the value is more than `1.5`, the mesh simplification +starts to cut some corners it shouldn't. + The effect of this parameter only applies to the xz-plane. @var rcConfig::minRegionArea @par +Watershed partitioning is really prone to noise in the input distance field. +In order to get nicer areas, the areas are merged and small disconnected areas +are removed after the water shed partitioning. The parameter #minRegionArea +describes the minimum isolated region size that is still kept. A region is +removed if the number of voxels in the region is less than the square of +#minRegionArea. + Any regions that are smaller than this area will be marked as unwalkable. This is useful in removing useless regions that can sometimes form on geometry such as table tops, box tops, etc. @@ -132,6 +220,13 @@ geometry such as table tops, box tops, etc. If the mesh data is to be used to construct a Detour navigation mesh, then the upper limit is limited to <= #DT_VERTS_PER_POLYGON. +@var rcConfig::mergeRegionArea +@par + +The triangulation process works best with small, localized voxel regions. +The parameter #mergeRegionArea controls the maximum voxel area of a region +that is allowed to be merged with another region. If you see small patches +missing here and there, you could lower the #minRegionArea value. @struct rcHeightfield @par diff --git a/Docs/Images/recast_intro.png b/Docs/Images/recast_intro.png deleted file mode 100644 index ad18ac61..00000000 Binary files a/Docs/Images/recast_intro.png and /dev/null differ diff --git a/RecastDemo/screenshot.png b/Docs/Images/screenshot.png similarity index 100% rename from RecastDemo/screenshot.png rename to Docs/Images/screenshot.png diff --git a/Docs/Readme.txt b/Docs/Readme.txt index be153b94..3bf89fa8 100644 --- a/Docs/Readme.txt +++ b/Docs/Readme.txt @@ -7,11 +7,6 @@ Directory Layout High level content and format files. (E.g. css, header, footer.) -./Conceptual - - Conceptual (non-api) documentation such as overviews, how-to's, etc. - The main index page content is also in this directory. - ./Extern API documentation that is located outside the source files. diff --git a/Docs/doxygen-awesome-css/LICENSE b/Docs/doxygen-awesome-css/LICENSE new file mode 100644 index 00000000..1d8b99a9 --- /dev/null +++ b/Docs/doxygen-awesome-css/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 jothepro + +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. diff --git a/Docs/doxygen-awesome-css/README.md b/Docs/doxygen-awesome-css/README.md new file mode 100644 index 00000000..5b07c923 --- /dev/null +++ b/Docs/doxygen-awesome-css/README.md @@ -0,0 +1,124 @@ +# Doxygen Awesome + +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/jothepro/doxygen-awesome-css)](https://github.com/jothepro/doxygen-awesome-css/releases/latest) +[![GitHub](https://img.shields.io/github/license/jothepro/doxygen-awesome-css)](https://github.com/jothepro/doxygen-awesome-css/blob/main/LICENSE) +![GitHub Repo stars](https://img.shields.io/github/stars/jothepro/doxygen-awesome-css) + +
+ +![Screenshot of Doxygen Awesome CSS](img/screenshot.png) + +
+ +**Doxygen Awesome** is a custom **CSS theme for Doxygen HTML-documentation** with lots of customization parameters. + +## Motivation + +I really like how the Doxygen HTML-documentation is structured! But IMHO it looks a bit outdated. + +This theme is an attempt to update the visuals of Doxygen without changing its overall layout too much. + +## Features + +- 🌈 Clean, modern design +- 🚀 Heavily customizable by adjusting CSS-variables +- 🧩 No changes to the HTML structure of Doxygen required +- 📱 Improved mobile usability +- 🌘 Dark mode support! +- 🥇 Works best with **doxygen 1.9.1** - **1.9.4** + +## Examples + +Some websites using this theme: + +- [Documentation of this repository](https://jothepro.github.io/doxygen-awesome-css/) +- [wxWidgets](https://docs.wxwidgets.org/3.2/) +- [OpenCV 5.x](https://docs.opencv.org/5.x/) +- [Zephyr](https://docs.zephyrproject.org/latest/doxygen/html/index.html) +- [FELTOR](https://mwiesenberger.github.io/feltor/dg/html/modules.html) +- [Spatial Audio Framework (SAF)](https://leomccormack.github.io/Spatial_Audio_Framework/index.html) +- [libCloudSync](https://jothepro.github.io/libCloudSync/) +- [libsl3](https://a4z.github.io/libsl3/) + +## Installation + +To use the theme in your documentation, copy the required CSS and JS files from this repository into your project or add the repository as submodule and check out the latest release: + +```bash +git submodule add https://github.com/jothepro/doxygen-awesome-css.git +cd doxygen-awesome-css +git checkout v2.1.0 +``` + +All theme files are located in the root of this repository and start with the prefix `doxygen-awesome-`. You may not need all of them. Follow the install instructions to figure out what files are required for your setup. + +### Choosing a layout + +There is two layout options. Choose one of them and configure Doxygen accordingly: + +
+ +![Available theme variants](img/theme-variants.drawio.svg) + +
+ + + +#### Base Theme (1) + +Comes with the typical Doxygen titlebar. Optionally the treeview in the sidebar can be enabled. + +Required files: `doxygen-awesome.css` + +Required `Doxyfile` configuration: +``` +GENERATE_TREEVIEW = YES # optional. Also works without treeview +DISABLE_INDEX = NO +FULL_SIDEBAR = NO +HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css +``` + +#### Sidebar-Only Theme (2) + +Hides the top titlebar to give more space to the content. The treeview must be enabled in order for this theme to work. + +Required files: `doxygen-awesome.css`, `doxygen-awesome-sidebar-only.css` + +Required `Doxyfile` configuration: +``` +GENERATE_TREEVIEW = YES # required! +DISABLE_INDEX = NO +FULL_SIDEBAR = NO +HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css \ + doxygen-awesome-css/doxygen-awesome-sidebar-only.css +``` + + +**Caution**: This theme is not compatible with the `FULL_SIDEBAR = YES` option provided by Doxygen! + +### Further installation instructions: + +- [Installing extensions](docs/extensions.md) +- [Customizing the theme (colors, spacing, border-radius, ...)](docs/customization.md) +- [Tips and Tricks for further configuration](docs/tricks.md) + +## Browser support + +Tested with + +- Chrome 104, Chrome 104 for Android, Chrome 103 for iOS +- Safari 15, Safari for iOS 15 +- Firefox 103, Firefox 103 for Android, Firefox Daylight 102 for iOS +- Edge 104 + +The theme does not strive to be backwards compatible to (significantly) older browser versions. + +## Credits + +- This theme was initially inspired by the [vuepress](https://vuepress.vuejs.org/) static site generator default theme. +- Thank you for all the bug reports, pull requests and inspiring feedback on github! + + + +Read Next: [Extensions](docs/extensions.md) + \ No newline at end of file diff --git a/Docs/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js b/Docs/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js new file mode 100644 index 00000000..f2c5853f --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js @@ -0,0 +1,157 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2021 - 2022 jothepro + +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. + +*/ + +class DoxygenAwesomeDarkModeToggle extends HTMLElement { + // SVG icons from https://fonts.google.com/icons + // Licensed under the Apache 2.0 license: + // https://www.apache.org/licenses/LICENSE-2.0.html + static lightModeIcon = `` + static darkModeIcon = `` + static title = "Toggle Light/Dark Mode" + + static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode" + static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode" + + static _staticConstructor = function() { + DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference) + // Update the color scheme when the browsers preference changes + // without user interaction on the website. + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { + DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() + }) + // Update the color scheme when the tab is made visible again. + // It is possible that the appearance was changed in another tab + // while this tab was in the background. + document.addEventListener("visibilitychange", visibilityState => { + if (document.visibilityState === 'visible') { + DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() + } + }); + }() + + static init() { + $(function() { + $(document).ready(function() { + const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle') + toggleButton.title = DoxygenAwesomeDarkModeToggle.title + toggleButton.updateIcon() + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { + toggleButton.updateIcon() + }) + document.addEventListener("visibilitychange", visibilityState => { + if (document.visibilityState === 'visible') { + toggleButton.updateIcon() + } + }); + + $(document).ready(function(){ + document.getElementById("MSearchBox").parentNode.appendChild(toggleButton) + }) + $(window).resize(function(){ + document.getElementById("MSearchBox").parentNode.appendChild(toggleButton) + }) + }) + }) + } + + constructor() { + super(); + this.onclick=this.toggleDarkMode + } + + /** + * @returns `true` for dark-mode, `false` for light-mode system preference + */ + static get systemPreference() { + return window.matchMedia('(prefers-color-scheme: dark)').matches + } + + /** + * @returns `true` for dark-mode, `false` for light-mode user preference + */ + static get userPreference() { + return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) || + (DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)) + } + + static set userPreference(userPreference) { + DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference + if(!userPreference) { + if(DoxygenAwesomeDarkModeToggle.systemPreference) { + localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true) + } else { + localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey) + } + } else { + if(!DoxygenAwesomeDarkModeToggle.systemPreference) { + localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true) + } else { + localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey) + } + } + DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged() + } + + static enableDarkMode(enable) { + if(enable) { + DoxygenAwesomeDarkModeToggle.darkModeEnabled = true + document.documentElement.classList.add("dark-mode") + document.documentElement.classList.remove("light-mode") + } else { + DoxygenAwesomeDarkModeToggle.darkModeEnabled = false + document.documentElement.classList.remove("dark-mode") + document.documentElement.classList.add("light-mode") + } + } + + static onSystemPreferenceChanged() { + DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference + DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) + } + + static onUserPreferenceChanged() { + DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) + } + + toggleDarkMode() { + DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference + this.updateIcon() + } + + updateIcon() { + if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) { + this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon + } else { + this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon + } + } +} + +customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle); diff --git a/Docs/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js b/Docs/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js new file mode 100644 index 00000000..7d06b348 --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js @@ -0,0 +1,85 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2022 jothepro + +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. + +*/ + +class DoxygenAwesomeFragmentCopyButton extends HTMLElement { + constructor() { + super(); + this.onclick=this.copyContent + } + static title = "Copy to clipboard" + static copyIcon = `` + static successIcon = `` + static successDuration = 980 + static init() { + $(function() { + $(document).ready(function() { + if(navigator.clipboard) { + const fragments = document.getElementsByClassName("fragment") + for(const fragment of fragments) { + const fragmentWrapper = document.createElement("div") + fragmentWrapper.className = "doxygen-awesome-fragment-wrapper" + const fragmentCopyButton = document.createElement("doxygen-awesome-fragment-copy-button") + fragmentCopyButton.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon + fragmentCopyButton.title = DoxygenAwesomeFragmentCopyButton.title + + fragment.parentNode.replaceChild(fragmentWrapper, fragment) + fragmentWrapper.appendChild(fragment) + fragmentWrapper.appendChild(fragmentCopyButton) + + } + } + }) + }) + } + + + copyContent() { + const content = this.previousSibling.cloneNode(true) + // filter out line number from file listings + content.querySelectorAll(".lineno, .ttc").forEach((node) => { + node.remove() + }) + let textContent = content.textContent + // remove trailing newlines that appear in file listings + let numberOfTrailingNewlines = 0 + while(textContent.charAt(textContent.length - (numberOfTrailingNewlines + 1)) == '\n') { + numberOfTrailingNewlines++; + } + textContent = textContent.substring(0, textContent.length - numberOfTrailingNewlines) + navigator.clipboard.writeText(textContent); + this.classList.add("success") + this.innerHTML = DoxygenAwesomeFragmentCopyButton.successIcon + window.setTimeout(() => { + this.classList.remove("success") + this.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon + }, DoxygenAwesomeFragmentCopyButton.successDuration); + } +} + +customElements.define("doxygen-awesome-fragment-copy-button", DoxygenAwesomeFragmentCopyButton) diff --git a/Docs/doxygen-awesome-css/doxygen-awesome-interactive-toc.js b/Docs/doxygen-awesome-css/doxygen-awesome-interactive-toc.js new file mode 100644 index 00000000..b049f573 --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-awesome-interactive-toc.js @@ -0,0 +1,81 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2022 jothepro + +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. + +*/ + +class DoxygenAwesomeInteractiveToc { + static topOffset = 38 + static hideMobileMenu = true + static headers = [] + + static init() { + window.addEventListener("load", () => { + let toc = document.querySelector(".contents > .toc") + if(toc) { + toc.classList.add("interactive") + if(!DoxygenAwesomeInteractiveToc.hideMobileMenu) { + toc.classList.add("open") + } + document.querySelector(".contents > .toc > h3")?.addEventListener("click", () => { + if(toc.classList.contains("open")) { + toc.classList.remove("open") + } else { + toc.classList.add("open") + } + }) + + document.querySelectorAll(".contents > .toc > ul a").forEach((node) => { + let id = node.getAttribute("href").substring(1) + DoxygenAwesomeInteractiveToc.headers.push({ + node: node, + headerNode: document.getElementById(id) + }) + + document.getElementById("doc-content")?.addEventListener("scroll", () => { + DoxygenAwesomeInteractiveToc.update() + }) + }) + DoxygenAwesomeInteractiveToc.update() + } + }) + } + + static update() { + let active = DoxygenAwesomeInteractiveToc.headers[0]?.node + DoxygenAwesomeInteractiveToc.headers.forEach((header) => { + let position = header.headerNode.getBoundingClientRect().top + header.node.classList.remove("active") + header.node.classList.remove("aboveActive") + if(position < DoxygenAwesomeInteractiveToc.topOffset) { + active = header.node + active?.classList.add("aboveActive") + } + }) + active?.classList.add("active") + active?.classList.remove("aboveActive") + } +} \ No newline at end of file diff --git a/Docs/doxygen-awesome-css/doxygen-awesome-paragraph-link.js b/Docs/doxygen-awesome-css/doxygen-awesome-paragraph-link.js new file mode 100644 index 00000000..6424dbd4 --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-awesome-paragraph-link.js @@ -0,0 +1,51 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2022 jothepro + +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. + +*/ + +class DoxygenAwesomeParagraphLink { + // Icon from https://fonts.google.com/icons + // Licensed under the Apache 2.0 license: + // https://www.apache.org/licenses/LICENSE-2.0.html + static icon = `` + static title = "Permanent Link" + static init() { + $(function() { + $(document).ready(function() { + document.querySelectorAll(".contents a.anchor[id], .contents .groupheader > a[id]").forEach((node) => { + let anchorlink = document.createElement("a") + anchorlink.setAttribute("href", `#${node.getAttribute("id")}`) + anchorlink.setAttribute("title", DoxygenAwesomeParagraphLink.title) + anchorlink.classList.add("anchorlink") + node.classList.add("anchor") + anchorlink.innerHTML = DoxygenAwesomeParagraphLink.icon + node.parentElement.appendChild(anchorlink) + }) + }) + }) + } +} diff --git a/Docs/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css b/Docs/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css new file mode 100644 index 00000000..b988b6f0 --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css @@ -0,0 +1,40 @@ + +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2021 jothepro + +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. + +*/ + +@media screen and (min-width: 768px) { + + #MSearchBox { + width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px); + } + + #MSearchField { + width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height)); + } +} diff --git a/Docs/doxygen-awesome-css/doxygen-awesome-sidebar-only.css b/Docs/doxygen-awesome-css/doxygen-awesome-sidebar-only.css new file mode 100644 index 00000000..656ebbf4 --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-awesome-sidebar-only.css @@ -0,0 +1,115 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2021 jothepro + +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. + + */ + +html { + /* side nav width. MUST be = `TREEVIEW_WIDTH`. + * Make sure it is wide enough to contain the page title (logo + title + version) + */ + --side-nav-fixed-width: 335px; + --menu-display: none; + + --top-height: 120px; + --toc-sticky-top: -25px; + --toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px); +} + +#projectname { + white-space: nowrap; +} + + +@media screen and (min-width: 768px) { + html { + --searchbar-background: var(--page-background-color); + } + + #side-nav { + min-width: var(--side-nav-fixed-width); + max-width: var(--side-nav-fixed-width); + top: var(--top-height); + overflow: visible; + } + + #nav-tree, #side-nav { + height: calc(100vh - var(--top-height)) !important; + } + + #nav-tree { + padding: 0; + } + + #top { + display: block; + border-bottom: none; + height: var(--top-height); + margin-bottom: calc(0px - var(--top-height)); + max-width: var(--side-nav-fixed-width); + overflow: hidden; + background: var(--side-nav-background); + } + #main-nav { + float: left; + padding-right: 0; + } + + .ui-resizable-handle { + cursor: default; + width: 1px !important; + box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color); + } + + #nav-path { + position: fixed; + right: 0; + left: var(--side-nav-fixed-width); + bottom: 0; + width: auto; + } + + #doc-content { + height: calc(100vh - 31px) !important; + padding-bottom: calc(3 * var(--spacing-large)); + padding-top: calc(var(--top-height) - 80px); + box-sizing: border-box; + margin-left: var(--side-nav-fixed-width) !important; + } + + #MSearchBox { + width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium))); + } + + #MSearchField { + width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px); + } + + #MSearchResultsWindow { + left: var(--spacing-medium) !important; + right: auto; + } +} diff --git a/Docs/doxygen-awesome-css/doxygen-awesome.css b/Docs/doxygen-awesome-css/doxygen-awesome.css new file mode 100644 index 00000000..abd2893c --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-awesome.css @@ -0,0 +1,2405 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2021 - 2022 jothepro + +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. + +*/ + +html { + /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ + --primary-color: #1779c4; + --primary-dark-color: #335c80; + --primary-light-color: #70b1e9; + + /* page base colors */ + --page-background-color: #ffffff; + --page-foreground-color: #2f4153; + --page-secondary-foreground-color: #6f7e8e; + + /* color for all separators on the website: hr, borders, ... */ + --separator-color: #dedede; + + /* border radius for all rounded components. Will affect many components, like dropdowns, memitems, codeblocks, ... */ + --border-radius-large: 8px; + --border-radius-small: 4px; + --border-radius-medium: 6px; + + /* default spacings. Most components reference these values for spacing, to provide uniform spacing on the page. */ + --spacing-small: 5px; + --spacing-medium: 10px; + --spacing-large: 16px; + + /* default box shadow used for raising an element above the normal content. Used in dropdowns, search result, ... */ + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.075); + + --odd-color: rgba(0,0,0,.028); + + /* font-families. will affect all text on the website + * font-family: the normal font for text, headlines, menus + * font-family-monospace: used for preformatted text in memtitle, code, fragments + */ + --font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; + --font-family-monospace: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + + /* font sizes */ + --page-font-size: 15.6px; + --navigation-font-size: 14.4px; + --toc-font-size: 13.4px; + --code-font-size: 14px; /* affects code, fragment */ + --title-font-size: 22px; + + /* content text properties. These only affect the page content, not the navigation or any other ui elements */ + --content-line-height: 27px; + /* The content is centered and constraint in it's width. To make the content fill the whole page, set the variable to auto.*/ + --content-maxwidth: 1050px; + --table-line-height: 24px; + --toc-sticky-top: var(--spacing-medium); + --toc-width: 200px; + --toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 85px); + + /* colors for various content boxes: @warning, @note, @deprecated @bug */ + --warning-color: #f8d1cc; + --warning-color-dark: #b61825; + --warning-color-darker: #75070f; + --note-color: #faf3d8; + --note-color-dark: #f3a600; + --note-color-darker: #5f4204; + --todo-color: #e4f3ff; + --todo-color-dark: #1879C4; + --todo-color-darker: #274a5c; + --deprecated-color: #ecf0f3; + --deprecated-color-dark: #5b6269; + --deprecated-color-darker: #43454a; + --bug-color: #e4dafd; + --bug-color-dark: #5b2bdd; + --bug-color-darker: #2a0d72; + --invariant-color: #d8f1e3; + --invariant-color-dark: #44b86f; + --invariant-color-darker: #265532; + + /* blockquote colors */ + --blockquote-background: #f8f9fa; + --blockquote-foreground: #636568; + + /* table colors */ + --tablehead-background: #f1f1f1; + --tablehead-foreground: var(--page-foreground-color); + + /* menu-display: block | none + * Visibility of the top navigation on screens >= 768px. On smaller screen the menu is always visible. + * `GENERATE_TREEVIEW` MUST be enabled! + */ + --menu-display: block; + + --menu-focus-foreground: var(--page-background-color); + --menu-focus-background: var(--primary-color); + --menu-selected-background: rgba(0,0,0,.05); + + + --header-background: var(--page-background-color); + --header-foreground: var(--page-foreground-color); + + /* searchbar colors */ + --searchbar-background: var(--side-nav-background); + --searchbar-foreground: var(--page-foreground-color); + + /* searchbar size + * (`searchbar-width` is only applied on screens >= 768px. + * on smaller screens the searchbar will always fill the entire screen width) */ + --searchbar-height: 33px; + --searchbar-width: 210px; + --searchbar-border-radius: var(--searchbar-height); + + /* code block colors */ + --code-background: #f5f5f5; + --code-foreground: var(--page-foreground-color); + + /* fragment colors */ + --fragment-background: #F8F9FA; + --fragment-foreground: #37474F; + --fragment-keyword: #bb6bb2; + --fragment-keywordtype: #8258b3; + --fragment-keywordflow: #d67c3b; + --fragment-token: #438a59; + --fragment-comment: #969696; + --fragment-link: #5383d6; + --fragment-preprocessor: #46aaa5; + --fragment-linenumber-color: #797979; + --fragment-linenumber-background: #f4f4f5; + --fragment-linenumber-border: #e3e5e7; + --fragment-lineheight: 20px; + + /* sidebar navigation (treeview) colors */ + --side-nav-background: #fbfbfb; + --side-nav-foreground: var(--page-foreground-color); + --side-nav-arrow-opacity: 0; + --side-nav-arrow-hover-opacity: 0.9; + + --toc-background: var(--side-nav-background); + --toc-foreground: var(--side-nav-foreground); + + /* height of an item in any tree / collapsable table */ + --tree-item-height: 30px; + + --memname-font-size: var(--code-font-size); + --memtitle-font-size: 18px; + + --webkit-scrollbar-size: 7px; + --webkit-scrollbar-padding: 4px; + --webkit-scrollbar-color: var(--separator-color); +} + +@media screen and (max-width: 767px) { + html { + --page-font-size: 16px; + --navigation-font-size: 16px; + --toc-font-size: 15px; + --code-font-size: 15px; /* affects code, fragment */ + --title-font-size: 22px; + } +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) { + color-scheme: dark; + + --primary-color: #1982d2; + --primary-dark-color: #86a9c4; + --primary-light-color: #4779ac; + + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.35); + + --odd-color: rgba(100,100,100,.06); + + --menu-selected-background: rgba(0,0,0,.4); + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #38393b; + --side-nav-background: #252628; + + --code-background: #2a2c2f; + + --tablehead-background: #2a2c2f; + + --blockquote-background: #222325; + --blockquote-foreground: #7e8c92; + + --warning-color: #2e1917; + --warning-color-dark: #ad2617; + --warning-color-darker: #f5b1aa; + --note-color: #3b2e04; + --note-color-dark: #f1b602; + --note-color-darker: #ceb670; + --todo-color: #163750; + --todo-color-dark: #1982D2; + --todo-color-darker: #dcf0fa; + --deprecated-color: #2e323b; + --deprecated-color-dark: #738396; + --deprecated-color-darker: #abb0bd; + --bug-color: #2a2536; + --bug-color-dark: #7661b3; + --bug-color-darker: #ae9ed6; + --invariant-color: #303a35; + --invariant-color-dark: #76ce96; + --invariant-color-darker: #cceed5; + + --fragment-background: #282c34; + --fragment-foreground: #dbe4eb; + --fragment-keyword: #cc99cd; + --fragment-keywordtype: #ab99cd; + --fragment-keywordflow: #e08000; + --fragment-token: #7ec699; + --fragment-comment: #999999; + --fragment-link: #98c0e3; + --fragment-preprocessor: #65cabe; + --fragment-linenumber-color: #cccccc; + --fragment-linenumber-background: #35393c; + --fragment-linenumber-border: #1f1f1f; + } +} + +/* dark mode variables are defined twice, to support both the dark-mode without and with doxygen-awesome-darkmode-toggle.js */ +html.dark-mode { + color-scheme: dark; + + --primary-color: #1982d2; + --primary-dark-color: #86a9c4; + --primary-light-color: #4779ac; + + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.30); + + --odd-color: rgba(100,100,100,.06); + + --menu-selected-background: rgba(0,0,0,.4); + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #38393b; + --side-nav-background: #252628; + + --code-background: #2a2c2f; + + --tablehead-background: #2a2c2f; + + --blockquote-background: #222325; + --blockquote-foreground: #7e8c92; + + --warning-color: #2e1917; + --warning-color-dark: #ad2617; + --warning-color-darker: #f5b1aa; + --note-color: #3b2e04; + --note-color-dark: #f1b602; + --note-color-darker: #ceb670; + --todo-color: #163750; + --todo-color-dark: #1982D2; + --todo-color-darker: #dcf0fa; + --deprecated-color: #2e323b; + --deprecated-color-dark: #738396; + --deprecated-color-darker: #abb0bd; + --bug-color: #2a2536; + --bug-color-dark: #7661b3; + --bug-color-darker: #ae9ed6; + --invariant-color: #303a35; + --invariant-color-dark: #76ce96; + --invariant-color-darker: #cceed5; + + --fragment-background: #282c34; + --fragment-foreground: #dbe4eb; + --fragment-keyword: #cc99cd; + --fragment-keywordtype: #ab99cd; + --fragment-keywordflow: #e08000; + --fragment-token: #7ec699; + --fragment-comment: #999999; + --fragment-link: #98c0e3; + --fragment-preprocessor: #65cabe; + --fragment-linenumber-color: #cccccc; + --fragment-linenumber-background: #35393c; + --fragment-linenumber-border: #1f1f1f; +} + +body { + color: var(--page-foreground-color); + background-color: var(--page-background-color); + font-size: var(--page-font-size); +} + +body, table, div, p, dl, #nav-tree .label, .title, +.sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, +.SelectItem, #MSearchField, .navpath li.navelem a, +.navpath li.navelem a:hover, p.reference, p.definition { + font-family: var(--font-family); +} + +h1, h2, h3, h4, h5 { + margin-top: .9em; + font-weight: 600; + line-height: initial; +} + +p, div, table, dl, p.reference, p.definition { + font-size: var(--page-font-size); +} + +p.reference, p.definition { + color: var(--page-secondary-foreground-color); +} + +a:link, a:visited, a:hover, a:focus, a:active { + color: var(--primary-color) !important; + font-weight: 500; +} + +a.anchor { + scroll-margin-top: var(--spacing-large); + display: block; +} + +/* + Title and top navigation + */ + +#top { + background: var(--header-background); + border-bottom: 1px solid var(--separator-color); +} + +@media screen and (min-width: 768px) { + #top { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + } +} + +#main-nav { + flex-grow: 5; + padding: var(--spacing-small) var(--spacing-medium); +} + +#titlearea { + width: auto; + padding: var(--spacing-medium) var(--spacing-large); + background: none; + color: var(--header-foreground); + border-bottom: none; +} + +@media screen and (max-width: 767px) { + #titlearea { + padding-bottom: var(--spacing-small); + } +} + +#titlearea table tbody tr { + height: auto !important; +} + +#projectname { + font-size: var(--title-font-size); + font-weight: 600; +} + +#projectnumber { + font-family: inherit; + font-size: 60%; +} + +#projectbrief { + font-family: inherit; + font-size: 80%; +} + +#projectlogo { + vertical-align: middle; +} + +#projectlogo img { + max-height: calc(var(--title-font-size) * 2); + margin-right: var(--spacing-small); +} + +.sm-dox, .tabs, .tabs2, .tabs3 { + background: none; + padding: 0; +} + +.tabs, .tabs2, .tabs3 { + border-bottom: 1px solid var(--separator-color); + margin-bottom: -1px; +} + +.main-menu-btn-icon, .main-menu-btn-icon:before, .main-menu-btn-icon:after { + background: var(--page-secondary-foreground-color); +} + +@media screen and (max-width: 767px) { + .sm-dox a span.sub-arrow { + background: var(--code-background); + } + + #main-menu a.has-submenu span.sub-arrow { + color: var(--page-secondary-foreground-color); + border-radius: var(--border-radius-medium); + } + + #main-menu a.has-submenu:hover span.sub-arrow { + color: var(--page-foreground-color); + } +} + +@media screen and (min-width: 768px) { + .sm-dox li, .tablist li { + display: var(--menu-display); + } + + .sm-dox a span.sub-arrow { + border-color: var(--header-foreground) transparent transparent transparent; + } + + .sm-dox a:hover span.sub-arrow { + border-color: var(--menu-focus-foreground) transparent transparent transparent; + } + + .sm-dox ul a span.sub-arrow { + border-color: transparent transparent transparent var(--page-foreground-color); + } + + .sm-dox ul a:hover span.sub-arrow { + border-color: transparent transparent transparent var(--menu-focus-foreground); + } +} + +.sm-dox ul { + background: var(--page-background-color); + box-shadow: var(--box-shadow); + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium) !important; + padding: var(--spacing-small); + animation: ease-out 150ms slideInMenu; +} + +@keyframes slideInMenu { + from { + opacity: 0; + transform: translate(0px, -2px); + } + + to { + opacity: 1; + transform: translate(0px, 0px); + } +} + +.sm-dox ul a { + color: var(--page-foreground-color) !important; + background: var(--page-background-color); + font-size: var(--navigation-font-size); +} + +.sm-dox>li>ul:after { + border-bottom-color: var(--page-background-color) !important; +} + +.sm-dox>li>ul:before { + border-bottom-color: var(--separator-color) !important; +} + +.sm-dox ul a:hover, .sm-dox ul a:active, .sm-dox ul a:focus { + font-size: var(--navigation-font-size) !important; + color: var(--menu-focus-foreground) !important; + text-shadow: none; + background-color: var(--menu-focus-background); + border-radius: var(--border-radius-small) !important; +} + +.sm-dox a, .sm-dox a:focus, .tablist li, .tablist li a, .tablist li.current a { + text-shadow: none; + background: transparent; + background-image: none !important; + color: var(--header-foreground) !important; + font-weight: normal; + font-size: var(--navigation-font-size); + border-radius: var(--border-radius-small) !important; +} + +.sm-dox a:focus { + outline: auto; +} + +.sm-dox a:hover, .sm-dox a:active, .tablist li a:hover { + text-shadow: none; + font-weight: normal; + background: var(--menu-focus-background); + color: var(--menu-focus-foreground) !important; + border-radius: var(--border-radius-small) !important; + font-size: var(--navigation-font-size); +} + +.tablist li.current { + border-radius: var(--border-radius-small); + background: var(--menu-selected-background); +} + +.tablist li { + margin: var(--spacing-small) 0 var(--spacing-small) var(--spacing-small); +} + +.tablist a { + padding: 0 var(--spacing-large); +} + + +/* + Search box + */ + +#MSearchBox { + height: var(--searchbar-height); + background: var(--searchbar-background); + border-radius: var(--searchbar-border-radius); + border: 1px solid var(--separator-color); + overflow: hidden; + width: var(--searchbar-width); + position: relative; + box-shadow: none; + display: block; + margin-top: 0; +} + +/* until Doxygen 1.9.4 */ +.left img#MSearchSelect { + left: 0; + user-select: none; + padding-left: 8px; +} + +/* Doxygen 1.9.5 */ +.left span#MSearchSelect { + left: 0; + user-select: none; + margin-left: 8px; + padding: 0; +} + +.left #MSearchSelect[src$=".png"] { + padding-left: 0 +} + +.SelectionMark { + user-select: none; +} + +.tabs .left #MSearchSelect { + padding-left: 0; +} + +.tabs #MSearchBox { + position: absolute; + right: var(--spacing-medium); +} + +@media screen and (max-width: 767px) { + .tabs #MSearchBox { + position: relative; + right: 0; + margin-left: var(--spacing-medium); + margin-top: 0; + } +} + +#MSearchSelectWindow, #MSearchResultsWindow { + z-index: 9999; +} + +#MSearchBox.MSearchBoxActive { + border-color: var(--primary-color); + box-shadow: inset 0 0 0 1px var(--primary-color); +} + +#main-menu > li:last-child { + margin-right: 0; +} + +@media screen and (max-width: 767px) { + #main-menu > li:last-child { + height: 50px; + } +} + +#MSearchField { + font-size: var(--navigation-font-size); + height: calc(var(--searchbar-height) - 2px); + background: transparent; + width: calc(var(--searchbar-width) - 64px); +} + +.MSearchBoxActive #MSearchField { + color: var(--searchbar-foreground); +} + +#MSearchSelect { + top: calc(calc(var(--searchbar-height) / 2) - 11px); +} + +#MSearchBox span.left, #MSearchBox span.right { + background: none; + background-image: none; +} + +#MSearchBox span.right { + padding-top: calc(calc(var(--searchbar-height) / 2) - 12px); + position: absolute; + right: var(--spacing-small); +} + +.tabs #MSearchBox span.right { + top: calc(calc(var(--searchbar-height) / 2) - 12px); +} + +@keyframes slideInSearchResults { + from { + opacity: 0; + transform: translate(0, 15px); + } + + to { + opacity: 1; + transform: translate(0, 20px); + } +} + +#MSearchResultsWindow { + left: auto !important; + right: var(--spacing-medium); + border-radius: var(--border-radius-large); + border: 1px solid var(--separator-color); + transform: translate(0, 20px); + box-shadow: var(--box-shadow); + animation: ease-out 280ms slideInSearchResults; + background: var(--page-background-color); +} + +iframe#MSearchResults { + margin: 4px; +} + +iframe { + color-scheme: normal; +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) iframe#MSearchResults { + filter: invert() hue-rotate(180deg); + } +} + +html.dark-mode iframe#MSearchResults { + filter: invert() hue-rotate(180deg); +} + +#MSearchResults .SRPage { + background-color: transparent; +} + +#MSearchResults .SRPage .SREntry { + font-size: 10pt; + padding: var(--spacing-small) var(--spacing-medium); +} + +#MSearchSelectWindow { + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + box-shadow: var(--box-shadow); + background: var(--page-background-color); + padding-top: var(--spacing-small); + padding-bottom: var(--spacing-small); +} + +#MSearchSelectWindow a.SelectItem { + font-size: var(--navigation-font-size); + line-height: var(--content-line-height); + margin: 0 var(--spacing-small); + border-radius: var(--border-radius-small); + color: var(--page-foreground-color) !important; + font-weight: normal; +} + +#MSearchSelectWindow a.SelectItem:hover { + background: var(--menu-focus-background); + color: var(--menu-focus-foreground) !important; +} + +@media screen and (max-width: 767px) { + #MSearchBox { + margin-top: var(--spacing-medium); + margin-bottom: var(--spacing-medium); + width: calc(100vw - 30px); + } + + #main-menu > li:last-child { + float: none !important; + } + + #MSearchField { + width: calc(100vw - 110px); + } + + @keyframes slideInSearchResultsMobile { + from { + opacity: 0; + transform: translate(0, 15px); + } + + to { + opacity: 1; + transform: translate(0, 20px); + } + } + + #MSearchResultsWindow { + left: var(--spacing-medium) !important; + right: var(--spacing-medium); + overflow: auto; + transform: translate(0, 20px); + animation: ease-out 280ms slideInSearchResultsMobile; + width: auto !important; + } + + /* + * Overwrites for fixing the searchbox on mobile in doxygen 1.9.2 + */ + label.main-menu-btn ~ #searchBoxPos1 { + top: 3px !important; + right: 6px !important; + left: 45px; + display: flex; + } + + label.main-menu-btn ~ #searchBoxPos1 > #MSearchBox { + margin-top: 0; + margin-bottom: 0; + flex-grow: 2; + float: left; + } +} + +/* + Tree view + */ + +#side-nav { + padding: 0 !important; + background: var(--side-nav-background); +} + +@media screen and (max-width: 767px) { + #side-nav { + display: none; + } + + #doc-content { + margin-left: 0 !important; + } +} + +#nav-tree { + background: transparent; +} + +#nav-tree .label { + font-size: var(--navigation-font-size); +} + +#nav-tree .item { + height: var(--tree-item-height); + line-height: var(--tree-item-height); +} + +#nav-sync { + bottom: 12px; + right: 12px; + top: auto !important; + user-select: none; +} + +#nav-tree .selected { + text-shadow: none; + background-image: none; + background-color: transparent; + position: relative; +} + +#nav-tree .selected::after { + content: ""; + position: absolute; + top: 1px; + bottom: 1px; + left: 0; + width: 4px; + border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; + background: var(--primary-color); +} + + +#nav-tree a { + color: var(--side-nav-foreground) !important; + font-weight: normal; +} + +#nav-tree a:focus { + outline-style: auto; +} + +#nav-tree .arrow { + opacity: var(--side-nav-arrow-opacity); +} + +.arrow { + color: inherit; + cursor: pointer; + font-size: 45%; + vertical-align: middle; + margin-right: 2px; + font-family: serif; + height: auto; + text-align: right; +} + +#nav-tree div.item:hover .arrow, #nav-tree a:focus .arrow { + opacity: var(--side-nav-arrow-hover-opacity); +} + +#nav-tree .selected a { + color: var(--primary-color) !important; + font-weight: bolder; + font-weight: 600; +} + +.ui-resizable-e { + background: var(--separator-color); + width: 1px; +} + +/* + Contents + */ + +div.header { + border-bottom: 1px solid var(--separator-color); + background-color: var(--page-background-color); + background-image: none; +} + +@media screen and (min-width: 1000px) { + #doc-content > div > div.contents, + .PageDoc > div.contents { + display: flex; + flex-direction: row-reverse; + flex-wrap: nowrap; + align-items: flex-start; + } + + div.contents .textblock { + min-width: 200px; + flex-grow: 1; + } +} + +div.contents, div.header .title, div.header .summary { + max-width: var(--content-maxwidth); +} + +div.contents, div.header .title { + line-height: initial; + margin: calc(var(--spacing-medium) + .2em) auto var(--spacing-medium) auto; +} + +div.header .summary { + margin: var(--spacing-medium) auto 0 auto; +} + +div.headertitle { + padding: 0; +} + +div.header .title { + font-weight: 600; + font-size: 225%; + padding: var(--spacing-medium) var(--spacing-large); + word-break: break-word; +} + +div.header .summary { + width: auto; + display: block; + float: none; + padding: 0 var(--spacing-large); +} + +td.memSeparator { + border-color: var(--separator-color); +} + +span.mlabel { + background: var(--primary-color); + border: none; + padding: 4px 9px; + border-radius: 12px; + margin-right: var(--spacing-medium); +} + +span.mlabel:last-of-type { + margin-right: 2px; +} + +div.contents { + padding: 0 var(--spacing-large); +} + +div.contents p, div.contents li { + line-height: var(--content-line-height); +} + +div.contents div.dyncontent { + margin: var(--spacing-medium) 0; +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) div.contents div.dyncontent img, + html:not(.light-mode) div.contents center img, + html:not(.light-mode) div.contents > table img, + html:not(.light-mode) div.contents div.dyncontent iframe, + html:not(.light-mode) div.contents center iframe, + html:not(.light-mode) div.contents table iframe { + filter: hue-rotate(180deg) invert(); + } +} + +html.dark-mode div.contents div.dyncontent img, +html.dark-mode div.contents center img, +html.dark-mode div.contents > table img, +html.dark-mode div.contents div.dyncontent iframe, +html.dark-mode div.contents center iframe, +html.dark-mode div.contents table iframe { + filter: hue-rotate(180deg) invert(); +} + +h2.groupheader { + border-bottom: 0px; + color: var(--page-foreground-color); + box-shadow: + 100px 0 var(--page-background-color), + -100px 0 var(--page-background-color), + 100px 0.75px var(--separator-color), + -100px 0.75px var(--separator-color), + 500px 0 var(--page-background-color), + -500px 0 var(--page-background-color), + 500px 0.75px var(--separator-color), + -500px 0.75px var(--separator-color), + 900px 0 var(--page-background-color), + -900px 0 var(--page-background-color), + 900px 0.75px var(--separator-color), + -900px 0.75px var(--separator-color), + 1400px 0 var(--page-background-color), + -1400px 0 var(--page-background-color), + 1400px 0.75px var(--separator-color), + -1400px 0.75px var(--separator-color), + 1900px 0 var(--page-background-color), + -1900px 0 var(--page-background-color), + 1900px 0.75px var(--separator-color), + -1900px 0.75px var(--separator-color); +} + +blockquote { + margin: 0 var(--spacing-medium) 0 var(--spacing-medium); + padding: var(--spacing-small) var(--spacing-large); + background: var(--blockquote-background); + color: var(--blockquote-foreground); + border-left: 0; + overflow: visible; + border-radius: var(--border-radius-medium); + overflow: visible; + position: relative; +} + +blockquote::before, blockquote::after { + font-weight: bold; + font-family: serif; + font-size: 360%; + opacity: .15; + position: absolute; +} + +blockquote::before { + content: "“"; + left: -10px; + top: 4px; +} + +blockquote::after { + content: "”"; + right: -8px; + bottom: -25px; +} + +blockquote p { + margin: var(--spacing-small) 0 var(--spacing-medium) 0; +} +.paramname { + font-weight: 600; + color: var(--primary-dark-color); +} + +.paramname > code { + border: 0; +} + +table.params .paramname { + font-weight: 600; + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); + padding-right: var(--spacing-small); + line-height: var(--table-line-height); +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px var(--primary-light-color); +} + +.alphachar a { + color: var(--page-foreground-color); +} + +/* + Table of Contents + */ + +div.contents .toc { + max-height: var(--toc-max-height); + min-width: var(--toc-width); + border: 0; + border-left: 1px solid var(--separator-color); + border-radius: 0; + background-color: transparent; + box-shadow: none; + position: sticky; + top: var(--toc-sticky-top); + padding: 0 var(--spacing-large); + margin: var(--spacing-small) 0 var(--spacing-large) var(--spacing-large); +} + +div.toc h3 { + color: var(--toc-foreground); + font-size: var(--navigation-font-size); + margin: var(--spacing-large) 0 var(--spacing-medium) 0; +} + +div.toc li { + padding: 0; + background: none; + line-height: var(--toc-font-size); + margin: var(--toc-font-size) 0 0 0; +} + +div.toc li::before { + display: none; +} + +div.toc ul { + margin-top: 0 +} + +div.toc li a { + font-size: var(--toc-font-size); + color: var(--page-foreground-color) !important; + text-decoration: none; +} + +div.toc li a:hover, div.toc li a.active { + color: var(--primary-color) !important; +} + +div.toc li a.aboveActive { + color: var(--page-secondary-foreground-color) !important; +} + + +@media screen and (max-width: 999px) { + div.contents .toc { + max-height: 45vh; + float: none; + width: auto; + margin: 0 0 var(--spacing-medium) 0; + position: relative; + top: 0; + position: relative; + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + background-color: var(--toc-background); + box-shadow: var(--box-shadow); + } + + div.contents .toc.interactive { + max-height: calc(var(--navigation-font-size) + 2 * var(--spacing-large)); + overflow: hidden; + } + + div.contents .toc > h3 { + -webkit-tap-highlight-color: transparent; + cursor: pointer; + position: sticky; + top: 0; + background-color: var(--toc-background); + margin: 0; + padding: var(--spacing-large) 0; + display: block; + } + + div.contents .toc.interactive > h3::before { + content: ""; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid var(--primary-color); + display: inline-block; + margin-right: var(--spacing-small); + margin-bottom: calc(var(--navigation-font-size) / 4); + transform: rotate(-90deg); + transition: transform 0.25s ease-out; + } + + div.contents .toc.interactive.open > h3::before { + transform: rotate(0deg); + } + + div.contents .toc.interactive.open { + max-height: 45vh; + overflow: auto; + transition: max-height 0.2s ease-in-out; + } + + div.contents .toc a, div.contents .toc a.active { + color: var(--primary-color) !important; + } + + div.contents .toc a:hover { + text-decoration: underline; + } +} + +/* + Code & Fragments + */ + +code, div.fragment, pre.fragment { + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + overflow: hidden; +} + +code { + display: inline; + background: var(--code-background); + color: var(--code-foreground); + padding: 2px 6px; +} + +div.fragment, pre.fragment { + margin: var(--spacing-medium) 0; + padding: calc(var(--spacing-large) - (var(--spacing-large) / 6)) var(--spacing-large); + background: var(--fragment-background); + color: var(--fragment-foreground); + overflow-x: auto; +} + +@media screen and (max-width: 767px) { + div.fragment, pre.fragment { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: 0; + } + + .contents > div.fragment, + .textblock > div.fragment, + .textblock > pre.fragment, + .contents > .doxygen-awesome-fragment-wrapper > div.fragment, + .textblock > .doxygen-awesome-fragment-wrapper > div.fragment, + .textblock > .doxygen-awesome-fragment-wrapper > pre.fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-large)); + border-radius: 0; + border-left: 0; + } + + .textblock li > .fragment, + .textblock li > .doxygen-awesome-fragment-wrapper > .fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-large)); + } + + .memdoc li > .fragment, + .memdoc li > .doxygen-awesome-fragment-wrapper > .fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); + } + + .textblock ul, .memdoc ul { + overflow: initial; + } + + .memdoc > div.fragment, + .memdoc > pre.fragment, + dl dd > div.fragment, + dl dd pre.fragment, + .memdoc > .doxygen-awesome-fragment-wrapper > div.fragment, + .memdoc > .doxygen-awesome-fragment-wrapper > pre.fragment, + dl dd > .doxygen-awesome-fragment-wrapper > div.fragment, + dl dd .doxygen-awesome-fragment-wrapper > pre.fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); + border-radius: 0; + border-left: 0; + } +} + +code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size) !important; +} + +div.line:after { + margin-right: var(--spacing-medium); +} + +div.fragment .line, pre.fragment { + white-space: pre; + word-wrap: initial; + line-height: var(--fragment-lineheight); +} + +div.fragment span.keyword { + color: var(--fragment-keyword); +} + +div.fragment span.keywordtype { + color: var(--fragment-keywordtype); +} + +div.fragment span.keywordflow { + color: var(--fragment-keywordflow); +} + +div.fragment span.stringliteral { + color: var(--fragment-token) +} + +div.fragment span.comment { + color: var(--fragment-comment); +} + +div.fragment a.code { + color: var(--fragment-link) !important; +} + +div.fragment span.preprocessor { + color: var(--fragment-preprocessor); +} + +div.fragment span.lineno { + display: inline-block; + width: 27px; + border-right: none; + background: var(--fragment-linenumber-background); + color: var(--fragment-linenumber-color); +} + +div.fragment span.lineno a { + background: none; + color: var(--fragment-link) !important; +} + +div.fragment .line:first-child .lineno { + box-shadow: -999999px 0px 0 999999px var(--fragment-linenumber-background), -999998px 0px 0 999999px var(--fragment-linenumber-border); +} + +div.line { + border-radius: var(--border-radius-small); +} + +div.line.glow { + background-color: var(--primary-light-color); + box-shadow: none; +} + +/* + dl warning, attention, note, deprecated, bug, ... + */ + +dl.bug dt a, dl.deprecated dt a, dl.todo dt a { + font-weight: bold !important; +} + +dl.warning, dl.attention, dl.note, dl.deprecated, dl.bug, dl.invariant, dl.pre, dl.todo, dl.remark { + padding: var(--spacing-medium); + margin: var(--spacing-medium) 0; + color: var(--page-background-color); + overflow: hidden; + margin-left: 0; + border-radius: var(--border-radius-small); +} + +dl.section dd { + margin-bottom: 2px; +} + +dl.warning, dl.attention { + background: var(--warning-color); + border-left: 8px solid var(--warning-color-dark); + color: var(--warning-color-darker); +} + +dl.warning dt, dl.attention dt { + color: var(--warning-color-dark); +} + +dl.note, dl.remark { + background: var(--note-color); + border-left: 8px solid var(--note-color-dark); + color: var(--note-color-darker); +} + +dl.note dt, dl.remark dt { + color: var(--note-color-dark); +} + +dl.todo { + background: var(--todo-color); + border-left: 8px solid var(--todo-color-dark); + color: var(--todo-color-darker); +} + +dl.todo dt { + color: var(--todo-color-dark); +} + +dl.bug dt a { + color: var(--todo-color-dark) !important; +} + +dl.bug { + background: var(--bug-color); + border-left: 8px solid var(--bug-color-dark); + color: var(--bug-color-darker); +} + +dl.bug dt a { + color: var(--bug-color-dark) !important; +} + +dl.deprecated { + background: var(--deprecated-color); + border-left: 8px solid var(--deprecated-color-dark); + color: var(--deprecated-color-darker); +} + +dl.deprecated dt a { + color: var(--deprecated-color-dark) !important; +} + +dl.section dd, dl.bug dd, dl.deprecated dd, dl.todo dd { + margin-inline-start: 0px; +} + +dl.invariant, dl.pre { + background: var(--invariant-color); + border-left: 8px solid var(--invariant-color-dark); + color: var(--invariant-color-darker); +} + +dl.invariant dt, dl.pre dt { + color: var(--invariant-color-dark); +} + +/* + memitem + */ + +div.memdoc, div.memproto, h2.memtitle { + box-shadow: none; + background-image: none; + border: none; +} + +div.memdoc { + padding: 0 var(--spacing-medium); + background: var(--page-background-color); +} + +h2.memtitle, div.memitem { + border: 1px solid var(--separator-color); + box-shadow: var(--box-shadow); +} + +h2.memtitle { + box-shadow: 0px var(--spacing-medium) 0 -1px var(--fragment-background), var(--box-shadow); +} + +div.memitem { + transition: none; +} + +div.memproto, h2.memtitle { + background: var(--fragment-background); +} + +h2.memtitle { + font-weight: 500; + font-size: var(--memtitle-font-size); + font-family: var(--font-family-monospace); + border-bottom: none; + border-top-left-radius: var(--border-radius-medium); + border-top-right-radius: var(--border-radius-medium); + word-break: break-all; + position: relative; +} + +h2.memtitle:after { + content: ""; + display: block; + background: var(--fragment-background); + height: var(--spacing-medium); + bottom: calc(0px - var(--spacing-medium)); + left: 0; + right: -14px; + position: absolute; + border-top-right-radius: var(--border-radius-medium); +} + +h2.memtitle > span.permalink { + font-size: inherit; +} + +h2.memtitle > span.permalink > a { + text-decoration: none; + padding-left: 3px; + margin-right: -4px; + user-select: none; + display: inline-block; + margin-top: -6px; +} + +h2.memtitle > span.permalink > a:hover { + color: var(--primary-dark-color) !important; +} + +a:target + h2.memtitle, a:target + h2.memtitle + div.memitem { + border-color: var(--primary-light-color); +} + +div.memitem { + border-top-right-radius: var(--border-radius-medium); + border-bottom-right-radius: var(--border-radius-medium); + border-bottom-left-radius: var(--border-radius-medium); + overflow: hidden; + display: block !important; +} + +div.memdoc { + border-radius: 0; +} + +div.memproto { + border-radius: 0 var(--border-radius-small) 0 0; + overflow: auto; + border-bottom: 1px solid var(--separator-color); + padding: var(--spacing-medium); + margin-bottom: -1px; +} + +div.memtitle { + border-top-right-radius: var(--border-radius-medium); + border-top-left-radius: var(--border-radius-medium); +} + +div.memproto table.memname { + font-family: var(--font-family-monospace); + color: var(--page-foreground-color); + font-size: var(--memname-font-size); + text-shadow: none; +} + +div.memproto div.memtemplate { + font-family: var(--font-family-monospace); + color: var(--primary-dark-color); + font-size: var(--memname-font-size); + margin-left: 2px; + text-shadow: none; +} + +table.mlabels, table.mlabels > tbody { + display: block; +} + +td.mlabels-left { + width: auto; +} + +td.mlabels-right { + margin-top: 3px; + position: sticky; + left: 0; +} + +table.mlabels > tbody > tr:first-child { + display: flex; + justify-content: space-between; + flex-wrap: wrap; +} + +.memname, .memitem span.mlabels { + margin: 0 +} + +/* + reflist + */ + +dl.reflist { + box-shadow: var(--box-shadow); + border-radius: var(--border-radius-medium); + border: 1px solid var(--separator-color); + overflow: hidden; + padding: 0; +} + + +dl.reflist dt, dl.reflist dd { + box-shadow: none; + text-shadow: none; + background-image: none; + border: none; + padding: 12px; +} + + +dl.reflist dt { + font-weight: 500; + border-radius: 0; + background: var(--code-background); + border-bottom: 1px solid var(--separator-color); + color: var(--page-foreground-color) +} + + +dl.reflist dd { + background: none; +} + +/* + Table + */ + +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname), +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody { + display: inline-block; + max-width: 100%; +} + +.contents > table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname):not(.classindex) { + margin-left: calc(0px - var(--spacing-large)); + margin-right: calc(0px - var(--spacing-large)); + max-width: calc(100% + 2 * var(--spacing-large)); +} + +table.fieldtable, +table.markdownTable tbody, +table.doxtable tbody { + border: none; + margin: var(--spacing-medium) 0; + box-shadow: 0 0 0 1px var(--separator-color); + border-radius: var(--border-radius-small); +} + +table.doxtable caption { + display: block; +} + +table.fieldtable { + border-collapse: collapse; + width: 100%; +} + +th.markdownTableHeadLeft, +th.markdownTableHeadRight, +th.markdownTableHeadCenter, +th.markdownTableHeadNone, +table.doxtable th { + background: var(--tablehead-background); + color: var(--tablehead-foreground); + font-weight: 600; + font-size: var(--page-font-size); +} + +th.markdownTableHeadLeft:first-child, +th.markdownTableHeadRight:first-child, +th.markdownTableHeadCenter:first-child, +th.markdownTableHeadNone:first-child, +table.doxtable tr th:first-child { + border-top-left-radius: var(--border-radius-small); +} + +th.markdownTableHeadLeft:last-child, +th.markdownTableHeadRight:last-child, +th.markdownTableHeadCenter:last-child, +th.markdownTableHeadNone:last-child, +table.doxtable tr th:last-child { + border-top-right-radius: var(--border-radius-small); +} + +table.markdownTable td, +table.markdownTable th, +table.fieldtable td, +table.fieldtable th, +table.doxtable td, +table.doxtable th { + border: 1px solid var(--separator-color); + padding: var(--spacing-small) var(--spacing-medium); +} + +table.markdownTable td:last-child, +table.markdownTable th:last-child, +table.fieldtable td:last-child, +table.fieldtable th:last-child, +table.doxtable td:last-child, +table.doxtable th:last-child { + border-right: none; +} + +table.markdownTable td:first-child, +table.markdownTable th:first-child, +table.fieldtable td:first-child, +table.fieldtable th:first-child, +table.doxtable td:first-child, +table.doxtable th:first-child { + border-left: none; +} + +table.markdownTable tr:first-child td, +table.markdownTable tr:first-child th, +table.fieldtable tr:first-child td, +table.fieldtable tr:first-child th, +table.doxtable tr:first-child td, +table.doxtable tr:first-child th { + border-top: none; +} + +table.markdownTable tr:last-child td, +table.markdownTable tr:last-child th, +table.fieldtable tr:last-child td, +table.fieldtable tr:last-child th, +table.doxtable tr:last-child td, +table.doxtable tr:last-child th { + border-bottom: none; +} + +table.markdownTable tr, table.doxtable tr { + border-bottom: 1px solid var(--separator-color); +} + +table.markdownTable tr:last-child, table.doxtable tr:last-child { + border-bottom: none; +} + +table.fieldtable th { + font-size: var(--page-font-size); + font-weight: 600; + background-image: none; + background-color: var(--tablehead-background); + color: var(--tablehead-foreground); +} + +table.fieldtable td.fieldtype, .fieldtable td.fieldname, .fieldtable td.fielddoc, .fieldtable th { + border-bottom: 1px solid var(--separator-color); + border-right: 1px solid var(--separator-color); +} + +table.fieldtable tr:last-child td:first-child { + border-bottom-left-radius: var(--border-radius-small); +} + +table.fieldtable tr:last-child td:last-child { + border-bottom-right-radius: var(--border-radius-small); +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: var(--primary-light-color); + box-shadow: none; +} + +table.memberdecls { + display: block; + -webkit-tap-highlight-color: transparent; +} + +table.memberdecls tr[class^='memitem'] { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); +} + +table.memberdecls tr[class^='memitem'] .memTemplParams { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); + color: var(--primary-dark-color); + white-space: normal; +} + +table.memberdecls .memItemLeft, +table.memberdecls .memItemRight, +table.memberdecls .memTemplItemLeft, +table.memberdecls .memTemplItemRight, +table.memberdecls .memTemplParams { + transition: none; + padding-top: var(--spacing-small); + padding-bottom: var(--spacing-small); + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + background-color: var(--fragment-background); +} + +table.memberdecls .memTemplItemLeft, +table.memberdecls .memTemplItemRight { + padding-top: 2px; +} + +table.memberdecls .memTemplParams { + border-bottom: 0; + border-left: 1px solid var(--separator-color); + border-right: 1px solid var(--separator-color); + border-radius: var(--border-radius-small) var(--border-radius-small) 0 0; + padding-bottom: var(--spacing-small); +} + +table.memberdecls .memTemplItemLeft { + border-radius: 0 0 0 var(--border-radius-small); + border-left: 1px solid var(--separator-color); + border-top: 0; +} + +table.memberdecls .memTemplItemRight { + border-radius: 0 0 var(--border-radius-small) 0; + border-right: 1px solid var(--separator-color); + padding-left: 0; + border-top: 0; +} + +table.memberdecls .memItemLeft { + border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); + border-left: 1px solid var(--separator-color); + padding-left: var(--spacing-medium); + padding-right: 0; +} + +table.memberdecls .memItemRight { + border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; + border-right: 1px solid var(--separator-color); + padding-right: var(--spacing-medium); + padding-left: 0; + +} + +table.memberdecls .mdescLeft, table.memberdecls .mdescRight { + background: none; + color: var(--page-foreground-color); + padding: var(--spacing-small) 0; +} + +table.memberdecls .memItemLeft, +table.memberdecls .memTemplItemLeft { + padding-right: var(--spacing-medium); +} + +table.memberdecls .memSeparator { + background: var(--page-background-color); + height: var(--spacing-large); + border: 0; + transition: none; +} + +table.memberdecls .groupheader { + margin-bottom: var(--spacing-large); +} + +table.memberdecls .inherit_header td { + padding: 0 0 var(--spacing-medium) 0; + text-indent: -12px; + color: var(--page-secondary-foreground-color); +} + +table.memberdecls img[src="closed.png"], +table.memberdecls img[src="open.png"], +div.dynheader img[src="open.png"], +div.dynheader img[src="closed.png"] { + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid var(--primary-color); + margin-top: 8px; + display: block; + float: left; + margin-left: -10px; + transition: transform 0.25s ease-out; +} + +table.memberdecls img { + margin-right: 10px; +} + +table.memberdecls img[src="closed.png"], +div.dynheader img[src="closed.png"] { + transform: rotate(-90deg); + +} + +.compoundTemplParams { + font-family: var(--font-family-monospace); + color: var(--primary-dark-color); + font-size: var(--code-font-size); +} + +@media screen and (max-width: 767px) { + + table.memberdecls .memItemLeft, + table.memberdecls .memItemRight, + table.memberdecls .mdescLeft, + table.memberdecls .mdescRight, + table.memberdecls .memTemplItemLeft, + table.memberdecls .memTemplItemRight, + table.memberdecls .memTemplParams { + display: block; + text-align: left; + padding-left: var(--spacing-large); + margin: 0 calc(0px - var(--spacing-large)) 0 calc(0px - var(--spacing-large)); + border-right: none; + border-left: none; + border-radius: 0; + white-space: normal; + } + + table.memberdecls .memItemLeft, + table.memberdecls .mdescLeft, + table.memberdecls .memTemplItemLeft { + border-bottom: 0; + padding-bottom: 0; + } + + table.memberdecls .memTemplItemLeft { + padding-top: 0; + } + + table.memberdecls .mdescLeft { + margin-bottom: calc(0px - var(--page-font-size)); + } + + table.memberdecls .memItemRight, + table.memberdecls .mdescRight, + table.memberdecls .memTemplItemRight { + border-top: 0; + padding-top: 0; + padding-right: var(--spacing-large); + overflow-x: auto; + } + + table.memberdecls tr[class^='memitem']:not(.inherit) { + display: block; + width: calc(100vw - 2 * var(--spacing-large)); + } + + table.memberdecls .mdescRight { + color: var(--page-foreground-color); + } + + table.memberdecls tr.inherit { + visibility: hidden; + } + + table.memberdecls tr[style="display: table-row;"] { + display: block !important; + visibility: visible; + width: calc(100vw - 2 * var(--spacing-large)); + animation: fade .5s; + } + + @keyframes fade { + 0% { + opacity: 0; + max-height: 0; + } + + 100% { + opacity: 1; + max-height: 200px; + } + } +} + + +/* + Horizontal Rule + */ + +hr { + margin-top: var(--spacing-large); + margin-bottom: var(--spacing-large); + height: 1px; + background-color: var(--separator-color); + border: 0; +} + +.contents hr { + box-shadow: 100px 0 0 var(--separator-color), + -100px 0 0 var(--separator-color), + 500px 0 0 var(--separator-color), + -500px 0 0 var(--separator-color), + 1500px 0 0 var(--separator-color), + -1500px 0 0 var(--separator-color), + 2000px 0 0 var(--separator-color), + -2000px 0 0 var(--separator-color); +} + +.contents img, .contents .center, .contents center, .contents div.image object { + max-width: 100%; + overflow: auto; +} + +@media screen and (max-width: 767px) { + .contents .dyncontent > .center, .contents > center { + margin-left: calc(0px - var(--spacing-large)); + margin-right: calc(0px - var(--spacing-large)); + max-width: calc(100% + 2 * var(--spacing-large)); + } +} + +/* + Directories + */ +div.directory { + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + width: auto; +} + +table.directory { + font-family: var(--font-family); + font-size: var(--page-font-size); + font-weight: normal; + width: 100%; +} + +table.directory td.entry, table.directory td.desc { + padding: calc(var(--spacing-small) / 2) var(--spacing-small); + line-height: var(--table-line-height); +} + +table.directory tr.even td:last-child { + border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; +} + +table.directory tr.even td:first-child { + border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); +} + +table.directory tr.even:last-child td:last-child { + border-radius: 0 var(--border-radius-small) 0 0; +} + +table.directory tr.even:last-child td:first-child { + border-radius: var(--border-radius-small) 0 0 0; +} + +table.directory td.desc { + min-width: 250px; +} + +table.directory tr.even { + background-color: var(--odd-color); +} + +table.directory tr.odd { + background-color: transparent; +} + +.icona { + width: auto; + height: auto; + margin: 0 var(--spacing-small); +} + +.icon { + background: var(--primary-color); + border-radius: var(--border-radius-small); + font-size: var(--page-font-size); + padding: calc(var(--page-font-size) / 5); + line-height: var(--page-font-size); + transform: scale(0.8); + height: auto; + width: var(--page-font-size); + user-select: none; +} + +.iconfopen, .icondoc, .iconfclosed { + background-position: center; + margin-bottom: 0; + height: var(--table-line-height); +} + +.icondoc { + filter: saturate(0.2); +} + +@media screen and (max-width: 767px) { + div.directory { + margin-left: calc(0px - var(--spacing-large)); + margin-right: calc(0px - var(--spacing-large)); + } +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) .iconfopen, html:not(.light-mode) .iconfclosed { + filter: hue-rotate(180deg) invert(); + } +} + +html.dark-mode .iconfopen, html.dark-mode .iconfclosed { + filter: hue-rotate(180deg) invert(); +} + +/* + Class list + */ + +.classindex dl.odd { + background: var(--odd-color); + border-radius: var(--border-radius-small); +} + +.classindex dl.even { + background-color: transparent; +} + +/* + Class Index Doxygen 1.8 +*/ + +table.classindex { + margin-left: 0; + margin-right: 0; + width: 100%; +} + +table.classindex table div.ah { + background-image: none; + background-color: initial; + border-color: var(--separator-color); + color: var(--page-foreground-color); + box-shadow: var(--box-shadow); + border-radius: var(--border-radius-large); + padding: var(--spacing-small); +} + +div.qindex { + background-color: var(--odd-color); + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + padding: var(--spacing-small) 0; +} + +/* + Footer and nav-path + */ + +#nav-path { + width: 100%; +} + +#nav-path ul { + background-image: none; + background: var(--page-background-color); + border: none; + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + border-bottom: 0; + box-shadow: 0 0.75px 0 var(--separator-color); + font-size: var(--navigation-font-size); +} + +img.footer { + width: 60px; +} + +.navpath li.footer { + color: var(--page-secondary-foreground-color); +} + +address.footer { + color: var(--page-secondary-foreground-color); + margin-bottom: var(--spacing-large); +} + +#nav-path li.navelem { + background-image: none; + display: flex; + align-items: center; +} + +.navpath li.navelem a { + text-shadow: none; + display: inline-block; + color: var(--primary-color) !important; +} + +.navpath li.navelem b { + color: var(--primary-dark-color); + font-weight: 500; +} + +li.navelem { + padding: 0; + margin-left: -8px; +} + +li.navelem:first-child { + margin-left: var(--spacing-large); +} + +li.navelem:first-child:before { + display: none; +} + +#nav-path li.navelem:after { + content: ''; + border: 5px solid var(--page-background-color); + border-bottom-color: transparent; + border-right-color: transparent; + border-top-color: transparent; + transform: translateY(-1px) scaleY(4.2); + z-index: 10; + margin-left: 6px; +} + +#nav-path li.navelem:before { + content: ''; + border: 5px solid var(--separator-color); + border-bottom-color: transparent; + border-right-color: transparent; + border-top-color: transparent; + transform: translateY(-1px) scaleY(3.2); + margin-right: var(--spacing-small); +} + +.navpath li.navelem a:hover { + color: var(--primary-color); +} + +/* + Scrollbars for Webkit +*/ + +#nav-tree::-webkit-scrollbar, +div.fragment::-webkit-scrollbar, +pre.fragment::-webkit-scrollbar, +div.memproto::-webkit-scrollbar, +.contents center::-webkit-scrollbar, +.contents .center::-webkit-scrollbar, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody::-webkit-scrollbar, +div.contents .toc::-webkit-scrollbar { + background: transparent; + width: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); + height: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); +} + +#nav-tree::-webkit-scrollbar-thumb, +div.fragment::-webkit-scrollbar-thumb, +pre.fragment::-webkit-scrollbar-thumb, +div.memproto::-webkit-scrollbar-thumb, +.contents center::-webkit-scrollbar-thumb, +.contents .center::-webkit-scrollbar-thumb, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody::-webkit-scrollbar-thumb, +div.contents .toc::-webkit-scrollbar-thumb { + background-color: transparent; + border: var(--webkit-scrollbar-padding) solid transparent; + border-radius: calc(var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); + background-clip: padding-box; +} + +#nav-tree:hover::-webkit-scrollbar-thumb, +div.fragment:hover::-webkit-scrollbar-thumb, +pre.fragment:hover::-webkit-scrollbar-thumb, +div.memproto:hover::-webkit-scrollbar-thumb, +.contents center:hover::-webkit-scrollbar-thumb, +.contents .center:hover::-webkit-scrollbar-thumb, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody:hover::-webkit-scrollbar-thumb, +div.contents .toc:hover::-webkit-scrollbar-thumb { + background-color: var(--webkit-scrollbar-color); +} + +#nav-tree::-webkit-scrollbar-track, +div.fragment::-webkit-scrollbar-track, +pre.fragment::-webkit-scrollbar-track, +div.memproto::-webkit-scrollbar-track, +.contents center::-webkit-scrollbar-track, +.contents .center::-webkit-scrollbar-track, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody::-webkit-scrollbar-track, +div.contents .toc::-webkit-scrollbar-track { + background: transparent; +} + +#nav-tree::-webkit-scrollbar-corner { + background-color: var(--side-nav-background); +} + +#nav-tree, +div.fragment, +pre.fragment, +div.memproto, +.contents center, +.contents .center, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody, +div.contents .toc { + overflow-x: auto; + overflow-x: overlay; +} + +#nav-tree { + overflow-x: auto; + overflow-y: auto; + overflow-y: overlay; +} + +/* + Scrollbars for Firefox +*/ + +#nav-tree, +div.fragment, +pre.fragment, +div.memproto, +.contents center, +.contents .center, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody, +div.contents .toc { + scrollbar-width: thin; +} + +/* + Optional Dark mode toggle button +*/ + +doxygen-awesome-dark-mode-toggle { + display: inline-block; + margin: 0 0 0 var(--spacing-small); + padding: 0; + width: var(--searchbar-height); + height: var(--searchbar-height); + background: none; + border: none; + border-radius: var(--searchbar-height); + vertical-align: middle; + text-align: center; + line-height: var(--searchbar-height); + font-size: 22px; + display: flex; + align-items: center; + justify-content: center; + user-select: none; + cursor: pointer; +} + +doxygen-awesome-dark-mode-toggle > svg { + transition: transform .1s ease-in-out; +} + +doxygen-awesome-dark-mode-toggle:active > svg { + transform: scale(.5); +} + +doxygen-awesome-dark-mode-toggle:hover { + background-color: rgba(0,0,0,.03); +} + +html.dark-mode doxygen-awesome-dark-mode-toggle:hover { + background-color: rgba(0,0,0,.18); +} + +/* + Optional fragment copy button +*/ +.doxygen-awesome-fragment-wrapper { + position: relative; +} + +doxygen-awesome-fragment-copy-button { + opacity: 0; + background: var(--fragment-background); + width: 28px; + height: 28px; + position: absolute; + right: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); + top: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); + border: 1px solid var(--fragment-foreground); + cursor: pointer; + border-radius: var(--border-radius-small); + display: flex; + justify-content: center; + align-items: center; +} + +.doxygen-awesome-fragment-wrapper:hover doxygen-awesome-fragment-copy-button, doxygen-awesome-fragment-copy-button.success { + opacity: .28; +} + +doxygen-awesome-fragment-copy-button:hover, doxygen-awesome-fragment-copy-button.success { + opacity: 1 !important; +} + +doxygen-awesome-fragment-copy-button:active:not([class~=success]) svg { + transform: scale(.91); +} + +doxygen-awesome-fragment-copy-button svg { + fill: var(--fragment-foreground); + width: 18px; + height: 18px; +} + +doxygen-awesome-fragment-copy-button.success svg { + fill: rgb(14, 168, 14); +} + +doxygen-awesome-fragment-copy-button.success { + border-color: rgb(14, 168, 14); +} + +@media screen and (max-width: 767px) { + .textblock > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .textblock li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .memdoc li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .memdoc > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + dl dd > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button { + right: 0; + } +} + +/* + Optional paragraph link button +*/ + +a.anchorlink { + font-size: 90%; + margin-left: var(--spacing-small); + color: var(--page-foreground-color) !important; + text-decoration: none; + opacity: .15; + display: none; + transition: opacity .1s ease-in-out, color .1s ease-in-out; +} + +a.anchorlink svg { + fill: var(--page-foreground-color); +} + +h3 a.anchorlink svg, h4 a.anchorlink svg { + margin-bottom: -3px; + margin-top: -4px; +} + +a.anchorlink:hover { + opacity: .45; +} + +h2:hover a.anchorlink, h1:hover a.anchorlink, h3:hover a.anchorlink, h4:hover a.anchorlink { + display: inline-block; +} diff --git a/Docs/doxygen-awesome-css/doxygen-custom/custom-alternative.css b/Docs/doxygen-awesome-css/doxygen-custom/custom-alternative.css new file mode 100644 index 00000000..e66c1aee --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-custom/custom-alternative.css @@ -0,0 +1,54 @@ +html.alternative { + /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ + --primary-color: #AF7FE4; + --primary-dark-color: #9270E4; + --primary-light-color: #7aabd6; + --primary-lighter-color: #cae1f1; + --primary-lightest-color: #e9f1f8; + + /* page base colors */ + --page-background-color: white; + --page-foreground-color: #2c3e50; + --page-secondary-foreground-color: #67727e; + + + --border-radius-large: 22px; + --border-radius-small: 9px; + --border-radius-medium: 14px; + --spacing-small: 8px; + --spacing-medium: 14px; + --spacing-large: 19px; + + --top-height: 125px; + + --side-nav-background: #324067; + --side-nav-foreground: #F1FDFF; + --header-foreground: var(--side-nav-foreground); + --searchbar-background: var(--side-nav-foreground); + --searchbar-border-radius: var(--border-radius-medium); + --header-background: var(--side-nav-background); + --header-foreground: var(--side-nav-foreground); + + --toc-background: rgb(243, 240, 252); + --toc-foreground: var(--page-foreground-color); +} + +html.alternative.dark-mode { + color-scheme: dark; + + --primary-color: #AF7FE4; + --primary-dark-color: #9270E4; + --primary-light-color: #4779ac; + --primary-lighter-color: #191e21; + --primary-lightest-color: #191a1c; + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #3a3246; + --side-nav-background: #171D32; + --side-nav-foreground: #F1FDFF; + --toc-background: #20142C; + --searchbar-background: var(--page-background-color); + +} \ No newline at end of file diff --git a/Docs/doxygen-awesome-css/doxygen-custom/custom.css b/Docs/doxygen-awesome-css/doxygen-custom/custom.css new file mode 100644 index 00000000..0e694c0b --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-custom/custom.css @@ -0,0 +1,101 @@ +.github-corner svg { + fill: var(--primary-light-color); + color: var(--page-background-color); + width: 72px; + height: 72px; +} + +@media screen and (max-width: 767px) { + .github-corner svg { + width: 50px; + height: 50px; + } + #projectnumber { + margin-right: 22px; + } +} + +.alter-theme-button { + display: inline-block; + cursor: pointer; + background: var(--primary-color); + color: var(--page-background-color) !important; + border-radius: var(--border-radius-medium); + padding: var(--spacing-small) var(--spacing-medium); + text-decoration: none; +} + +.next_section_button { + display: block; + padding: var(--spacing-large) 0 var(--spacing-small) 0; + color: var(--page-background-color); + user-select: none; +} + +.next_section_button::after { + /* clearfix */ + content: ""; + clear: both; + display: table; +} + +.next_section_button a { + overflow: hidden; + float: right; + border: 1px solid var(--separator-color); + padding: var(--spacing-medium) calc(var(--spacing-large) / 2) var(--spacing-medium) var(--spacing-large); + border-radius: var(--border-radius-medium); + color: var(--page-secondary-foreground-color) !important; + text-decoration: none; + background-color: var(--page-background-color); + transition: color .08s ease-in-out, background-color .1s ease-in-out; +} + +.next_section_button a:hover { + color: var(--page-foreground-color) !important; + background-color: var(--odd-color); +} + +.next_section_button a::after { + content: '〉'; + color: var(--page-secondary-foreground-color) !important; + padding-left: var(--spacing-large); + display: inline-block; + transition: color .08s ease-in-out, transform .09s ease-in-out; +} + +.next_section_button a:hover::after { + color: var(--page-foreground-color) !important; + transform: translateX(3px); +} + +.alter-theme-button:hover { + background: var(--primary-dark-color); +} + +html.dark-mode .darkmode_inverted_image img, /* < doxygen 1.9.3 */ +html.dark-mode .darkmode_inverted_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ { + filter: brightness(87%) hue-rotate(180deg) invert(); +} + +.bordered_image { + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + display: inline-block; + overflow: hidden; +} + +html.dark-mode .bordered_image img, /* < doxygen 1.9.3 */ +html.dark-mode .bordered_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ { + border-radius: var(--border-radius-small); +} + +.title_screenshot { + filter: drop-shadow(0px 3px 10px rgba(0,0,0,0.22)); + max-width: 500px; + margin: var(--spacing-large) 0; +} + +.title_screenshot .caption { + display: none; +} \ No newline at end of file diff --git a/Docs/doxygen-awesome-css/doxygen-custom/header.html b/Docs/doxygen-awesome-css/doxygen-custom/header.html new file mode 100644 index 00000000..1fc73c5a --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-custom/header.html @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + +$projectname: $title +$title + + + + + + + + + + +$treeview +$search +$mathjax + +$extrastylesheet + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
$projectname +  $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
+
+ + diff --git a/Docs/doxygen-awesome-css/doxygen-custom/toggle-alternative-theme.js b/Docs/doxygen-awesome-css/doxygen-custom/toggle-alternative-theme.js new file mode 100644 index 00000000..72c37317 --- /dev/null +++ b/Docs/doxygen-awesome-css/doxygen-custom/toggle-alternative-theme.js @@ -0,0 +1,12 @@ + +let original_theme_active = true; + +function toggle_alternative_theme() { + if(original_theme_active) { + document.documentElement.classList.add("alternative") + original_theme_active = false; + } else { + document.documentElement.classList.remove("alternative") + original_theme_active = true; + } +} \ No newline at end of file diff --git a/Docs/footer.html b/Docs/footer.html index 5bd570ad..d67b2be1 100644 --- a/Docs/footer.html +++ b/Docs/footer.html @@ -5,16 +5,16 @@
diff --git a/Doxyfile b/Doxyfile index e175a45c..920937bf 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.10 +# Doxyfile 1.9.5 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -12,16 +12,26 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 @@ -38,7 +48,7 @@ PROJECT_NAME = "Recast Navigation" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -51,7 +61,7 @@ PROJECT_BRIEF = "Navigation-mesh Toolset for Games" # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is @@ -60,16 +70,28 @@ PROJECT_LOGO = OUTPUT_DIRECTORY = Docs -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# numer of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode @@ -81,14 +103,14 @@ ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English @@ -162,7 +184,7 @@ FULL_PATH_NAMES = NO # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -171,7 +193,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -189,6 +211,16 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus @@ -209,6 +241,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -232,19 +272,18 @@ TAB_SIZE = 4 # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = +ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -274,28 +313,40 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. -EXTENSION_MAPPING = +EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. +# documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. @@ -303,6 +354,15 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -328,7 +388,7 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. @@ -414,6 +474,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -434,6 +507,12 @@ EXTRACT_ALL = YES EXTRACT_PRIVATE = NO +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. @@ -471,6 +550,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -488,8 +574,8 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO @@ -508,12 +594,20 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = YES @@ -531,6 +625,12 @@ HIDE_SCOPE_NAMES = NO HIDE_COMPOUND_REFERENCE= NO +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -639,7 +739,7 @@ GENERATE_DEPRECATEDLIST= YES # sections, marked by \if ... \endif and \cond # ... \endcond blocks. -ENABLED_SECTIONS = +ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the @@ -681,14 +781,15 @@ SHOW_NAMESPACES = YES # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. -FILE_VERSION_FILTER = +FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE @@ -699,12 +800,12 @@ LAYOUT_FILE = Docs/DoxygenLayout.xml # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. -CITE_BIB_FILES = +CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages @@ -734,36 +835,68 @@ WARNINGS = YES WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -776,21 +909,34 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = . \ + README.md \ + CONTRIBUTING.md \ + Roadmap.md \ DetourCrowd \ DetourTileCache \ Recast \ - Docs/Conceptual \ Docs/Extern # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. @@ -799,11 +945,15 @@ INPUT_ENCODING = UTF-8 # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, -# *.vhdl, *.ucf, *.qsf, *.as and *.js. +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.cpp \ *.h \ @@ -829,7 +979,8 @@ EXCLUDE = Doxyfile \ TODO.txt \ RecastDemo/Contrib \ RecastDemo/Build \ - RecastDemo/Bin + RecastDemo/Bin \ + Tests # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -851,25 +1002,25 @@ EXCLUDE_PATTERNS = CMakeLists.txt # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test +# ANamespace::AClass, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. -EXAMPLE_PATTERNS = +EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands @@ -898,8 +1049,17 @@ IMAGE_PATH = Docs/Images # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. -INPUT_FILTER = +INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the @@ -907,8 +1067,12 @@ INPUT_FILTER = # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. -FILTER_PATTERNS = +FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for @@ -923,14 +1087,23 @@ FILTER_SOURCE_FILES = NO # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. -FILTER_SOURCE_PATTERNS = +FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = ./README.md + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -959,7 +1132,7 @@ INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. +# entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO @@ -991,12 +1164,12 @@ SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version +# (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # @@ -1018,25 +1191,6 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was -# compiled with the --with-libclang option. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1048,20 +1202,13 @@ CLANG_OPTIONS = ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output @@ -1105,7 +1252,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1127,7 +1274,7 @@ HTML_FOOTER = Docs/footer.html # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = +HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -1140,7 +1287,8 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = Docs/doxygen-awesome-css/doxygen-awesome.css \ + Docs/doxygen-awesome-css/doxygen-awesome-sidebar-only.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1150,12 +1298,29 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = +HTML_EXTRA_FILES = License.txt + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. Default setting AUTO_LIGHT +# enables light output unless the user preference is dark output. Other options +# are DARK to always use dark mode, LIGHT to always use light mode, AUTO_DARK to +# default to dark mode unless the user prefers light mode, and TOGGLE to let the +# user toggle between dark and light mode via a button. +# Possible values are: LIGHT Always generate light output., DARK Always generate +# dark output., AUTO_LIGHT Automatically set the mode according to the user +# preference, use light mode if no preference is set (the default)., AUTO_DARK +# Automatically set the mode according to the user preference, use dark mode if +# no preference is set. and TOGGLE Allow to user to switch between light and +# dark mode via a button.. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. @@ -1164,7 +1329,7 @@ HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A +# in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1191,6 +1356,17 @@ HTML_COLORSTYLE_GAMMA = 80 HTML_TIMESTAMP = YES +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. @@ -1214,13 +1390,14 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1234,6 +1411,13 @@ GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. @@ -1259,8 +1443,12 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1279,7 +1467,7 @@ GENERATE_HTMLHELP = NO # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_FILE = +CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, @@ -1287,10 +1475,10 @@ CHM_FILE = # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -HHC_LOCATION = +HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1300,7 +1488,7 @@ GENERATE_CHI = NO # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_INDEX_ENCODING = +CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it @@ -1331,11 +1519,12 @@ GENERATE_QHP = NO # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. -QCH_FILE = +QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1343,8 +1532,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1352,33 +1541,33 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_ATTRS = +QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_SECT_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. -QHG_LOCATION = +QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To @@ -1418,16 +1607,28 @@ DISABLE_INDEX = NO # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # @@ -1452,6 +1653,24 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1461,19 +1680,14 @@ EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. -FORMULA_TRANSPARENT = YES +FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1483,11 +1697,29 @@ FORMULA_TRANSPARENT = YES USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1500,26 +1732,33 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://www.mathjax.org/mathjax # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_EXTENSIONS = +MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_CODEFILE = +MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and @@ -1543,7 +1782,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing @@ -1562,7 +1801,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1575,11 +1815,12 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. -SEARCHENGINE_URL = +SEARCHENGINE_URL = # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed # search data is written to a file for indexing by an external tool. With the @@ -1595,7 +1836,7 @@ SEARCHDATA_FILE = searchdata.xml # projects and redirect the results back to the right project. # This tag requires that the tag SEARCHENGINE is set to YES. -EXTERNAL_SEARCH_ID = +EXTERNAL_SEARCH_ID = # The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen # projects other than the one defined by this configuration file, but that are @@ -1605,7 +1846,7 @@ EXTERNAL_SEARCH_ID = # EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... # This tag requires that the tag SEARCHENGINE is set to YES. -EXTRA_SEARCH_MAPPINGS = +EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Configuration options related to the LaTeX output @@ -1627,21 +1868,35 @@ LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. # -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). # The default file is: makeindex. # This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. @@ -1657,7 +1912,7 @@ COMPACT_LATEX = NO # The default value is: a4. # This tag requires that the tag GENERATE_LATEX is set to YES. -PAPER_TYPE = a4wide +PAPER_TYPE = a4 # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names # that should be included in the LaTeX output. The package can be specified just @@ -1669,34 +1924,36 @@ PAPER_TYPE = a4wide # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = +EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. # -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_HEADER = +LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_FOOTER = +LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created @@ -1707,7 +1964,7 @@ LATEX_FOOTER = # list). # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_STYLESHEET = # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the LATEX_OUTPUT output @@ -1715,7 +1972,7 @@ LATEX_EXTRA_STYLESHEET = # markers available. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_FILES = +LATEX_EXTRA_FILES = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is # prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will @@ -1726,9 +1983,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = NO -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1736,8 +1995,7 @@ USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode # command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. +# if errors occur, instead of asking the user for help. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1750,24 +2008,30 @@ LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. # The default value is: plain. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BIB_STYLE = plain +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- @@ -1807,32 +2071,22 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the # default style sheet that doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_EXTENSIONS_FILE = - -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO +RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # Configuration options related to the man page output @@ -1867,7 +2121,7 @@ MAN_EXTENSION = .3dbus # MAN_EXTENSION with the initial . removed. # This tag requires that the tag GENERATE_MAN is set to YES. -MAN_SUBDIR = +MAN_SUBDIR = # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it # will generate one additional man file for each entity documented in the real @@ -1905,6 +2159,13 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- @@ -1923,23 +2184,14 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sf.net) file that captures the -# structure of the code including all documentation. Note that this feature is -# still experimental and incomplete at the moment. +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO @@ -1980,7 +2232,7 @@ PERLMOD_PRETTY = YES # overwrite each other's variables. # This tag requires that the tag GENERATE_PERLMOD is set to YES. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor @@ -2018,10 +2270,11 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -2029,7 +2282,7 @@ INCLUDE_PATH = # used. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that are # defined before the preprocessor is started (similar to the -D option of e.g. @@ -2039,7 +2292,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2048,7 +2301,7 @@ PREDEFINED = # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have @@ -2077,13 +2330,13 @@ SKIP_FUNCTION_MACROS = YES # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = +TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = +GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES, all external class will be listed in # the class index. If set to NO, only the inherited external classes will be @@ -2106,40 +2359,16 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. -DIA_PATH = +DIA_PATH = # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. @@ -2166,35 +2395,50 @@ HAVE_DOT = NO DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTPATH = +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a +# graph for each documented class showing the direct and indirect inheritance +# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, +# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set +# to TEXT the direct and indirect inheritance relations will be shown as texts / +# links. +# Possible values are: NO, YES, TEXT and GRAPH. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES @@ -2208,7 +2452,8 @@ CLASS_GRAPH = YES COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# groups, showing the direct groups dependencies. See also the chapter Grouping +# in the manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2231,10 +2476,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2301,6 +2568,13 @@ GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: @@ -2332,39 +2606,44 @@ INTERACTIVE_SVG = NO # found. If left blank, it is assumed the dot tool can be found in the path. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_PATH = +DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile # command). # This tag requires that the tag HAVE_DOT is set to YES. -DOTFILE_DIRS = +DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the \mscfile # command). -MSCFILE_DIRS = +MSCFILE_DIRS = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile # command). -DIAFILE_DIRS = +DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. -PLANTUML_JAR_PATH = +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = # When using plantuml, the specified paths are searched for files specified by # the !include statement in a plantuml block. -PLANTUML_INCLUDE_PATH = +PLANTUML_INCLUDE_PATH = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes @@ -2390,18 +2669,6 @@ DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support @@ -2414,14 +2681,18 @@ DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/README.md b/README.md index 7db79963..1de41e8e 100644 --- a/README.md +++ b/README.md @@ -2,42 +2,36 @@ Recast & Detour =============== -[![Travis (Linux) Build Status](https://travis-ci.org/recastnavigation/recastnavigation.svg?branch=master)](https://travis-ci.org/recastnavigation/recastnavigation) -[![Appveyor (Windows) Build Status](https://ci.appveyor.com/api/projects/status/20w84u25b3f8h179/branch/master?svg=true)](https://ci.appveyor.com/project/recastnavigation/recastnavigation/branch/master) +[![Build](https://github.com/recastnavigation/recastnavigation/actions/workflows/Build.yaml/badge.svg)](https://github.com/recastnavigation/recastnavigation/actions/workflows/Build.yaml) +[![Tests](https://github.com/recastnavigation/recastnavigation/actions/workflows/Tests.yaml/badge.svg)](https://github.com/recastnavigation/recastnavigation/actions/workflows/Tests.yaml) -[![Issue Stats](http://www.issuestats.com/github/recastnavigation/recastnavigation/badge/pr?style=flat)](http://www.issuestats.com/github/recastnavigation/recastnavigation) -[![Issue Stats](http://www.issuestats.com/github/recastnavigation/recastnavigation/badge/issue?style=flat)](http://www.issuestats.com/github/recastnavigation/recastnavigation) - -![screenshot of a navmesh baked with the sample program](/RecastDemo/screenshot.png?raw=true) +![screenshot of a navmesh baked with the sample program](/Docs/Images/screenshot.png) ## Recast Recast is state of the art navigation mesh construction toolset for games. -* It is automatic, which means that you can throw any level geometry at it and you will get robust mesh out -* It is fast which means swift turnaround times for level designers -* It is open source so it comes with full source and you can customize it to your heart's content. +Recast is... +* 🤖 **Automatic** - throw any level geometry at it and you will get a robust navmesh out +* 🏎️ **Fast** - swift turnaround times for level designers +* 🧘 **Flexible** - easily customize the navmesh generation and runtime navigation systems to suit your specific game's needs. -The Recast process starts with constructing a voxel mold from a level geometry -and then casting a navigation mesh over it. The process consists of three steps, -building the voxel mold, partitioning the mold into simple regions, peeling off -the regions as simple polygons. - -1. The voxel mold is build from the input triangle mesh by rasterizing the triangles into a multi-layer heightfield. Some simple filters are then applied to the mold to prune out locations where the character would not be able to move. -2. The walkable areas described by the mold are divided into simple overlayed 2D regions. The resulting regions have only one non-overlapping contour, which simplifies the final step of the process tremendously. -3. The navigation polygons are peeled off from the regions by first tracing the boundaries and then simplifying them. The resulting polygons are finally converted to convex polygons which makes them perfect for pathfinding and spatial reasoning about the level. +Recast constructs a navmesh through a multi-step rasterization process: +1. First Recast voxelizes the input triangle mesh by rasterizing the triangles into a multi-layer heightfield. +2. Voxels in areas where the character would not be able to move are removed by applying simple voxel data filters. +3. The walkable areas described by the voxel grid are then divided into sets of 2D polygonal regions. +4. The navigation polygons are generated by triangulating and stiching together the generated 2d plygonal regions. ## Detour -Recast is accompanied with Detour, path-finding and spatial reasoning toolkit. You can use any navigation mesh with Detour, but of course the data generated with Recast fits perfectly. +Recast is accompanied by Detour, a path-finding and spatial reasoning toolkit. You can use any navigation mesh with Detour, but of course the data generated with Recast fits perfectly. -Detour offers simple static navigation mesh which is suitable for many simple cases, as well as tiled navigation mesh which allows you to plug in and out pieces of the mesh. The tiled mesh allows you to create systems where you stream new navigation data in and out as the player progresses the level, or you may regenerate tiles as the world changes. +Detour offers a simple static navmesh data representation which is suitable for many simple cases. It also provides a tiled navigation mesh representation, which allows you to stream of navigation data in and out as the player progresses through the world and regenerate sections of the navmesh data as the world changes. +## RecastDemo -## Recast Demo - -You can find a comprehensive demo project in RecastDemo folder. It is a kitchen sink demo containing all the functionality of the library. If you are new to Recast & Detour, check out [Sample_SoloMesh.cpp](/RecastDemo/Source/Sample_SoloMesh.cpp) to get started with building navmeshes and [NavMeshTesterTool.cpp](/RecastDemo/Source/NavMeshTesterTool.cpp) to see how Detour can be used to find paths. +You can find a comprehensive demo project in the `RecastDemo` folder. It's a kitchen sink demo showcasing all the functionality of the library. If you are new to Recast & Detour, check out [Sample_SoloMesh.cpp](/RecastDemo/Source/Sample_SoloMesh.cpp) to get started with building navmeshes and [NavMeshTesterTool.cpp](/RecastDemo/Source/NavMeshTesterTool.cpp) to see how Detour can be used to find paths. ### Building RecastDemo @@ -45,25 +39,24 @@ RecastDemo uses [premake5](http://premake.github.io/) to build platform specific #### Linux -- Install SDl2 and its dependencies according to your distro's guidelines. -- run `premake5 gmake` from the `RecastDemo` folder. -- `cd Build/gmake` then `make` -- Run `RecastDemo\Bin\RecastDemo` +- Install SDL2 and its dependencies according to your distro's guidelines. +- Navigate to the `RecastDemo` folder and run `premake5 gmake2` +- Navigate to `RecastDemo/Build/gmake2` and run `make` +- Navigate to `RecastDemo/Bin` and run `./RecastDemo` -#### OSX +#### macOS -- Grab the latest SDL2 development library dmg from [here](https://www.libsdl.org/download-2.0.php) and place `SDL2.framework` in `/Library/Frameworks/` +- Grab the latest SDL2 development library dmg from [here](https://github.com/libsdl-org/SDL) and place `SDL2.framework` in `RecastDemo/Bin` - Navigate to the `RecastDemo` folder and run `premake5 xcode4` - Open `Build/xcode4/recastnavigation.xcworkspace` -- Select the "RecastDemo" project in the left pane, go to the "BuildPhases" tab and expand "Link Binary With Libraries" -- Remove the existing entry for SDL2 (it should have a white box icon) and re-add it by hitting the plus, selecting "Add Other", and selecting `/Library/Frameworks/SDL2.framework`. It should now have a suitcase icon. - Set the RecastDemo project as the target and build. #### Windows -- Grab the latest SDL2 development library release from [here](https://www.libsdl.org/download-2.0.php) and unzip it `RecastDemo\Contrib`. Rename the SDL folder such that the path `RecastDemo\Contrib\SDL\lib\x86` is valid. -- Run `"premake5" vs2015` from the `RecastDemo` folder -- Open the solution, build, and run. +- Grab the latest SDL2 development library release from [here](https://github.com/libsdl-org/SDL) and unzip it `RecastDemo\Contrib`. Rename the SDL folder such that the path `RecastDemo\Contrib\SDL\lib\x86` is valid. +- Navigate to the `RecastDemo` folder and run `premake5 vs2022` +- Open `Build/vs2022/recastnavigation.sln`. +- Set `RecastDemo` as the startup project, build, and run. ### Running Unit tests @@ -71,13 +64,21 @@ RecastDemo uses [premake5](http://premake.github.io/) to build platform specific - Build the "Tests" project. This will generate an executable named "Tests" in `RecastDemo/Bin/` - Run the "Tests" executable. It will execute all the unit tests, indicate those that failed, and display a count of those that succeeded. -## Integrating with your own project +## Integrating with your game or engine It is recommended to add the source directories `DebugUtils`, `Detour`, `DetourCrowd`, `DetourTileCache`, and `Recast` into your own project depending on which parts of the project you need. For example your level building tool could include `DebugUtils`, `Recast`, and `Detour`, and your game runtime could just include `Detour`. -## Contributing +### Install through vcpkg -See the [Contributing document](CONTRIBUTING.md) for guidelines for making contributions. +If you are using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager you can download and install Recast with: + +``` +vcpkg install recast +``` + +## Contribute + +Check out the [Project Roadmap](Roadmap.md) to see what features and functionality you might be able to help with, and the [Contributing doc](CONTRIBUTING.md) for guidance on making contributions. ## Discuss diff --git a/Recast/CMakeLists.txt b/Recast/CMakeLists.txt new file mode 100644 index 00000000..001430b5 --- /dev/null +++ b/Recast/CMakeLists.txt @@ -0,0 +1,33 @@ +file(GLOB SOURCES Source/*.cpp) +add_library(Recast ${SOURCES}) + +add_library(RecastNavigation::Recast ALIAS Recast) +set_target_properties(Recast PROPERTIES DEBUG_POSTFIX -d) + +set(Recast_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") + +target_include_directories(Recast PUBLIC + "$" +) + +set_target_properties(Recast PROPERTIES + SOVERSION ${SOVERSION} + VERSION ${LIB_VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "Recast-d" + ) + +install(TARGETS Recast + EXPORT recastnavigation-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation + ) + +file(GLOB INCLUDES Include/*.h) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +if(MSVC) + install(FILES "$/Recast-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") +endif() diff --git a/Recast/Include/Recast.h b/Recast/Include/Recast.h index 02b2070b..9d7298d0 100644 --- a/Recast/Include/Recast.h +++ b/Recast/Include/Recast.h @@ -24,13 +24,16 @@ /// The value of PI used by Recast. static const float RC_PI = 3.14159265f; +/// Used to ignore unused function parameters and silence any compiler warnings. +template void rcIgnoreUnused(const T&) { } + /// Recast log categories. /// @see rcContext enum rcLogCategory { RC_LOG_PROGRESS = 1, ///< A progress log entry. RC_LOG_WARNING, ///< A warning log entry. - RC_LOG_ERROR, ///< An error log entry. + RC_LOG_ERROR ///< An error log entry. }; /// Recast performance timer categories. @@ -99,12 +102,21 @@ enum rcTimerLabel /// Provides an interface for optional logging and performance tracking of the Recast /// build process. +/// +/// This class does not provide logging or timer functionality on its +/// own. Both must be provided by a concrete implementation +/// by overriding the protected member functions. Also, this class does not +/// provide an interface for extracting log messages. (Only adding them.) +/// So concrete implementations must provide one. +/// +/// If no logging or timers are required, just pass an instance of this +/// class through the Recast build process. +/// /// @ingroup recast class rcContext { public: - - /// Contructor. + /// Constructor. /// @param[in] state TRUE if the logging and performance timers should be enabled. [Default: true] inline rcContext(bool state = true) : m_logEnabled(state), m_timerEnabled(state) {} virtual ~rcContext() {} @@ -117,56 +129,62 @@ public: inline void resetLog() { if (m_logEnabled) doResetLog(); } /// Logs a message. - /// @param[in] category The category of the message. - /// @param[in] format The message. + /// + /// Example: + /// @code + /// // Where ctx is an instance of rcContext and filepath is a char array. + /// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath); + /// @endcode + /// + /// @param[in] category The category of the message. + /// @param[in] format The message. void log(const rcLogCategory category, const char* format, ...); /// Enables or disables the performance timers. /// @param[in] state TRUE if timers should be enabled. inline void enableTimer(bool state) { m_timerEnabled = state; } - /// Clears all peformance timers. (Resets all to unused.) + /// Clears all performance timers. (Resets all to unused.) inline void resetTimers() { if (m_timerEnabled) doResetTimers(); } /// Starts the specified performance timer. - /// @param label The category of the timer. + /// @param label The category of the timer. inline void startTimer(const rcTimerLabel label) { if (m_timerEnabled) doStartTimer(label); } /// Stops the specified performance timer. - /// @param label The category of the timer. + /// @param label The category of the timer. inline void stopTimer(const rcTimerLabel label) { if (m_timerEnabled) doStopTimer(label); } /// Returns the total accumulated time of the specified performance timer. - /// @param label The category of the timer. - /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + /// @param label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. inline int getAccumulatedTime(const rcTimerLabel label) const { return m_timerEnabled ? doGetAccumulatedTime(label) : -1; } protected: - /// Clears all log entries. - virtual void doResetLog() {} + virtual void doResetLog(); /// Logs a message. - /// @param[in] category The category of the message. - /// @param[in] msg The formatted message. - /// @param[in] len The length of the formatted message. - virtual void doLog(const rcLogCategory /*category*/, const char* /*msg*/, const int /*len*/) {} + /// @param[in] category The category of the message. + /// @param[in] msg The formatted message. + /// @param[in] len The length of the formatted message. + virtual void doLog(const rcLogCategory category, const char* msg, const int len) { rcIgnoreUnused(category); rcIgnoreUnused(msg); rcIgnoreUnused(len); } /// Clears all timers. (Resets all to unused.) virtual void doResetTimers() {} /// Starts the specified performance timer. - /// @param[in] label The category of timer. - virtual void doStartTimer(const rcTimerLabel /*label*/) {} + /// @param[in] label The category of timer. + virtual void doStartTimer(const rcTimerLabel label) { rcIgnoreUnused(label); } /// Stops the specified performance timer. - /// @param[in] label The category of the timer. - virtual void doStopTimer(const rcTimerLabel /*label*/) {} + /// @param[in] label The category of the timer. + virtual void doStopTimer(const rcTimerLabel label) { rcIgnoreUnused(label); } /// Returns the total accumulated time of the specified performance timer. - /// @param[in] label The category of the timer. - /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. - virtual int doGetAccumulatedTime(const rcTimerLabel /*label*/) const { return -1; } + /// @param[in] label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + virtual int doGetAccumulatedTime(const rcTimerLabel label) const { rcIgnoreUnused(label); return -1; } /// True if logging is enabled. bool m_logEnabled; @@ -240,7 +258,7 @@ struct rcConfig /// The maximum allowed length for contour edges along the border of the mesh. [Limit: >=0] [Units: vx] int maxEdgeLen; - /// The maximum distance a simplfied contour's border edges should deviate + /// The maximum distance a simplified contour's border edges should deviate /// the original raw contour. [Limit: >=0] [Units: vx] float maxSimplificationError; @@ -332,6 +350,9 @@ struct rcCompactSpan /// @ingroup recast struct rcCompactHeightfield { + rcCompactHeightfield(); + ~rcCompactHeightfield(); + int width; ///< The width of the heightfield. (Along the x-axis in cell units.) int height; ///< The height of the heightfield. (Along the z-axis in cell units.) int spanCount; ///< The number of spans in the heightfield. @@ -348,6 +369,11 @@ struct rcCompactHeightfield rcCompactSpan* spans; ///< Array of spans. [Size: #spanCount] unsigned short* dist; ///< Array containing border distance data. [Size: #spanCount] unsigned char* areas; ///< Array containing area id data. [Size: #spanCount] + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcCompactHeightfield(const rcCompactHeightfield&); + rcCompactHeightfield& operator=(const rcCompactHeightfield&); }; /// Represents a heightfield layer within a layer set. @@ -376,8 +402,16 @@ struct rcHeightfieldLayer /// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet struct rcHeightfieldLayerSet { + rcHeightfieldLayerSet(); + ~rcHeightfieldLayerSet(); + rcHeightfieldLayer* layers; ///< The layers in the set. [Size: #nlayers] int nlayers; ///< The number of layers in the set. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcHeightfieldLayerSet(const rcHeightfieldLayerSet&); + rcHeightfieldLayerSet& operator=(const rcHeightfieldLayerSet&); }; /// Represents a simple, non-overlapping contour in field space. @@ -395,6 +429,9 @@ struct rcContour /// @ingroup recast struct rcContourSet { + rcContourSet(); + ~rcContourSet(); + rcContour* conts; ///< An array of the contours in the set. [Size: #nconts] int nconts; ///< The number of contours in the set. float bmin[3]; ///< The minimum bounds in world space. [(x, y, z)] @@ -405,12 +442,20 @@ struct rcContourSet int height; ///< The height of the set. (Along the z-axis in cell units.) int borderSize; ///< The AABB border size used to generate the source data from which the contours were derived. float maxError; ///< The max edge error that this contour set was simplified with. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcContourSet(const rcContourSet&); + rcContourSet& operator=(const rcContourSet&); }; /// Represents a polygon mesh suitable for use in building a navigation mesh. /// @ingroup recast struct rcPolyMesh { + rcPolyMesh(); + ~rcPolyMesh(); + unsigned short* verts; ///< The mesh vertices. [Form: (x, y, z) * #nverts] unsigned short* polys; ///< Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] unsigned short* regs; ///< The region id assigned to each polygon. [Length: #maxpolys] @@ -426,6 +471,11 @@ struct rcPolyMesh float ch; ///< The height of each cell. (The minimum increment along the y-axis.) int borderSize; ///< The AABB border size used to generate the source data from which the mesh was derived. float maxEdgeError; ///< The max error of the polygon edges in the mesh. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcPolyMesh(const rcPolyMesh&); + rcPolyMesh& operator=(const rcPolyMesh&); }; /// Contains triangle meshes that represent detailed height data associated @@ -433,12 +483,20 @@ struct rcPolyMesh /// @ingroup recast struct rcPolyMeshDetail { + rcPolyMeshDetail(); + ~rcPolyMeshDetail(); + unsigned int* meshes; ///< The sub-mesh data. [Size: 4*#nmeshes] float* verts; ///< The mesh vertices. [Size: 3*#nverts] unsigned char* tris; ///< The mesh triangles. [Size: 4*#ntris] int nmeshes; ///< The number of sub-meshes defined by #meshes. int nverts; ///< The number of vertices in #verts. int ntris; ///< The number of triangles in #tris. + +private: + // Explicitly-disabled copy constructor and copy assignment operator. + rcPolyMeshDetail(const rcPolyMeshDetail&); + rcPolyMeshDetail& operator=(const rcPolyMeshDetail&); }; static const unsigned char RC_AREA_FLAGS_MASK = 0x3F; @@ -470,82 +528,82 @@ public: /// @{ /// Allocates a heightfield object using the Recast allocator. -/// @return A heightfield that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcCreateHeightfield, rcFreeHeightField +/// @return A heightfield that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcCreateHeightfield, rcFreeHeightField rcHeightfield* rcAllocHeightfield(); /// Frees the specified heightfield object using the Recast allocator. -/// @param[in] hf A heightfield allocated using #rcAllocHeightfield -/// @ingroup recast -/// @see rcAllocHeightfield -void rcFreeHeightField(rcHeightfield* hf); +/// @param[in] heightfield A heightfield allocated using #rcAllocHeightfield +/// @ingroup recast +/// @see rcAllocHeightfield +void rcFreeHeightField(rcHeightfield* heightfield); /// Allocates a compact heightfield object using the Recast allocator. -/// @return A compact heightfield that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildCompactHeightfield, rcFreeCompactHeightfield +/// @return A compact heightfield that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildCompactHeightfield, rcFreeCompactHeightfield rcCompactHeightfield* rcAllocCompactHeightfield(); /// Frees the specified compact heightfield object using the Recast allocator. -/// @param[in] chf A compact heightfield allocated using #rcAllocCompactHeightfield -/// @ingroup recast -/// @see rcAllocCompactHeightfield -void rcFreeCompactHeightfield(rcCompactHeightfield* chf); +/// @param[in] compactHeightfield A compact heightfield allocated using #rcAllocCompactHeightfield +/// @ingroup recast +/// @see rcAllocCompactHeightfield +void rcFreeCompactHeightfield(rcCompactHeightfield* compactHeightfield); /// Allocates a heightfield layer set using the Recast allocator. -/// @return A heightfield layer set that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildHeightfieldLayers, rcFreeHeightfieldLayerSet +/// @return A heightfield layer set that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildHeightfieldLayers, rcFreeHeightfieldLayerSet rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet(); /// Frees the specified heightfield layer set using the Recast allocator. -/// @param[in] lset A heightfield layer set allocated using #rcAllocHeightfieldLayerSet -/// @ingroup recast -/// @see rcAllocHeightfieldLayerSet -void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset); +/// @param[in] layerSet A heightfield layer set allocated using #rcAllocHeightfieldLayerSet +/// @ingroup recast +/// @see rcAllocHeightfieldLayerSet +void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* layerSet); /// Allocates a contour set object using the Recast allocator. -/// @return A contour set that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildContours, rcFreeContourSet +/// @return A contour set that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildContours, rcFreeContourSet rcContourSet* rcAllocContourSet(); /// Frees the specified contour set using the Recast allocator. -/// @param[in] cset A contour set allocated using #rcAllocContourSet -/// @ingroup recast -/// @see rcAllocContourSet -void rcFreeContourSet(rcContourSet* cset); +/// @param[in] contourSet A contour set allocated using #rcAllocContourSet +/// @ingroup recast +/// @see rcAllocContourSet +void rcFreeContourSet(rcContourSet* contourSet); /// Allocates a polygon mesh object using the Recast allocator. -/// @return A polygon mesh that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildPolyMesh, rcFreePolyMesh +/// @return A polygon mesh that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildPolyMesh, rcFreePolyMesh rcPolyMesh* rcAllocPolyMesh(); /// Frees the specified polygon mesh using the Recast allocator. -/// @param[in] pmesh A polygon mesh allocated using #rcAllocPolyMesh -/// @ingroup recast -/// @see rcAllocPolyMesh -void rcFreePolyMesh(rcPolyMesh* pmesh); +/// @param[in] polyMesh A polygon mesh allocated using #rcAllocPolyMesh +/// @ingroup recast +/// @see rcAllocPolyMesh +void rcFreePolyMesh(rcPolyMesh* polyMesh); /// Allocates a detail mesh object using the Recast allocator. -/// @return A detail mesh that is ready for initialization, or null on failure. -/// @ingroup recast -/// @see rcBuildPolyMeshDetail, rcFreePolyMeshDetail +/// @return A detail mesh that is ready for initialization, or null on failure. +/// @ingroup recast +/// @see rcBuildPolyMeshDetail, rcFreePolyMeshDetail rcPolyMeshDetail* rcAllocPolyMeshDetail(); /// Frees the specified detail mesh using the Recast allocator. -/// @param[in] dmesh A detail mesh allocated using #rcAllocPolyMeshDetail -/// @ingroup recast -/// @see rcAllocPolyMeshDetail -void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh); +/// @param[in] detailMesh A detail mesh allocated using #rcAllocPolyMeshDetail +/// @ingroup recast +/// @see rcAllocPolyMeshDetail +void rcFreePolyMeshDetail(rcPolyMeshDetail* detailMesh); /// @} -/// Heighfield border flag. +/// Heightfield border flag. /// If a heightfield region ID has this bit set, then the region is a border -/// region and its spans are considered unwalkable. +/// region and its spans are considered un-walkable. /// (Used during the region and contour build process.) /// @see rcCompactSpan::reg static const unsigned short RC_BORDER_REG = 0x8000; @@ -579,7 +637,7 @@ static const int RC_AREA_BORDER = 0x20000; enum rcBuildContoursFlags { RC_CONTOUR_TESS_WALL_EDGES = 0x01, ///< Tessellate solid (impassable) edges during contour simplification. - RC_CONTOUR_TESS_AREA_EDGES = 0x02, ///< Tessellate edges between areas during contour simplification. + RC_CONTOUR_TESS_AREA_EDGES = 0x02 ///< Tessellate edges between areas during contour simplification. }; /// Applied to the region id field of contour vertices in order to extract the region id. @@ -595,7 +653,7 @@ static const unsigned short RC_MESH_NULL_IDX = 0xffff; /// Represents the null area. /// When a data element is given this value it is considered to no longer be -/// assigned to a usable area. (E.g. It is unwalkable.) +/// assigned to a usable area. (E.g. It is un-walkable.) static const unsigned char RC_NULL_AREA = 0; /// The default area id used to indicate a walkable polygon. @@ -610,44 +668,42 @@ static const int RC_NOT_CONNECTED = 0x3f; /// @name General helper functions /// @{ -/// Used to ignore a function parameter. VS complains about unused parameters -/// and this silences the warning. -/// @param [in] _ Unused parameter -template void rcIgnoreUnused(const T&) { } - /// Swaps the values of the two parameters. -/// @param[in,out] a Value A -/// @param[in,out] b Value B +/// @param[in,out] a Value A +/// @param[in,out] b Value B template inline void rcSwap(T& a, T& b) { T t = a; a = b; b = t; } /// Returns the minimum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The minimum of the two values. +/// @param[in] a Value A +/// @param[in] b Value B +/// @return The minimum of the two values. template inline T rcMin(T a, T b) { return a < b ? a : b; } /// Returns the maximum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The maximum of the two values. +/// @param[in] a Value A +/// @param[in] b Value B +/// @return The maximum of the two values. template inline T rcMax(T a, T b) { return a > b ? a : b; } /// Returns the absolute value. -/// @param[in] a The value. -/// @return The absolute value of the specified value. +/// @param[in] a The value. +/// @return The absolute value of the specified value. template inline T rcAbs(T a) { return a < 0 ? -a : a; } /// Returns the square of the value. -/// @param[in] a The value. -/// @return The square of the value. +/// @param[in] a The value. +/// @return The square of the value. template inline T rcSqr(T a) { return a*a; } /// Clamps the value to the specified range. -/// @param[in] v The value to clamp. -/// @param[in] mn The minimum permitted return value. -/// @param[in] mx The maximum permitted return value. -/// @return The value, clamped to the specified range. -template inline T rcClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); } +/// @param[in] value The value to clamp. +/// @param[in] minInclusive The minimum permitted return value. +/// @param[in] maxInclusive The maximum permitted return value. +/// @return The value, clamped to the specified range. +template inline T rcClamp(T value, T minInclusive, T maxInclusive) +{ + return value < minInclusive ? minInclusive: (value > maxInclusive ? maxInclusive : value); +} /// Returns the square root of the value. /// @param[in] x The value. @@ -659,9 +715,9 @@ float rcSqrt(float x); /// @{ /// Derives the cross product of two vectors. (@p v1 x @p v2) -/// @param[out] dest The cross product. [(x, y, z)] -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] +/// @param[out] dest The cross product. [(x, y, z)] +/// @param[in] v1 A Vector [(x, y, z)] +/// @param[in] v2 A vector [(x, y, z)] inline void rcVcross(float* dest, const float* v1, const float* v2) { dest[0] = v1[1]*v2[2] - v1[2]*v2[1]; @@ -670,8 +726,8 @@ inline void rcVcross(float* dest, const float* v1, const float* v2) } /// Derives the dot product of two vectors. (@p v1 . @p v2) -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] +/// @param[in] v1 A Vector [(x, y, z)] +/// @param[in] v2 A vector [(x, y, z)] /// @return The dot product. inline float rcVdot(const float* v1, const float* v2) { @@ -679,10 +735,10 @@ inline float rcVdot(const float* v1, const float* v2) } /// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s)) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] -/// @param[in] s The amount to scale @p v2 by before adding to @p v1. +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] +/// @param[in] s The amount to scale @p v2 by before adding to @p v1. inline void rcVmad(float* dest, const float* v1, const float* v2, const float s) { dest[0] = v1[0]+v2[0]*s; @@ -691,9 +747,9 @@ inline void rcVmad(float* dest, const float* v1, const float* v2, const float s) } /// Performs a vector addition. (@p v1 + @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to add to @p v1. [(x, y, z)] +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to add to @p v1. [(x, y, z)] inline void rcVadd(float* dest, const float* v1, const float* v2) { dest[0] = v1[0]+v2[0]; @@ -702,9 +758,9 @@ inline void rcVadd(float* dest, const float* v1, const float* v2) } /// Performs a vector subtraction. (@p v1 - @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] inline void rcVsub(float* dest, const float* v1, const float* v2) { dest[0] = v1[0]-v2[0]; @@ -713,8 +769,8 @@ inline void rcVsub(float* dest, const float* v1, const float* v2) } /// Selects the minimum value of each element from the specified vectors. -/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] +/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] +/// @param[in] v A vector. [(x, y, z)] inline void rcVmin(float* mn, const float* v) { mn[0] = rcMin(mn[0], v[0]); @@ -723,8 +779,8 @@ inline void rcVmin(float* mn, const float* v) } /// Selects the maximum value of each element from the specified vectors. -/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] +/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] +/// @param[in] v A vector. [(x, y, z)] inline void rcVmax(float* mx, const float* v) { mx[0] = rcMax(mx[0], v[0]); @@ -733,8 +789,8 @@ inline void rcVmax(float* mx, const float* v) } /// Performs a vector copy. -/// @param[out] dest The result. [(x, y, z)] -/// @param[in] v The vector to copy. [(x, y, z)] +/// @param[out] dest The result. [(x, y, z)] +/// @param[in] v The vector to copy. [(x, y, z)] inline void rcVcopy(float* dest, const float* v) { dest[0] = v[0]; @@ -743,8 +799,8 @@ inline void rcVcopy(float* dest, const float* v) } /// Returns the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] +/// @param[in] v1 A point. [(x, y, z)] +/// @param[in] v2 A point. [(x, y, z)] /// @return The distance between the two points. inline float rcVdist(const float* v1, const float* v2) { @@ -755,8 +811,8 @@ inline float rcVdist(const float* v1, const float* v2) } /// Returns the square of the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] +/// @param[in] v1 A point. [(x, y, z)] +/// @param[in] v2 A point. [(x, y, z)] /// @return The square of the distance between the two points. inline float rcVdistSqr(const float* v1, const float* v2) { @@ -767,7 +823,7 @@ inline float rcVdistSqr(const float* v1, const float* v2) } /// Normalizes the vector. -/// @param[in,out] v The vector to normalize. [(x, y, z)] +/// @param[in,out] v The vector to normalize. [(x, y, z)] inline void rcVnormalize(float* v) { float d = 1.0f / rcSqrt(rcSqr(v[0]) + rcSqr(v[1]) + rcSqr(v[2])); @@ -782,174 +838,249 @@ inline void rcVnormalize(float* v) /// @{ /// Calculates the bounding box of an array of vertices. -/// @ingroup recast -/// @param[in] verts An array of vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices in the @p verts array. -/// @param[out] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[out] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] -void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax); +/// @ingroup recast +/// @param[in] verts An array of vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices in the @p verts array. +/// @param[out] minBounds The minimum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[out] maxBounds The maximum bounds of the AABB. [(x, y, z)] [Units: wu] +void rcCalcBounds(const float* verts, int numVerts, float* minBounds, float* maxBounds); /// Calculates the grid size based on the bounding box and grid cell size. -/// @ingroup recast -/// @param[in] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[in] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[in] cs The xz-plane cell size. [Limit: > 0] [Units: wu] -/// @param[out] w The width along the x-axis. [Limit: >= 0] [Units: vx] -/// @param[out] h The height along the z-axis. [Limit: >= 0] [Units: vx] -void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h); +/// @ingroup recast +/// @param[in] minBounds The minimum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[in] maxBounds The maximum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[in] cellSize The xz-plane cell size. [Limit: > 0] [Units: wu] +/// @param[out] sizeX The width along the x-axis. [Limit: >= 0] [Units: vx] +/// @param[out] sizeZ The height along the z-axis. [Limit: >= 0] [Units: vx] +void rcCalcGridSize(const float* minBounds, const float* maxBounds, float cellSize, int* sizeX, int* sizeZ); /// Initializes a new heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] hf The allocated heightfield to initialize. -/// @param[in] width The width of the field along the x-axis. [Limit: >= 0] [Units: vx] -/// @param[in] height The height of the field along the z-axis. [Limit: >= 0] [Units: vx] -/// @param[in] bmin The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] -/// @param[in] bmax The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] -/// @param[in] cs The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] -/// @param[in] ch The y-axis cell size to use for field. [Limit: > 0] [Units: wu] -/// @returns True if the operation completed successfully. -bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, - const float* bmin, const float* bmax, - float cs, float ch); +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocHeightfield, rcHeightfield +/// @ingroup recast +/// +/// @param[in,out] context The build context to use during the operation. +/// @param[in,out] heightfield The allocated heightfield to initialize. +/// @param[in] sizeX The width of the field along the x-axis. [Limit: >= 0] [Units: vx] +/// @param[in] sizeZ The height of the field along the z-axis. [Limit: >= 0] [Units: vx] +/// @param[in] minBounds The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] +/// @param[in] maxBounds The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] +/// @param[in] cellSize The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] +/// @param[in] cellHeight The y-axis cell size to use for field. [Limit: > 0] [Units: wu] +/// @returns True if the operation completed successfully. +bool rcCreateHeightfield(rcContext* context, rcHeightfield& heightfield, int sizeX, int sizeZ, + const float* minBounds, const float* maxBounds, + float cellSize, float cellHeight); /// Modifies the area id of all triangles with a slope below the specified value. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. -/// [Limits: 0 <= value < 90] [Units: Degrees] -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] nt The number of triangles. -/// @param[out] areas The triangle area ids. [Length: >= @p nt] -/// @param[in] areaMod The area modification to apply. -void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, - const int* tris, int nt, unsigned char* areas, rcAreaModification areaMod); +/// +/// Only sets the area id's for the walkable triangles. Does not alter the +/// area id's for un-walkable triangles. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. +/// [Limits: 0 <= value < 90] [Units: Degrees] +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. +/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] numTris The number of triangles. +/// @param[out] triAreaIDs The triangle area ids. [Length: >= @p nt] +/// @param[in] areaMod The area modification to apply. +void rcMarkWalkableTriangles(rcContext* context, float walkableSlopeAngle, const float* verts, int numVerts, + const int* tris, int numTris, unsigned char* triAreaIDs, rcAreaModification areaMod); /// Modifies the area id of all triangles with a slope greater than or equal to the specified value. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. -/// [Limits: 0 <= value < 90] [Units: Degrees] -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] nt The number of triangles. -/// @param[out] areas The triangle area ids. [Length: >= @p nt] -void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, - const int* tris, int nt, unsigned char* areas); +/// +/// Only sets the area id's for the un-walkable triangles. Does not alter the +/// area id's for walkable triangles. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. +/// [Limits: 0 <= value < 90] [Units: Degrees] +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. +/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] numTris The number of triangles. +/// @param[out] triAreaIDs The triangle area ids. [Length: >= @p nt] +void rcClearUnwalkableTriangles(rcContext* context, float walkableSlopeAngle, const float* verts, int numVerts, + const int* tris, int numTris, unsigned char* triAreaIDs); /// Adds a span to the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] hf An initialized heightfield. -/// @param[in] x The width index where the span is to be added. -/// [Limits: 0 <= value < rcHeightfield::width] -/// @param[in] y The height index where the span is to be added. -/// [Limits: 0 <= value < rcHeightfield::height] -/// @param[in] smin The minimum height of the span. [Limit: < @p smax] [Units: vx] -/// @param[in] smax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] -/// @param[in] area The area id of the span. [Limit: <= #RC_WALKABLE_AREA) -/// @param[in] flagMergeThr The merge theshold. [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr); +/// +/// The span addition can be set to favor flags. If the span is merged to +/// another span and the new @p spanMax is within @p flagMergeThreshold units +/// from the existing span, the span flags are merged. +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] x The column x index where the span is to be added. +/// [Limits: 0 <= value < rcHeightfield::width] +/// @param[in] z The column z index where the span is to be added. +/// [Limits: 0 <= value < rcHeightfield::height] +/// @param[in] spanMin The minimum height of the span. [Limit: < @p spanMax] [Units: vx] +/// @param[in] spanMax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] +/// @param[in] areaID The area id of the span. [Limit: <= #RC_WALKABLE_AREA) +/// @param[in] flagMergeThreshold The merge threshold. [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcAddSpan(rcContext* context, rcHeightfield& heightfield, + int x, int z, + unsigned short spanMin, unsigned short spanMax, + unsigned char areaID, int flagMergeThreshold); -/// Rasterizes a triangle into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] v0 Triangle vertex 0 [(x, y, z)] -/// @param[in] v1 Triangle vertex 1 [(x, y, z)] -/// @param[in] v2 Triangle vertex 2 [(x, y, z)] -/// @param[in] area The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& solid, - const int flagMergeThr = 1); +/// Rasterizes a single triangle into the specified heightfield. +/// +/// Calling this for each triangle in a mesh is less efficient than calling rcRasterizeTriangles +/// +/// No spans will be added if the triangle does not overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] v0 Triangle vertex 0 [(x, y, z)] +/// @param[in] v1 Triangle vertex 1 [(x, y, z)] +/// @param[in] v2 Triangle vertex 2 [(x, y, z)] +/// @param[in] areaID The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangle(rcContext* context, + const float* v0, const float* v1, const float* v2, + unsigned char areaID, rcHeightfield& heightfield, int flagMergeThreshold = 1); /// Rasterizes an indexed triangle mesh into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, - const int* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. (unused) TODO (graham): Remove in next major release +/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] numTris The number of triangles. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* context, + const float* verts, int numVerts, + const int* tris, const unsigned char* triAreaIDs, int numTris, + rcHeightfield& heightfield, int flagMergeThreshold = 1); /// Rasterizes an indexed triangle mesh into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, - const unsigned short* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] numVerts The number of vertices. (unused) TODO (graham): Remove in next major release +/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] numTris The number of triangles. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* context, + const float* verts, int numVerts, + const unsigned short* tris, const unsigned char* triAreaIDs, int numTris, + rcHeightfield& heightfield, int flagMergeThreshold = 1); -/// Rasterizes triangles into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); +/// Rasterizes a triangle list into the specified heightfield. +/// +/// Expects each triangle to be specified as three sequential vertices of 3 floats. +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] +/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] numTris The number of triangles. +/// @param[in,out] heightfield An initialized heightfield. +/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const unsigned char* triAreaIDs, int numTris, + rcHeightfield& heightfield, int flagMergeThreshold = 1); -/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimp of a walkable neighbor. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid); +/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimb of a walkable neighbor. +/// +/// Allows the formation of walkable regions that will flow over low lying +/// objects such as curbs, and up structures such as stairways. +/// +/// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb +/// +/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call +/// #rcFilterLedgeSpans after calling this filter. +/// +/// @see rcHeightfield, rcConfig +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.) +void rcFilterLowHangingWalkableObstacles(rcContext* context, int walkableClimb, rcHeightfield& heightfield); -/// Marks spans that are ledges as not-walkable. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to -/// be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, - const int walkableClimb, rcHeightfield& solid); +/// Marks spans that are ledges as not-walkable. +/// +/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb +/// from the current span's maximum. +/// This method removes the impact of the overestimation of conservative voxelization +/// so the resulting mesh will not have regions hanging in the air over ledges. +/// +/// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb +/// +/// @see rcHeightfield, rcConfig +/// +/// @ingroup recast +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to +/// be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.) +void rcFilterLedgeSpans(rcContext* context, int walkableHeight, int walkableClimb, rcHeightfield& heightfield); -/// Marks walkable spans as not walkable if the clearence above the span is less than the specified height. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to -/// be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid); +/// Marks walkable spans as not walkable if the clearance above the span is less than the specified height. +/// +/// For this filter, the clearance above the span is the distance from the span's +/// maximum to the next higher span's minimum. (Same grid column.) +/// +/// @see rcHeightfield, rcConfig +/// @ingroup recast +/// +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to +/// be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.) +void rcFilterWalkableLowHeightSpans(rcContext* context, int walkableHeight, rcHeightfield& heightfield); /// Returns the number of spans contained in the specified heightfield. /// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] hf An initialized heightfield. +/// @param[in,out] context The build context to use during the operation. +/// @param[in] heightfield An initialized heightfield. /// @returns The number of spans in the heightfield. -int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); +int rcGetHeightFieldSpanCount(rcContext* context, const rcHeightfield& heightfield); /// @} /// @name Compact Heightfield Functions @@ -957,175 +1088,181 @@ int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); /// @{ /// Builds a compact heightfield representing open space, from a heightfield representing solid space. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area -/// to be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in] hf The heightfield to be compacted. -/// @param[out] chf The resulting compact heightfield. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& hf, rcCompactHeightfield& chf); +/// +/// This is just the beginning of the process of fully building a compact heightfield. +/// Various filters may be applied, then the distance field and regions built. +/// E.g: #rcBuildDistanceField and #rcBuildRegions +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig +/// @ingroup recast +/// +/// @param[in,out] context The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area +/// to be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in] heightfield The heightfield to be compacted. +/// @param[out] compactHeightfield The resulting compact heightfield. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildCompactHeightfield(rcContext* context, int walkableHeight, int walkableClimb, + rcHeightfield& heightfield, rcCompactHeightfield& compactHeightfield); /// Erodes the walkable area within the heightfield by the specified radius. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] -/// @param[in,out] chf The populated compact heightfield to erode. -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] +/// @param[in,out] chf The populated compact heightfield to erode. +/// @returns True if the operation completed successfully. bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf); /// Applies a median filter to walkable area types (based on area id), removing noise. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @returns True if the operation completed successfully. bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf); /// Applies an area id to all spans within the specified bounding box. (AABB) -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] bmin The minimum of the bounding box. [(x, y, z)] -/// @param[in] bmax The maximum of the bounding box. [(x, y, z)] -/// @param[in] areaMod The area modification to apply. -/// @param[in,out] chf A populated compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] bmin The minimum of the bounding box. [(x, y, z)] +/// @param[in] bmax The maximum of the bounding box. [(x, y, z)] +/// @param[in] areaMod The area modification to apply +/// @param[in,out] chf A populated compact heightfield. void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, rcAreaModification areaMod, rcCompactHeightfield& chf); /// Applies the area id to the all spans within the specified convex polygon. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts] -/// @param[in] nverts The number of vertices in the polygon. -/// @param[in] hmin The height of the base of the polygon. -/// @param[in] hmax The height of the top of the polygon. -/// @param[in] areaMod The area modification to apply. -/// @param[in,out] chf A populated compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts] +/// @param[in] nverts The number of vertices in the polygon. +/// @param[in] hmin The height of the base of the polygon. +/// @param[in] hmax The height of the top of the polygon. +/// @param[in] areaMod The area modification to apply +/// @param[in,out] chf A populated compact heightfield. void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, const float hmin, const float hmax, rcAreaModification areaMod, rcCompactHeightfield& chf); /// Helper function to offset voncex polygons for rcMarkConvexPolyArea. -/// @ingroup recast -/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts] -/// @param[in] nverts The number of vertices in the polygon. -/// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value] -/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts. -/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts. +/// @ingroup recast +/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts] +/// @param[in] nverts The number of vertices in the polygon. +/// @param[in] offset How much to offset the polygon by. [Units: wu] +/// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value] +/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts. +/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts. int rcOffsetPoly(const float* verts, const int nverts, const float offset, float* outVerts, const int maxOutVerts); /// Applies the area id to all spans within the specified cylinder. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)] -/// @param[in] r The radius of the cylinder. -/// @param[in] h The height of the cylinder. -/// @param[in] areaMod The area modification to apply. -/// @param[in,out] chf A populated compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)] +/// @param[in] r The radius of the cylinder. +/// @param[in] h The height of the cylinder. +/// @param[in] areaMod The area modification to apply. +/// @param[in,out] chf A populated compact heightfield. void rcMarkCylinderArea(rcContext* ctx, const float* pos, const float r, const float h, rcAreaModification areaMod, rcCompactHeightfield& chf); /// Builds the distance field for the specified compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @returns True if the operation completed successfully. bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf); /// Builds region data for the heightfield using watershed partitioning. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. -/// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. -/// [Limit: >=0] [Units: vx]. -/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, -/// be merged with larger regions. [Limit: >=0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea); +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// [Limit: >=0] [Units: vx] +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// [Limit: >=0] [Units: vx]. +/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, +/// be merged with larger regions. [Limit: >=0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, int borderSize, int minRegionArea, int mergeRegionArea); /// Builds region data for the heightfield by partitioning the heightfield in non-overlapping layers. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. /// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. /// [Limit: >=0] [Units: vx]. -/// @returns True if the operation completed successfully. -bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea); +/// @returns True if the operation completed successfully. +bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, int borderSize, int minRegionArea); /// Builds region data for the heightfield using simple monotone partitioning. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. /// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. /// [Limit: >=0] [Units: vx]. -/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, +/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, /// be merged with larger regions. [Limit: >=0] [Units: vx] -/// @returns True if the operation completed successfully. +/// @returns True if the operation completed successfully. bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea); + int borderSize, int minRegionArea, int mergeRegionArea); /// Sets the neighbor connection data for the specified direction. -/// @param[in] s The span to update. -/// @param[in] dir The direction to set. [Limits: 0 <= value < 4] -/// @param[in] i The index of the neighbor span. -inline void rcSetCon(rcCompactSpan& s, int dir, int i) +/// @param[in] span The span to update. +/// @param[in] direction The direction to set. [Limits: 0 <= value < 4] +/// @param[in] neighborIndex The index of the neighbor span. +inline void rcSetCon(rcCompactSpan& span, int direction, int neighborIndex) { - const unsigned int shift = (unsigned int)dir*6; - unsigned int con = s.con; - s.con = (con & ~(0x3f << shift)) | (((unsigned int)i & 0x3f) << shift); + const unsigned int shift = (unsigned int)direction * 6; + const unsigned int con = span.con; + span.con = (con & ~(0x3f << shift)) | (((unsigned int)neighborIndex & 0x3f) << shift); } /// Gets neighbor connection data for the specified direction. -/// @param[in] s The span to check. -/// @param[in] dir The direction to check. [Limits: 0 <= value < 4] -/// @return The neighbor connection data for the specified direction, -/// or #RC_NOT_CONNECTED if there is no connection. -inline int rcGetCon(const rcCompactSpan& s, int dir) +/// @param[in] span The span to check. +/// @param[in] direction The direction to check. [Limits: 0 <= value < 4] +/// @return The neighbor connection data for the specified direction, or #RC_NOT_CONNECTED if there is no connection. +inline int rcGetCon(const rcCompactSpan& span, int direction) { - const unsigned int shift = (unsigned int)dir*6; - return (s.con >> shift) & 0x3f; + const unsigned int shift = (unsigned int)direction * 6; + return (span.con >> shift) & 0x3f; } /// Gets the standard width (x-axis) offset for the specified direction. -/// @param[in] dir The direction. [Limits: 0 <= value < 4] -/// @return The width offset to apply to the current cell position to move -/// in the direction. -inline int rcGetDirOffsetX(int dir) +/// @param[in] direction The direction. [Limits: 0 <= value < 4] +/// @return The width offset to apply to the current cell position to move in the direction. +inline int rcGetDirOffsetX(int direction) { static const int offset[4] = { -1, 0, 1, 0, }; - return offset[dir&0x03]; + return offset[direction & 0x03]; } +// TODO (graham): Rename this to rcGetDirOffsetZ /// Gets the standard height (z-axis) offset for the specified direction. -/// @param[in] dir The direction. [Limits: 0 <= value < 4] -/// @return The height offset to apply to the current cell position to move -/// in the direction. -inline int rcGetDirOffsetY(int dir) +/// @param[in] direction The direction. [Limits: 0 <= value < 4] +/// @return The height offset to apply to the current cell position to move in the direction. +inline int rcGetDirOffsetY(int direction) { static const int offset[4] = { 0, 1, 0, -1 }; - return offset[dir&0x03]; + return offset[direction & 0x03]; } /// Gets the direction for the specified offset. One of x and y should be 0. -/// @param[in] x The x offset. [Limits: -1 <= value <= 1] -/// @param[in] y The y offset. [Limits: -1 <= value <= 1] -/// @return The direction that represents the offset. -inline int rcGetDirForOffset(int x, int y) +/// @param[in] offsetX The x offset. [Limits: -1 <= value <= 1] +/// @param[in] offsetZ The z offset. [Limits: -1 <= value <= 1] +/// @return The direction that represents the offset. +inline int rcGetDirForOffset(int offsetX, int offsetZ) { static const int dirs[5] = { 3, 0, -1, 2, 1 }; - return dirs[((y+1)<<1)+x]; + return dirs[((offsetZ + 1) << 1) + offsetX]; } /// @} @@ -1134,42 +1271,42 @@ inline int rcGetDirForOffset(int x, int y) /// @{ /// Builds a layer set from the specified compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] chf A fully built compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0] -/// [Units: vx] -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area -/// to be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[out] lset The resulting layer set. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] chf A fully built compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0] +/// [Units: vx] +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area +/// to be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[out] lset The resulting layer set. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int walkableHeight, + int borderSize, int walkableHeight, rcHeightfieldLayerSet& lset); /// Builds a contour set from the region outlines in the provided compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] chf A fully built compact heightfield. -/// @param[in] maxError The maximum distance a simplfied contour's border edges should deviate -/// the original raw contour. [Limit: >=0] [Units: wu] -/// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh. -/// [Limit: >=0] [Units: vx] -/// @param[out] cset The resulting contour set. (Must be pre-allocated.) -/// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] chf A fully built compact heightfield. +/// @param[in] maxError The maximum distance a simplified contour's border edges should deviate +/// the original raw contour. [Limit: >=0] [Units: wu] +/// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh. +/// [Limit: >=0] [Units: vx] +/// @param[out] cset The resulting contour set. (Must be pre-allocated.) +/// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags) +/// @returns True if the operation completed successfully. bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, - const float maxError, const int maxEdgeLen, - rcContourSet& cset, const int buildFlags = RC_CONTOUR_TESS_WALL_EDGES); + float maxError, int maxEdgeLen, + rcContourSet& cset, int buildFlags = RC_CONTOUR_TESS_WALL_EDGES); /// Builds a polygon mesh from the provided contours. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] cset A fully built contour set. -/// @param[in] nvp The maximum number of vertices allowed for polygons generated during the -/// contour to polygon conversion process. [Limit: >= 3] -/// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] cset A fully built contour set. +/// @param[in] nvp The maximum number of vertices allowed for polygons generated during the +/// contour to polygon conversion process. [Limit: >= 3] +/// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.) +/// @returns True if the operation completed successfully. bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh); /// Merges multiple polygon meshes into a single mesh. @@ -1182,34 +1319,34 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh); /// Builds a detail mesh from the provided polygon mesh. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] mesh A fully built polygon mesh. -/// @param[in] chf The compact heightfield used to build the polygon mesh. -/// @param[in] sampleDist Sets the distance to use when samping the heightfield. [Limit: >=0] [Units: wu] -/// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from -/// heightfield data. [Limit: >=0] [Units: wu] -/// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] mesh A fully built polygon mesh. +/// @param[in] chf The compact heightfield used to build the polygon mesh. +/// @param[in] sampleDist Sets the distance to use when sampling the heightfield. [Limit: >=0] [Units: wu] +/// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from +/// heightfield data. [Limit: >=0] [Units: wu] +/// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf, - const float sampleDist, const float sampleMaxError, + float sampleDist, float sampleMaxError, rcPolyMeshDetail& dmesh); /// Copies the poly mesh data from src to dst. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] src The source mesh to copy from. -/// @param[out] dst The resulting detail mesh. (Must be pre-allocated, must be empty mesh.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] src The source mesh to copy from. +/// @param[out] dst The resulting detail mesh. (Must be pre-allocated, must be empty mesh.) +/// @returns True if the operation completed successfully. bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst); /// Merges multiple detail meshes into a single detail mesh. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes] -/// @param[in] nmeshes The number of detail meshes in the meshes array. -/// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes] +/// @param[in] nmeshes The number of detail meshes in the meshes array. +/// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh); /// @} diff --git a/Recast/Include/RecastAlloc.h b/Recast/Include/RecastAlloc.h index 3cdd450d..1741de9f 100644 --- a/Recast/Include/RecastAlloc.h +++ b/Recast/Include/RecastAlloc.h @@ -19,7 +19,10 @@ #ifndef RECASTALLOC_H #define RECASTALLOC_H -#include +#include "RecastAssert.h" + +#include +#include /// Provides hint values to the memory allocator on how long the /// memory is expected to be used. @@ -44,78 +47,301 @@ typedef void (rcFreeFunc)(void* ptr); /// Sets the base custom allocation functions to be used by Recast. /// @param[in] allocFunc The memory allocation function to be used by #rcAlloc /// @param[in] freeFunc The memory de-allocation function to be used by #rcFree +/// +/// @see rcAlloc, rcFree void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc); /// Allocates a memory block. -/// @param[in] size The size, in bytes of memory, to allocate. -/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. -/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. -/// @see rcFree +/// +/// @param[in] size The size, in bytes of memory, to allocate. +/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. +/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. +/// +/// @see rcFree, rcAllocSetCustom void* rcAlloc(size_t size, rcAllocHint hint); -/// Deallocates a memory block. -/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc. -/// @see rcAlloc +/// Deallocates a memory block. If @p ptr is NULL, this does nothing. +/// +/// @warning This function leaves the value of @p ptr unchanged. So it still +/// points to the same (now invalid) location, and not to null. +/// +/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc. +/// +/// @see rcAlloc, rcAllocSetCustom void rcFree(void* ptr); +/// An implementation of operator new usable for placement new. The default one is part of STL (which we don't use). +/// rcNewTag is a dummy type used to differentiate our operator from the STL one, in case users import both Recast +/// and STL. +struct rcNewTag {}; +inline void* operator new(size_t, const rcNewTag&, void* p) { return p; } +inline void operator delete(void*, const rcNewTag&, void*) {} -/// A simple dynamic array of integers. +/// Signed to avoid warnnings when comparing to int loop indexes, and common error with comparing to zero. +/// MSVC2010 has a bug where ssize_t is unsigned (!!!). +typedef intptr_t rcSizeType; +#define RC_SIZE_MAX INTPTR_MAX + +/// Macros to hint to the compiler about the likeliest branch. Please add a benchmark that demonstrates a performance +/// improvement before introducing use cases. +#if defined(__GNUC__) || defined(__clang__) +#define rcLikely(x) __builtin_expect((x), true) +#define rcUnlikely(x) __builtin_expect((x), false) +#else +#define rcLikely(x) (x) +#define rcUnlikely(x) (x) +#endif + +/// Variable-sized storage type. Mimics the interface of std::vector with some notable differences: +/// * Uses rcAlloc()/rcFree() to handle storage. +/// * No support for a custom allocator. +/// * Uses signed size instead of size_t to avoid warnings in for loops: "for (int i = 0; i < foo.size(); i++)" +/// * Omits methods of limited utility: insert/erase, (bad performance), at (we don't use exceptions), operator=. +/// * assign() and the pre-sizing constructor follow C++11 semantics -- they don't construct a temporary if no value is provided. +/// * push_back() and resize() support adding values from the current vector. Range-based constructors and assign(begin, end) do not. +/// * No specialization for bool. +template +class rcVectorBase { + rcSizeType m_size; + rcSizeType m_cap; + T* m_data; + // Constructs a T at the give address with either the copy constructor or the default. + static void construct(T* p, const T& v) { ::new(rcNewTag(), (void*)p) T(v); } + static void construct(T* p) { ::new(rcNewTag(), (void*)p) T; } + static void construct_range(T* begin, T* end); + static void construct_range(T* begin, T* end, const T& value); + static void copy_range(T* dst, const T* begin, const T* end); + void destroy_range(rcSizeType begin, rcSizeType end); + // Creates an array of the given size, copies all of this vector's data into it, and returns it. + T* allocate_and_copy(rcSizeType size); + void resize_impl(rcSizeType size, const T* value); + // Requires: min_capacity > m_cap. + rcSizeType get_new_capacity(rcSizeType min_capacity); + public: + typedef rcSizeType size_type; + typedef T value_type; + + rcVectorBase() : m_size(0), m_cap(0), m_data(0) {} + rcVectorBase(const rcVectorBase& other) : m_size(0), m_cap(0), m_data(0) { assign(other.begin(), other.end()); } + explicit rcVectorBase(rcSizeType count) : m_size(0), m_cap(0), m_data(0) { resize(count); } + rcVectorBase(rcSizeType count, const T& value) : m_size(0), m_cap(0), m_data(0) { resize(count, value); } + rcVectorBase(const T* begin, const T* end) : m_size(0), m_cap(0), m_data(0) { assign(begin, end); } + ~rcVectorBase() { destroy_range(0, m_size); rcFree(m_data); } + + // Unlike in std::vector, we return a bool to indicate whether the alloc was successful. + bool reserve(rcSizeType size); + + void assign(rcSizeType count, const T& value) { clear(); resize(count, value); } + void assign(const T* begin, const T* end); + + void resize(rcSizeType size) { resize_impl(size, NULL); } + void resize(rcSizeType size, const T& value) { resize_impl(size, &value); } + // Not implemented as resize(0) because resize requires T to be default-constructible. + void clear() { destroy_range(0, m_size); m_size = 0; } + + void push_back(const T& value); + void pop_back() { rcAssert(m_size > 0); back().~T(); m_size--; } + + rcSizeType size() const { return m_size; } + rcSizeType capacity() const { return m_cap; } + bool empty() const { return size() == 0; } + + const T& operator[](rcSizeType i) const { rcAssert(i >= 0 && i < m_size); return m_data[i]; } + T& operator[](rcSizeType i) { rcAssert(i >= 0 && i < m_size); return m_data[i]; } + + const T& front() const { rcAssert(m_size); return m_data[0]; } + T& front() { rcAssert(m_size); return m_data[0]; } + const T& back() const { rcAssert(m_size); return m_data[m_size - 1]; } + T& back() { rcAssert(m_size); return m_data[m_size - 1]; } + const T* data() const { return m_data; } + T* data() { return m_data; } + + T* begin() { return m_data; } + T* end() { return m_data + m_size; } + const T* begin() const { return m_data; } + const T* end() const { return m_data + m_size; } + + void swap(rcVectorBase& other); + + // Explicitly deleted. + rcVectorBase& operator=(const rcVectorBase& other); +}; + +template +bool rcVectorBase::reserve(rcSizeType count) { + if (count <= m_cap) { + return true; + } + T* new_data = allocate_and_copy(count); + if (!new_data) { + return false; + } + destroy_range(0, m_size); + rcFree(m_data); + m_data = new_data; + m_cap = count; + return true; +} +template +T* rcVectorBase::allocate_and_copy(rcSizeType size) { + rcAssert(RC_SIZE_MAX / static_cast(sizeof(T)) >= size); + T* new_data = static_cast(rcAlloc(sizeof(T) * size, H)); + if (new_data) { + copy_range(new_data, m_data, m_data + m_size); + } + return new_data; +} +template +void rcVectorBase::assign(const T* begin, const T* end) { + clear(); + reserve(end - begin); + m_size = end - begin; + copy_range(m_data, begin, end); +} +template +void rcVectorBase::push_back(const T& value) { + // rcLikely increases performance by ~50% on BM_rcVector_PushPreallocated, + // and by ~2-5% on BM_rcVector_Push. + if (rcLikely(m_size < m_cap)) { + construct(m_data + m_size++, value); + return; + } + + const rcSizeType new_cap = get_new_capacity(m_cap + 1); + T* data = allocate_and_copy(new_cap); + // construct between allocate and destroy+free in case value is + // in this vector. + construct(data + m_size, value); + destroy_range(0, m_size); + m_size++; + m_cap = new_cap; + rcFree(m_data); + m_data = data; +} + +template +rcSizeType rcVectorBase::get_new_capacity(rcSizeType min_capacity) { + rcAssert(min_capacity <= RC_SIZE_MAX); + if (rcUnlikely(m_cap >= RC_SIZE_MAX / 2)) + return RC_SIZE_MAX; + return 2 * m_cap > min_capacity ? 2 * m_cap : min_capacity; +} + +template +void rcVectorBase::resize_impl(rcSizeType size, const T* value) { + if (size < m_size) { + destroy_range(size, m_size); + m_size = size; + } else if (size > m_size) { + if (size <= m_cap) { + if (value) { + construct_range(m_data + m_size, m_data + size, *value); + } else { + construct_range(m_data + m_size, m_data + size); + } + m_size = size; + } else { + const rcSizeType new_cap = get_new_capacity(size); + T* new_data = allocate_and_copy(new_cap); + // We defer deconstructing/freeing old data until after constructing + // new elements in case "value" is there. + if (value) { + construct_range(new_data + m_size, new_data + size, *value); + } else { + construct_range(new_data + m_size, new_data + size); + } + destroy_range(0, m_size); + rcFree(m_data); + m_data = new_data; + m_cap = new_cap; + m_size = size; + } + } +} +template +void rcVectorBase::swap(rcVectorBase& other) { + // TODO: Reorganize headers so we can use rcSwap here. + rcSizeType tmp_cap = other.m_cap; + rcSizeType tmp_size = other.m_size; + T* tmp_data = other.m_data; + + other.m_cap = m_cap; + other.m_size = m_size; + other.m_data = m_data; + + m_cap = tmp_cap; + m_size = tmp_size; + m_data = tmp_data; +} +// static +template +void rcVectorBase::construct_range(T* begin, T* end) { + for (T* p = begin; p < end; p++) { + construct(p); + } +} +// static +template +void rcVectorBase::construct_range(T* begin, T* end, const T& value) { + for (T* p = begin; p < end; p++) { + construct(p, value); + } +} +// static +template +void rcVectorBase::copy_range(T* dst, const T* begin, const T* end) { + for (rcSizeType i = 0 ; i < end - begin; i++) { + construct(dst + i, begin[i]); + } +} +template +void rcVectorBase::destroy_range(rcSizeType begin, rcSizeType end) { + for (rcSizeType i = begin; i < end; i++) { + m_data[i].~T(); + } +} + +template +class rcTempVector : public rcVectorBase { + typedef rcVectorBase Base; +public: + rcTempVector() : Base() {} + explicit rcTempVector(rcSizeType size) : Base(size) {} + rcTempVector(rcSizeType size, const T& value) : Base(size, value) {} + rcTempVector(const rcTempVector& other) : Base(other) {} + rcTempVector(const T* begin, const T* end) : Base(begin, end) {} +}; +template +class rcPermVector : public rcVectorBase { + typedef rcVectorBase Base; +public: + rcPermVector() : Base() {} + explicit rcPermVector(rcSizeType size) : Base(size) {} + rcPermVector(rcSizeType size, const T& value) : Base(size, value) {} + rcPermVector(const rcPermVector& other) : Base(other) {} + rcPermVector(const T* begin, const T* end) : Base(begin, end) {} +}; + + +/// Legacy class. Prefer rcVector. class rcIntArray { - int* m_data; - int m_size, m_cap; - - void doResize(int n); - - // Explicitly disabled copy constructor and copy assignment operator. - rcIntArray(const rcIntArray&); - rcIntArray& operator=(const rcIntArray&); - + rcTempVector m_impl; public: - /// Constructs an instance with an initial array size of zero. - rcIntArray() : m_data(0), m_size(0), m_cap(0) {} - - /// Constructs an instance initialized to the specified size. - /// @param[in] n The initial size of the integer array. - rcIntArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); } - ~rcIntArray() { rcFree(m_data); } - - /// Specifies the new size of the integer array. - /// @param[in] n The new size of the integer array. - void resize(int n) - { - if (n > m_cap) - doResize(n); - - m_size = n; - } - - /// Push the specified integer onto the end of the array and increases the size by one. - /// @param[in] item The new value. - void push(int item) { resize(m_size+1); m_data[m_size-1] = item; } - - /// Returns the value at the end of the array and reduces the size by one. - /// @return The value at the end of the array. + rcIntArray() {} + rcIntArray(int n) : m_impl(n, 0) {} + void push(int item) { m_impl.push_back(item); } + void resize(int size) { m_impl.resize(size); } + void clear() { m_impl.clear(); } int pop() { - if (m_size > 0) - m_size--; - - return m_data[m_size]; + int v = m_impl.back(); + m_impl.pop_back(); + return v; } - - /// The value at the specified array index. - /// @warning Does not provide overflow protection. - /// @param[in] i The index of the value. - const int& operator[](int i) const { return m_data[i]; } - - /// The value at the specified array index. - /// @warning Does not provide overflow protection. - /// @param[in] i The index of the value. - int& operator[](int i) { return m_data[i]; } - - /// The current size of the integer array. - int size() const { return m_size; } + int size() const { return static_cast(m_impl.size()); } + int& operator[](int index) { return m_impl[index]; } + int operator[](int index) const { return m_impl[index]; } }; /// A simple helper class used to delete an array when it goes out of scope. diff --git a/Recast/Include/RecastAssert.h b/Recast/Include/RecastAssert.h index e7cc10e4..617a4b75 100644 --- a/Recast/Include/RecastAssert.h +++ b/Recast/Include/RecastAssert.h @@ -19,13 +19,10 @@ #ifndef RECASTASSERT_H #define RECASTASSERT_H -// Note: This header file's only purpose is to include define assert. -// Feel free to change the file and include your own implementation instead. - #ifdef NDEBUG // From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ -# define rcAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false) +# define rcAssert(x) do { (void)sizeof(x); } while ((void)(__LINE__==-1), false) #else @@ -38,7 +35,7 @@ typedef void (rcAssertFailFunc)(const char* expression, const char* file, int li /// Sets the base custom assertion failure function to be used by Recast. /// @param[in] assertFailFunc The function to be used in case of failure of #dtAssert -void rcAssertFailSetCustom(rcAssertFailFunc *assertFailFunc); +void rcAssertFailSetCustom(rcAssertFailFunc* assertFailFunc); /// Gets the base custom assertion failure function to be used by Recast. rcAssertFailFunc* rcAssertFailGetCustom(); @@ -47,8 +44,8 @@ rcAssertFailFunc* rcAssertFailGetCustom(); # define rcAssert(expression) \ { \ rcAssertFailFunc* failFunc = rcAssertFailGetCustom(); \ - if(failFunc == NULL) { assert(expression); } \ - else if(!(expression)) { (*failFunc)(#expression, __FILE__, __LINE__); } \ + if (failFunc == NULL) { assert(expression); } \ + else if (!(expression)) { (*failFunc)(#expression, __FILE__, __LINE__); } \ } #endif diff --git a/Recast/Source/Recast.cpp b/Recast/Source/Recast.cpp index 4aefa682..237381f8 100644 --- a/Recast/Source/Recast.cpp +++ b/Recast/Source/Recast.cpp @@ -16,61 +16,73 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include -#include #include "Recast.h" #include "RecastAlloc.h" #include "RecastAssert.h" +#include +#include +#include +#include + +namespace +{ +/// Allocates and constructs an object of the given type, returning a pointer. +/// @param[in] allocLifetime Allocation lifetime hint +template +T* rcNew(const rcAllocHint allocLifetime) +{ + T* ptr = (T*)rcAlloc(sizeof(T), allocLifetime); + ::new(rcNewTag(), (void*)ptr) T(); + return ptr; +} + +/// Destroys and frees an object allocated with rcNew. +/// @param[in] ptr The object pointer to delete. +template +void rcDelete(T* ptr) +{ + if (ptr) + { + ptr->~T(); + rcFree((void*)ptr); + } +} +} // anonymous namespace + float rcSqrt(float x) { return sqrtf(x); } -/// @class rcContext -/// @par -/// -/// This class does not provide logging or timer functionality on its -/// own. Both must be provided by a concrete implementation -/// by overriding the protected member functions. Also, this class does not -/// provide an interface for extracting log messages. (Only adding them.) -/// So concrete implementations must provide one. -/// -/// If no logging or timers are required, just pass an instance of this -/// class through the Recast build process. -/// - -/// @par -/// -/// Example: -/// @code -/// // Where ctx is an instance of rcContext and filepath is a char array. -/// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath); -/// @endcode void rcContext::log(const rcLogCategory category, const char* format, ...) { if (!m_logEnabled) + { return; + } static const int MSG_SIZE = 512; char msg[MSG_SIZE]; - va_list ap; - va_start(ap, format); - int len = vsnprintf(msg, MSG_SIZE, format, ap); + va_list argList; + va_start(argList, format); + int len = vsnprintf(msg, MSG_SIZE, format, argList); if (len >= MSG_SIZE) { - len = MSG_SIZE-1; - msg[MSG_SIZE-1] = '\0'; + len = MSG_SIZE - 1; + msg[MSG_SIZE - 1] = '\0'; + + const char* errorMessage = "Log message was truncated"; + doLog(RC_LOG_ERROR, errorMessage, (int)strlen(errorMessage)); } - va_end(ap); + va_end(argList); doLog(category, msg, len); } +void rcContext::doResetLog() +{ + // Defined out of line to fix the weak v-tables warning +} + rcAreaModification::rcAreaModification(unsigned char value) : m_value(value), m_mask(RC_AREA_FLAGS_MASK) @@ -117,19 +129,24 @@ unsigned char rcAreaModification::getMaskedValue() const rcHeightfield* rcAllocHeightfield() { - return new (rcAlloc(sizeof(rcHeightfield), RC_ALLOC_PERM)) rcHeightfield; + return rcNew(RC_ALLOC_PERM); +} + +void rcFreeHeightField(rcHeightfield* heightfield) +{ + rcDelete(heightfield); } rcHeightfield::rcHeightfield() - : width() - , height() - , bmin() - , bmax() - , cs() - , ch() - , spans() - , pools() - , freelist() +: width() +, height() +, bmin() +, bmax() +, cs() +, ch() +, spans() +, pools() +, freelist() { } @@ -146,404 +163,425 @@ rcHeightfield::~rcHeightfield() } } -void rcFreeHeightField(rcHeightfield* hf) -{ - if (!hf) return; - hf->~rcHeightfield(); - rcFree(hf); -} - rcCompactHeightfield* rcAllocCompactHeightfield() { - rcCompactHeightfield* chf = (rcCompactHeightfield*)rcAlloc(sizeof(rcCompactHeightfield), RC_ALLOC_PERM); - memset(chf, 0, sizeof(rcCompactHeightfield)); - return chf; + return rcNew(RC_ALLOC_PERM); } -void rcFreeCompactHeightfield(rcCompactHeightfield* chf) +void rcFreeCompactHeightfield(rcCompactHeightfield* compactHeightfield) { - if (!chf) return; - rcFree(chf->cells); - rcFree(chf->spans); - rcFree(chf->dist); - rcFree(chf->areas); - rcFree(chf); + rcDelete(compactHeightfield); +} + +rcCompactHeightfield::rcCompactHeightfield() +: width() +, height() +, spanCount() +, walkableHeight() +, walkableClimb() +, borderSize() +, maxDistance() +, maxRegions() +, bmin() +, bmax() +, cs() +, ch() +, cells() +, spans() +, dist() +, areas() +{ +} + +rcCompactHeightfield::~rcCompactHeightfield() +{ + rcFree(cells); + rcFree(spans); + rcFree(dist); + rcFree(areas); } rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet() { - rcHeightfieldLayerSet* lset = (rcHeightfieldLayerSet*)rcAlloc(sizeof(rcHeightfieldLayerSet), RC_ALLOC_PERM); - memset(lset, 0, sizeof(rcHeightfieldLayerSet)); - return lset; + return rcNew(RC_ALLOC_PERM); } -void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset) +void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* layerSet) { - if (!lset) return; - for (int i = 0; i < lset->nlayers; ++i) + rcDelete(layerSet); +} + +rcHeightfieldLayerSet::rcHeightfieldLayerSet() +: layers() +, nlayers() +{ +} + +rcHeightfieldLayerSet::~rcHeightfieldLayerSet() +{ + for (int i = 0; i < nlayers; ++i) { - rcFree(lset->layers[i].heights); - rcFree(lset->layers[i].areas); - rcFree(lset->layers[i].cons); + rcFree(layers[i].heights); + rcFree(layers[i].areas); + rcFree(layers[i].cons); } - rcFree(lset->layers); - rcFree(lset); + rcFree(layers); } rcContourSet* rcAllocContourSet() { - rcContourSet* cset = (rcContourSet*)rcAlloc(sizeof(rcContourSet), RC_ALLOC_PERM); - memset(cset, 0, sizeof(rcContourSet)); - return cset; + return rcNew(RC_ALLOC_PERM); } -void rcFreeContourSet(rcContourSet* cset) +void rcFreeContourSet(rcContourSet* contourSet) { - if (!cset) return; - for (int i = 0; i < cset->nconts; ++i) + rcDelete(contourSet); +} + +rcContourSet::rcContourSet() +: conts() +, nconts() +, bmin() +, bmax() +, cs() +, ch() +, width() +, height() +, borderSize() +, maxError() +{ +} + +rcContourSet::~rcContourSet() +{ + for (int i = 0; i < nconts; ++i) { - rcFree(cset->conts[i].verts); - rcFree(cset->conts[i].rverts); + rcFree(conts[i].verts); + rcFree(conts[i].rverts); } - rcFree(cset->conts); - rcFree(cset); + rcFree(conts); } rcPolyMesh* rcAllocPolyMesh() { - rcPolyMesh* pmesh = (rcPolyMesh*)rcAlloc(sizeof(rcPolyMesh), RC_ALLOC_PERM); - memset(pmesh, 0, sizeof(rcPolyMesh)); - return pmesh; + return rcNew(RC_ALLOC_PERM); } -void rcFreePolyMesh(rcPolyMesh* pmesh) +void rcFreePolyMesh(rcPolyMesh* polyMesh) { - if (!pmesh) return; - rcFree(pmesh->verts); - rcFree(pmesh->polys); - rcFree(pmesh->regs); - rcFree(pmesh->flags); - rcFree(pmesh->areas); - rcFree(pmesh); + rcDelete(polyMesh); +} + +rcPolyMesh::rcPolyMesh() +: verts() +, polys() +, regs() +, flags() +, areas() +, nverts() +, npolys() +, maxpolys() +, nvp() +, bmin() +, bmax() +, cs() +, ch() +, borderSize() +, maxEdgeError() +{ +} + +rcPolyMesh::~rcPolyMesh() +{ + rcFree(verts); + rcFree(polys); + rcFree(regs); + rcFree(flags); + rcFree(areas); } rcPolyMeshDetail* rcAllocPolyMeshDetail() { - rcPolyMeshDetail* dmesh = (rcPolyMeshDetail*)rcAlloc(sizeof(rcPolyMeshDetail), RC_ALLOC_PERM); - memset(dmesh, 0, sizeof(rcPolyMeshDetail)); - return dmesh; + return rcNew(RC_ALLOC_PERM); } -void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh) +void rcFreePolyMeshDetail(rcPolyMeshDetail* detailMesh) { - if (!dmesh) return; - rcFree(dmesh->meshes); - rcFree(dmesh->verts); - rcFree(dmesh->tris); - rcFree(dmesh); + rcDelete(detailMesh); } -void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax) +rcPolyMeshDetail::rcPolyMeshDetail() +: meshes() +, verts() +, tris() +, nmeshes() +, nverts() +, ntris() +{ +} + +rcPolyMeshDetail::~rcPolyMeshDetail() +{ + rcFree(meshes); + rcFree(verts); + rcFree(tris); +} + +void rcCalcBounds(const float* verts, int numVerts, float* minBounds, float* maxBounds) { // Calculate bounding box. - rcVcopy(bmin, verts); - rcVcopy(bmax, verts); - for (int i = 1; i < nv; ++i) + rcVcopy(minBounds, verts); + rcVcopy(maxBounds, verts); + for (int i = 1; i < numVerts; ++i) { - const float* v = &verts[i*3]; - rcVmin(bmin, v); - rcVmax(bmax, v); + const float* v = &verts[i * 3]; + rcVmin(minBounds, v); + rcVmax(maxBounds, v); } } -void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h) +void rcCalcGridSize(const float* minBounds, const float* maxBounds, const float cellSize, int* sizeX, int* sizeZ) { - *w = (int)((bmax[0] - bmin[0])/cs+0.5f); - *h = (int)((bmax[2] - bmin[2])/cs+0.5f); + *sizeX = (int)((maxBounds[0] - minBounds[0]) / cellSize + 0.5f); + *sizeZ = (int)((maxBounds[2] - minBounds[2]) / cellSize + 0.5f); } -/// @par -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocHeightfield, rcHeightfield -bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, - const float* bmin, const float* bmax, - float cs, float ch) +bool rcCreateHeightfield(rcContext* context, rcHeightfield& heightfield, int sizeX, int sizeZ, + const float* minBounds, const float* maxBounds, + float cellSize, float cellHeight) { - rcIgnoreUnused(ctx); - - hf.width = width; - hf.height = height; - rcVcopy(hf.bmin, bmin); - rcVcopy(hf.bmax, bmax); - hf.cs = cs; - hf.ch = ch; - hf.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*)*hf.width*hf.height, RC_ALLOC_PERM); - if (!hf.spans) + rcIgnoreUnused(context); + + heightfield.width = sizeX; + heightfield.height = sizeZ; + rcVcopy(heightfield.bmin, minBounds); + rcVcopy(heightfield.bmax, maxBounds); + heightfield.cs = cellSize; + heightfield.ch = cellHeight; + heightfield.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*) * heightfield.width * heightfield.height, RC_ALLOC_PERM); + if (!heightfield.spans) + { return false; - memset(hf.spans, 0, sizeof(rcSpan*)*hf.width*hf.height); + } + memset(heightfield.spans, 0, sizeof(rcSpan*) * heightfield.width * heightfield.height); return true; } -static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* norm) +static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* faceNormal) { float e0[3], e1[3]; rcVsub(e0, v1, v0); rcVsub(e1, v2, v0); - rcVcross(norm, e0, e1); - rcVnormalize(norm); + rcVcross(faceNormal, e0, e1); + rcVnormalize(faceNormal); } -/// @par -/// -/// Only sets the area id's for the walkable triangles. Does not alter the -/// area id's for unwalkable triangles. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, - const float* verts, int nv, - const int* tris, int nt, - unsigned char* areas, - rcAreaModification areaMod) +void rcMarkWalkableTriangles(rcContext* context, const float walkableSlopeAngle, + const float* verts, const int numVerts, + const int* tris, const int numTris, + unsigned char* triAreaIDs, + rcAreaModification areaMod) { - rcIgnoreUnused(ctx); - rcIgnoreUnused(nv); - - const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); + rcIgnoreUnused(context); + rcIgnoreUnused(numVerts); + + const float walkableThr = cosf(walkableSlopeAngle / 180.0f * RC_PI); float norm[3]; - - for (int i = 0; i < nt; ++i) + + for (int i = 0; i < numTris; ++i) { - const int* tri = &tris[i*3]; - calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); + const int* tri = &tris[i * 3]; + calcTriNormal(&verts[tri[0] * 3], &verts[tri[1] * 3], &verts[tri[2] * 3], norm); // Check if the face is walkable. if (norm[1] > walkableThr) - areaMod.apply(areas[i]); - } -} - -/// @par -/// -/// Only sets the area id's for the unwalkable triangles. Does not alter the -/// area id's for walkable triangles. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, - const float* verts, int /*nv*/, - const int* tris, int nt, - unsigned char* areas) -{ - rcIgnoreUnused(ctx); - - const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); - - float norm[3]; - - for (int i = 0; i < nt; ++i) - { - const int* tri = &tris[i*3]; - calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); - // Check if the face is walkable. - if (norm[1] <= walkableThr) - areas[i] = RC_NULL_AREA; - } -} - -int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf) -{ - rcIgnoreUnused(ctx); - - const int w = hf.width; - const int h = hf.height; - int spanCount = 0; - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) { - for (rcSpan* s = hf.spans[x + y*w]; s; s = s->next) + areaMod.apply(triAreaIDs[i]); + } + } +} + +void rcClearUnwalkableTriangles(rcContext* context, const float walkableSlopeAngle, + const float* verts, int numVerts, + const int* tris, int numTris, + unsigned char* triAreaIDs) +{ + rcIgnoreUnused(context); + rcIgnoreUnused(numVerts); + + // The minimum Y value for a face normal of a triangle with a walkable slope. + const float walkableLimitY = cosf(walkableSlopeAngle / 180.0f * RC_PI); + + float faceNormal[3]; + for (int i = 0; i < numTris; ++i) + { + const int* tri = &tris[i * 3]; + calcTriNormal(&verts[tri[0] * 3], &verts[tri[1] * 3], &verts[tri[2] * 3], faceNormal); + // Check if the face is walkable. + if (faceNormal[1] <= walkableLimitY) + { + triAreaIDs[i] = RC_NULL_AREA; + } + } +} + +int rcGetHeightFieldSpanCount(rcContext* context, const rcHeightfield& heightfield) +{ + rcIgnoreUnused(context); + + const int numCols = heightfield.width * heightfield.height; + int spanCount = 0; + for (int columnIndex = 0; columnIndex < numCols; ++columnIndex) + { + for (rcSpan* span = heightfield.spans[columnIndex]; span != NULL; span = span->next) + { + if (span->area != RC_NULL_AREA) { - if (s->area != RC_NULL_AREA) - spanCount++; + spanCount++; } } } return spanCount; } -/// @par -/// -/// This is just the beginning of the process of fully building a compact heightfield. -/// Various filters may be applied, then the distance field and regions built. -/// E.g: #rcBuildDistanceField and #rcBuildRegions -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& hf, rcCompactHeightfield& chf) +bool rcBuildCompactHeightfield(rcContext* context, const int walkableHeight, const int walkableClimb, + rcHeightfield& heightfield, rcCompactHeightfield& compactHeightfield) { - rcAssert(ctx); - - rcScopedTimer timer(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD); - - const int w = hf.width; - const int h = hf.height; - const int spanCount = rcGetHeightFieldSpanCount(ctx, hf); + rcAssert(context); + + rcScopedTimer timer(context, RC_TIMER_BUILD_COMPACTHEIGHTFIELD); + + const int xSize = heightfield.width; + const int zSize = heightfield.height; + const int spanCount = rcGetHeightFieldSpanCount(context, heightfield); // Fill in header. - chf.width = w; - chf.height = h; - chf.spanCount = spanCount; - chf.walkableHeight = walkableHeight; - chf.walkableClimb = walkableClimb; - chf.maxRegions = 0; - rcVcopy(chf.bmin, hf.bmin); - rcVcopy(chf.bmax, hf.bmax); - chf.bmax[1] += walkableHeight*hf.ch; - chf.cs = hf.cs; - chf.ch = hf.ch; - chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); - if (!chf.cells) + compactHeightfield.width = xSize; + compactHeightfield.height = zSize; + compactHeightfield.spanCount = spanCount; + compactHeightfield.walkableHeight = walkableHeight; + compactHeightfield.walkableClimb = walkableClimb; + compactHeightfield.maxRegions = 0; + rcVcopy(compactHeightfield.bmin, heightfield.bmin); + rcVcopy(compactHeightfield.bmax, heightfield.bmax); + compactHeightfield.bmax[1] += walkableHeight * heightfield.ch; + compactHeightfield.cs = heightfield.cs; + compactHeightfield.ch = heightfield.ch; + compactHeightfield.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell) * xSize * zSize, RC_ALLOC_PERM); + if (!compactHeightfield.cells) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", xSize * zSize); return false; } - memset(chf.cells, 0, sizeof(rcCompactCell)*w*h); - chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); - if (!chf.spans) + memset(compactHeightfield.cells, 0, sizeof(rcCompactCell) * xSize * zSize); + compactHeightfield.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan) * spanCount, RC_ALLOC_PERM); + if (!compactHeightfield.spans) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); return false; } - memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); - chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM); - if (!chf.areas) + memset(compactHeightfield.spans, 0, sizeof(rcCompactSpan) * spanCount); + compactHeightfield.areas = (unsigned char*)rcAlloc(sizeof(unsigned char) * spanCount, RC_ALLOC_PERM); + if (!compactHeightfield.areas) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); return false; } - memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount); - + memset(compactHeightfield.areas, RC_NULL_AREA, sizeof(unsigned char) * spanCount); + const int MAX_HEIGHT = 0xffff; - + // Fill in cells and spans. - int idx = 0; - for (int y = 0; y < h; ++y) + int currentCellIndex = 0; + const int numColumns = xSize * zSize; + for (int columnIndex = 0; columnIndex < numColumns; ++columnIndex) { - for (int x = 0; x < w; ++x) + const rcSpan* span = heightfield.spans[columnIndex]; + + // If there are no spans at this cell, just leave the data to index=0, count=0. + if (span == NULL) { - const rcSpan* s = hf.spans[x + y*w]; - // If there are no spans at this cell, just leave the data to index=0, count=0. - if (!s) continue; - rcCompactCell& c = chf.cells[x+y*w]; - c.index = idx; - c.count = 0; - while (s) + continue; + } + + rcCompactCell& cell = compactHeightfield.cells[columnIndex]; + cell.index = currentCellIndex; + cell.count = 0; + + for (; span != NULL; span = span->next) + { + if (span->area != RC_NULL_AREA) { - if (s->area != RC_NULL_AREA) - { - const int bot = (int)s->smax; - const int top = s->next ? (int)s->next->smin : MAX_HEIGHT; - chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff); - chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff); - chf.areas[idx] = s->area; - idx++; - c.count++; - } - s = s->next; + const int bot = (int)span->smax; + const int top = span->next ? (int)span->next->smin : MAX_HEIGHT; + compactHeightfield.spans[currentCellIndex].y = (unsigned short)rcClamp(bot, 0, 0xffff); + compactHeightfield.spans[currentCellIndex].h = (unsigned char)rcClamp(top - bot, 0, 0xff); + compactHeightfield.areas[currentCellIndex] = span->area; + currentCellIndex++; + cell.count++; } } } - + // Find neighbour connections. - const int MAX_LAYERS = RC_NOT_CONNECTED-1; - int tooHighNeighbour = 0; - for (int y = 0; y < h; ++y) + const int MAX_LAYERS = RC_NOT_CONNECTED - 1; + int maxLayerIndex = 0; + const int zStride = xSize; // for readability + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + const rcCompactCell& cell = compactHeightfield.cells[x + z * zStride]; + for (int i = (int)cell.index, ni = (int)(cell.index + cell.count); i < ni; ++i) { - rcCompactSpan& s = chf.spans[i]; - + rcCompactSpan& span = compactHeightfield.spans[i]; + for (int dir = 0; dir < 4; ++dir) { - rcSetCon(s, dir, RC_NOT_CONNECTED); - const int nx = x + rcGetDirOffsetX(dir); - const int ny = y + rcGetDirOffsetY(dir); + rcSetCon(span, dir, RC_NOT_CONNECTED); + const int neighborX = x + rcGetDirOffsetX(dir); + const int neighborZ = z + rcGetDirOffsetY(dir); // First check that the neighbour cell is in bounds. - if (nx < 0 || ny < 0 || nx >= w || ny >= h) + if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize) + { continue; - + } + // Iterate over all neighbour spans and check if any of the is // accessible from current cell. - const rcCompactCell& nc = chf.cells[nx+ny*w]; - for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k) + const rcCompactCell& neighborCell = compactHeightfield.cells[neighborX + neighborZ * zStride]; + for (int k = (int)neighborCell.index, nk = (int)(neighborCell.index + neighborCell.count); k < nk; ++k) { - const rcCompactSpan& ns = chf.spans[k]; - const int bot = rcMax(s.y, ns.y); - const int top = rcMin(s.y+s.h, ns.y+ns.h); + const rcCompactSpan& neighborSpan = compactHeightfield.spans[k]; + const int bot = rcMax(span.y, neighborSpan.y); + const int top = rcMin(span.y + span.h, neighborSpan.y + neighborSpan.h); // Check that the gap between the spans is walkable, // and that the climb height between the gaps is not too high. - if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) + if ((top - bot) >= walkableHeight && rcAbs((int)neighborSpan.y - (int)span.y) <= walkableClimb) { // Mark direction as walkable. - const int lidx = k - (int)nc.index; - if (lidx < 0 || lidx > MAX_LAYERS) + const int layerIndex = k - (int)neighborCell.index; + if (layerIndex < 0 || layerIndex > MAX_LAYERS) { - tooHighNeighbour = rcMax(tooHighNeighbour, lidx); + maxLayerIndex = rcMax(maxLayerIndex, layerIndex); continue; } - rcSetCon(s, dir, lidx); + rcSetCon(span, dir, layerIndex); break; } } - } } } } - - if (tooHighNeighbour > MAX_LAYERS) + + if (maxLayerIndex > MAX_LAYERS) { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", - tooHighNeighbour, MAX_LAYERS); + context->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", + maxLayerIndex, MAX_LAYERS); } - + return true; } - -/* -static int getHeightfieldMemoryUsage(const rcHeightfield& hf) -{ - int size = 0; - size += sizeof(hf); - size += hf.width * hf.height * sizeof(rcSpan*); - - rcSpanPool* pool = hf.pools; - while (pool) - { - size += (sizeof(rcSpanPool) - sizeof(rcSpan)) + sizeof(rcSpan)*RC_SPANS_PER_POOL; - pool = pool->next; - } - return size; -} - -static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf) -{ - int size = 0; - size += sizeof(rcCompactHeightfield); - size += sizeof(rcCompactSpan) * chf.spanCount; - size += sizeof(rcCompactCell) * chf.width * chf.height; - return size; -} -*/ diff --git a/Recast/Source/RecastAlloc.cpp b/Recast/Source/RecastAlloc.cpp index 453b5fa6..e919daf7 100644 --- a/Recast/Source/RecastAlloc.cpp +++ b/Recast/Source/RecastAlloc.cpp @@ -16,12 +16,9 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include -#include #include "RecastAlloc.h" -#include "RecastAssert.h" -static void *rcAllocDefault(size_t size, rcAllocHint) +static void* rcAllocDefault(size_t size, rcAllocHint) { return malloc(size); } @@ -34,53 +31,21 @@ static void rcFreeDefault(void *ptr) static rcAllocFunc* sRecastAllocFunc = rcAllocDefault; static rcFreeFunc* sRecastFreeFunc = rcFreeDefault; -/// @see rcAlloc, rcFree -void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc) +void rcAllocSetCustom(rcAllocFunc* allocFunc, rcFreeFunc* freeFunc) { sRecastAllocFunc = allocFunc ? allocFunc : rcAllocDefault; sRecastFreeFunc = freeFunc ? freeFunc : rcFreeDefault; } -/// @see rcAllocSetCustom void* rcAlloc(size_t size, rcAllocHint hint) { return sRecastAllocFunc(size, hint); } -/// @par -/// -/// @warning This function leaves the value of @p ptr unchanged. So it still -/// points to the same (now invalid) location, and not to null. -/// -/// @see rcAllocSetCustom void rcFree(void* ptr) { - if (ptr) + if (ptr != NULL) + { sRecastFreeFunc(ptr); + } } - -/// @class rcIntArray -/// -/// While it is possible to pre-allocate a specific array size during -/// construction or by using the #resize method, certain methods will -/// automatically resize the array as needed. -/// -/// @warning The array memory is not initialized to zero when the size is -/// manually set during construction or when using #resize. - -/// @par -/// -/// Using this method ensures the array is at least large enough to hold -/// the specified number of elements. This can improve performance by -/// avoiding auto-resizing during use. -void rcIntArray::doResize(int n) -{ - if (!m_cap) m_cap = n; - while (m_cap < n) m_cap *= 2; - int* newData = (int*)rcAlloc(m_cap*sizeof(int), RC_ALLOC_TEMP); - rcAssert(newData); - if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int)); - rcFree(m_data); - m_data = newData; -} - diff --git a/Recast/Source/RecastArea.cpp b/Recast/Source/RecastArea.cpp index 92d8b205..f10a4e8e 100644 --- a/Recast/Source/RecastArea.cpp +++ b/Recast/Source/RecastArea.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include diff --git a/Recast/Source/RecastAssert.cpp b/Recast/Source/RecastAssert.cpp index 6297d420..973b6811 100644 --- a/Recast/Source/RecastAssert.cpp +++ b/Recast/Source/RecastAssert.cpp @@ -22,7 +22,7 @@ static rcAssertFailFunc* sRecastAssertFailFunc = 0; -void rcAssertFailSetCustom(rcAssertFailFunc *assertFailFunc) +void rcAssertFailSetCustom(rcAssertFailFunc* assertFailFunc) { sRecastAssertFailFunc = assertFailFunc; } diff --git a/Recast/Source/RecastContour.cpp b/Recast/Source/RecastContour.cpp index 277ab015..57f39815 100644 --- a/Recast/Source/RecastContour.cpp +++ b/Recast/Source/RecastContour.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -921,8 +920,8 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, continue; const unsigned char area = chf.areas[i]; - verts.resize(0); - simplified.resize(0); + verts.clear(); + simplified.clear(); ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); walkContour(x, y, i, chf, flags, verts); @@ -1009,7 +1008,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, if (cset.nconts > 0) { // Calculate winding of all polygons. - rcScopedDelete winding((char*)rcAlloc(sizeof(char)*cset.nconts, RC_ALLOC_TEMP)); + rcScopedDelete winding((signed char*)rcAlloc(sizeof(signed char)*cset.nconts, RC_ALLOC_TEMP)); if (!winding) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'hole' (%d).", cset.nconts); diff --git a/Recast/Source/RecastFilter.cpp b/Recast/Source/RecastFilter.cpp index 9d3e63c4..e980a830 100644 --- a/Recast/Source/RecastFilter.cpp +++ b/Recast/Source/RecastFilter.cpp @@ -16,186 +16,168 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES -#include -#include #include "Recast.h" #include "RecastAssert.h" -/// @par -/// -/// Allows the formation of walkable regions that will flow over low lying -/// objects such as curbs, and up structures such as stairways. -/// -/// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb -/// -/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call -/// #rcFilterLedgeSpans after calling this filter. -/// -/// @see rcHeightfield, rcConfig -void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid) -{ - rcAssert(ctx); +#include - rcScopedTimer timer(ctx, RC_TIMER_FILTER_LOW_OBSTACLES); - - const int w = solid.width; - const int h = solid.height; - - for (int y = 0; y < h; ++y) +void rcFilterLowHangingWalkableObstacles(rcContext* context, const int walkableClimb, rcHeightfield& heightfield) +{ + rcAssert(context); + + rcScopedTimer timer(context, RC_TIMER_FILTER_LOW_OBSTACLES); + + const int xSize = heightfield.width; + const int zSize = heightfield.height; + + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - rcSpan* ps = 0; - bool previousWalkable = false; + rcSpan* previousSpan = NULL; + bool previousWasWalkable = false; unsigned char previousArea = RC_NULL_AREA; - - for (rcSpan* s = solid.spans[x + y*w]; s; ps = s, s = s->next) + + for (rcSpan* span = heightfield.spans[x + z * xSize]; span != NULL; previousSpan = span, span = span->next) { - const bool walkable = s->area != RC_NULL_AREA; + const bool walkable = span->area != RC_NULL_AREA; // If current span is not walkable, but there is walkable // span just below it, mark the span above it walkable too. - if (!walkable && previousWalkable) + if (!walkable && previousWasWalkable) { - if (rcAbs((int)s->smax - (int)ps->smax) <= walkableClimb) - s->area = previousArea; + if (rcAbs((int)span->smax - (int)previousSpan->smax) <= walkableClimb) + { + span->area = previousArea; + } } // Copy walkable flag so that it cannot propagate // past multiple non-walkable objects. - previousWalkable = walkable; - previousArea = s->area; + previousWasWalkable = walkable; + previousArea = span->area; } } } } -/// @par -/// -/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb -/// from the current span's maximum. -/// This method removes the impact of the overestimation of conservative voxelization -/// so the resulting mesh will not have regions hanging in the air over ledges. -/// -/// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb -/// -/// @see rcHeightfield, rcConfig -void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& solid) +void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int walkableClimb, + rcHeightfield& heightfield) { - rcAssert(ctx); + rcAssert(context); - rcScopedTimer timer(ctx, RC_TIMER_FILTER_BORDER); + rcScopedTimer timer(context, RC_TIMER_FILTER_BORDER); - const int w = solid.width; - const int h = solid.height; - const int MAX_HEIGHT = 0xffff; + const int xSize = heightfield.width; + const int zSize = heightfield.height; + const int MAX_HEIGHT = 0xffff; // TODO (graham): Move this to a more visible constant and update usages. // Mark border spans. - for (int y = 0; y < h; ++y) + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) + for (rcSpan* span = heightfield.spans[x + z * xSize]; span; span = span->next) { // Skip non walkable spans. - if (s->area == RC_NULL_AREA) + if (span->area == RC_NULL_AREA) + { continue; - - const int bot = (int)(s->smax); - const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; - + } + + const int bot = (int)(span->smax); + const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHT; + // Find neighbours minimum height. - int minh = MAX_HEIGHT; + int minNeighborHeight = MAX_HEIGHT; // Min and max height of accessible neighbours. - int asmin = s->smax; - int asmax = s->smax; + int accessibleNeighborMinHeight = span->smax; + int accessibleNeighborMaxHeight = span->smax; - for (int dir = 0; dir < 4; ++dir) + for (int direction = 0; direction < 4; ++direction) { - int dx = x + rcGetDirOffsetX(dir); - int dy = y + rcGetDirOffsetY(dir); + int dx = x + rcGetDirOffsetX(direction); + int dy = z + rcGetDirOffsetY(direction); // Skip neighbours which are out of bounds. - if (dx < 0 || dy < 0 || dx >= w || dy >= h) + if (dx < 0 || dy < 0 || dx >= xSize || dy >= zSize) { - minh = rcMin(minh, -walkableClimb - bot); + minNeighborHeight = rcMin(minNeighborHeight, -walkableClimb - bot); continue; } // From minus infinity to the first span. - rcSpan* ns = solid.spans[dx + dy*w]; - int nbot = -walkableClimb; - int ntop = ns ? (int)ns->smin : MAX_HEIGHT; - // Skip neightbour if the gap between the spans is too small. - if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) - minh = rcMin(minh, nbot - bot); + const rcSpan* neighborSpan = heightfield.spans[dx + dy * xSize]; + int neighborBot = -walkableClimb; + int neighborTop = neighborSpan ? (int)neighborSpan->smin : MAX_HEIGHT; - // Rest of the spans. - for (ns = solid.spans[dx + dy*w]; ns; ns = ns->next) + // Skip neighbour if the gap between the spans is too small. + if (rcMin(top, neighborTop) - rcMax(bot, neighborBot) > walkableHeight) { - nbot = (int)ns->smax; - ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT; - // Skip neightbour if the gap between the spans is too small. - if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) - { - minh = rcMin(minh, nbot - bot); + minNeighborHeight = rcMin(minNeighborHeight, neighborBot - bot); + } + + // Rest of the spans. + for (neighborSpan = heightfield.spans[dx + dy * xSize]; neighborSpan; neighborSpan = neighborSpan->next) + { + neighborBot = (int)neighborSpan->smax; + neighborTop = neighborSpan->next ? (int)neighborSpan->next->smin : MAX_HEIGHT; + // Skip neighbour if the gap between the spans is too small. + if (rcMin(top, neighborTop) - rcMax(bot, neighborBot) > walkableHeight) + { + minNeighborHeight = rcMin(minNeighborHeight, neighborBot - bot); + // Find min/max accessible neighbour height. - if (rcAbs(nbot - bot) <= walkableClimb) + if (rcAbs(neighborBot - bot) <= walkableClimb) { - if (nbot < asmin) asmin = nbot; - if (nbot > asmax) asmax = nbot; + if (neighborBot < accessibleNeighborMinHeight) accessibleNeighborMinHeight = neighborBot; + if (neighborBot > accessibleNeighborMaxHeight) accessibleNeighborMaxHeight = neighborBot; } - + } } } - + // The current span is close to a ledge if the drop to any // neighbour span is less than the walkableClimb. - if (minh < -walkableClimb) + if (minNeighborHeight < -walkableClimb) { - s->area = RC_NULL_AREA; + span->area = RC_NULL_AREA; } // If the difference between all neighbours is too large, // we are at steep slope, mark the span as ledge. - else if ((asmax - asmin) > walkableClimb) + else if ((accessibleNeighborMaxHeight - accessibleNeighborMinHeight) > walkableClimb) { - s->area = RC_NULL_AREA; + span->area = RC_NULL_AREA; } } } } } -/// @par -/// -/// For this filter, the clearance above the span is the distance from the span's -/// maximum to the next higher span's minimum. (Same grid column.) -/// -/// @see rcHeightfield, rcConfig -void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid) +void rcFilterWalkableLowHeightSpans(rcContext* context, const int walkableHeight, rcHeightfield& heightfield) { - rcAssert(ctx); + rcAssert(context); - rcScopedTimer timer(ctx, RC_TIMER_FILTER_WALKABLE); + rcScopedTimer timer(context, RC_TIMER_FILTER_WALKABLE); - const int w = solid.width; - const int h = solid.height; + const int xSize = heightfield.width; + const int zSize = heightfield.height; const int MAX_HEIGHT = 0xffff; // Remove walkable flag from spans which do not have enough // space above them for the agent to stand there. - for (int y = 0; y < h; ++y) + for (int z = 0; z < zSize; ++z) { - for (int x = 0; x < w; ++x) + for (int x = 0; x < xSize; ++x) { - for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) + for (rcSpan* span = heightfield.spans[x + z*xSize]; span; span = span->next) { - const int bot = (int)(s->smax); - const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; + const int bot = (int)(span->smax); + const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHT; if ((top - bot) <= walkableHeight) - s->area = RC_NULL_AREA; + { + span->area = RC_NULL_AREA; + } } } } diff --git a/Recast/Source/RecastLayers.cpp b/Recast/Source/RecastLayers.cpp index acc97e44..d5eb06a4 100644 --- a/Recast/Source/RecastLayers.cpp +++ b/Recast/Source/RecastLayers.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include diff --git a/Recast/Source/RecastMesh.cpp b/Recast/Source/RecastMesh.cpp index e99eaebb..9bffd6de 100644 --- a/Recast/Source/RecastMesh.cpp +++ b/Recast/Source/RecastMesh.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -566,7 +565,6 @@ static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned sho const int nvp = mesh.nvp; // Count number of polygons to remove. - int numRemovedVerts = 0; int numTouchedVerts = 0; int numRemainingEdges = 0; for (int i = 0; i < mesh.npolys; ++i) @@ -586,7 +584,6 @@ static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned sho } if (numRemoved) { - numRemovedVerts += numRemoved; numRemainingEdges += numVerts-(numRemoved+1); } } diff --git a/Recast/Source/RecastMeshDetail.cpp b/Recast/Source/RecastMeshDetail.cpp index f953132f..40f5b8c6 100644 --- a/Recast/Source/RecastMeshDetail.cpp +++ b/Recast/Source/RecastMeshDetail.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include @@ -284,7 +283,7 @@ static unsigned short getHeight(const float fx, const float fy, const float fz, enum EdgeValues { EV_UNDEF = -1, - EV_HULL = -2, + EV_HULL = -2 }; static int findEdge(const int* edges, int nedges, int s, int t) @@ -557,15 +556,16 @@ static float polyMinExtent(const float* verts, const int nverts) inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; } inline int next(int i, int n) { return i+1 < n ? i+1 : 0; } -static void triangulateHull(const int /*nverts*/, const float* verts, const int nhull, const int* hull, rcIntArray& tris) +static void triangulateHull(const int /*nverts*/, const float* verts, const int nhull, const int* hull, const int nin, rcIntArray& tris) { int start = 0, left = 1, right = nhull-1; // Start from an ear with shortest perimeter. // This tends to favor well formed triangles as starting point. - float dmin = 0; + float dmin = FLT_MAX; for (int i = 0; i < nhull; i++) { + if (hull[i] >= nin) continue; // Ears are triangles with original vertices as middle vertex while others are actually line segments on edges int pi = prev(i, nhull); int ni = next(i, nhull); const float* pv = &verts[hull[pi]*3]; @@ -652,8 +652,8 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, for (int i = 0; i < nin; ++i) rcVcopy(&verts[i*3], &in[i*3]); - edges.resize(0); - tris.resize(0); + edges.clear(); + tris.clear(); const float cs = chf.cs; const float ics = 1.0f/cs; @@ -770,7 +770,7 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, // If the polygon minimum extent is small (sliver or small triangle), do not try to add internal points. if (minExtent < sampleDist*2) { - triangulateHull(nverts, verts, nhull, hull, tris); + triangulateHull(nverts, verts, nhull, hull, nin, tris); return true; } @@ -778,7 +778,7 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, // We're using the triangulateHull instead of delaunayHull as it tends to // create a bit better triangulation for long thin triangles when there // are no internal points. - triangulateHull(nverts, verts, nhull, hull, tris); + triangulateHull(nverts, verts, nhull, hull, nin, tris); if (tris.size() == 0) { @@ -802,7 +802,7 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, int x1 = (int)ceilf(bmax[0]/sampleDist); int z0 = (int)floorf(bmin[2]/sampleDist); int z1 = (int)ceilf(bmax[2]/sampleDist); - samples.resize(0); + samples.clear(); for (int z = z0; z < z1; ++z) { for (int x = x0; x < x1; ++x) @@ -863,8 +863,8 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, // Create new triangulation. // TODO: Incremental add instead of full rebuild. - edges.resize(0); - tris.resize(0); + edges.clear(); + tris.clear(); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); } } @@ -934,7 +934,7 @@ static void seedArrayWithPolyCenter(rcContext* ctx, const rcCompactHeightfield& pcy /= npoly; // Use seeds array as a stack for DFS - array.resize(0); + array.clear(); array.push(startCellX); array.push(startCellY); array.push(startSpanIndex); @@ -1000,7 +1000,7 @@ static void seedArrayWithPolyCenter(rcContext* ctx, const rcCompactHeightfield& rcSwap(dirs[directDir], dirs[3]); } - array.resize(0); + array.clear(); // getHeightData seeds are given in coordinates with borders array.push(cx+bs); array.push(cy+bs); @@ -1029,7 +1029,7 @@ static void getHeightData(rcContext* ctx, const rcCompactHeightfield& chf, // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. - queue.resize(0); + queue.clear(); // Set all heights to RC_UNSET_HEIGHT. memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); @@ -1140,7 +1140,8 @@ static void getHeightData(rcContext* ctx, const rcCompactHeightfield& chf, static unsigned char getEdgeFlags(const float* va, const float* vb, const float* vpoly, const int npoly) { - // Return true if edge (va,vb) is part of the polygon. + // The flag returned by this function matches dtDetailTriEdgeFlags in Detour. + // Figure out if edge (va,vb) is part of the polygon boundary. static const float thrSqr = rcSqr(0.001f); for (int i = 0, j = npoly-1; i < npoly; j=i++) { diff --git a/Recast/Source/RecastRasterization.cpp b/Recast/Source/RecastRasterization.cpp index a4cef749..2a4f619f 100644 --- a/Recast/Source/RecastRasterization.cpp +++ b/Recast/Source/RecastRasterization.cpp @@ -16,373 +16,485 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include "Recast.h" #include "RecastAlloc.h" #include "RecastAssert.h" -inline bool overlapBounds(const float* amin, const float* amax, const float* bmin, const float* bmax) +/// Check whether two bounding boxes overlap +/// +/// @param[in] aMin Min axis extents of bounding box A +/// @param[in] aMax Max axis extents of bounding box A +/// @param[in] bMin Min axis extents of bounding box B +/// @param[in] bMax Max axis extents of bounding box B +/// @returns true if the two bounding boxes overlap. False otherwise. +static bool overlapBounds(const float* aMin, const float* aMax, const float* bMin, const float* bMax) { - bool overlap = true; - overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; - overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; - overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; - return overlap; + return + aMin[0] <= bMax[0] && aMax[0] >= bMin[0] && + aMin[1] <= bMax[1] && aMax[1] >= bMin[1] && + aMin[2] <= bMax[2] && aMax[2] >= bMin[2]; } -inline bool overlapInterval(unsigned short amin, unsigned short amax, - unsigned short bmin, unsigned short bmax) -{ - if (amax < bmin) return false; - if (amin > bmax) return false; - return true; -} - - +/// Allocates a new span in the heightfield. +/// Use a memory pool and free list to minimize actual allocations. +/// +/// @param[in] hf The heightfield +/// @returns A pointer to the allocated or re-used span memory. static rcSpan* allocSpan(rcHeightfield& hf) { - // If running out of memory, allocate new page and update the freelist. - if (!hf.freelist || !hf.freelist->next) + // If necessary, allocate new page and update the freelist. + if (hf.freelist == NULL || hf.freelist->next == NULL) { // Create new page. // Allocate memory for the new pool. - rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); - if (!pool) return 0; + rcSpanPool* spanPool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); + if (spanPool == NULL) + { + return NULL; + } // Add the pool into the list of pools. - pool->next = hf.pools; - hf.pools = pool; - // Add new items to the free list. - rcSpan* freelist = hf.freelist; - rcSpan* head = &pool->items[0]; - rcSpan* it = &pool->items[RC_SPANS_PER_POOL]; + spanPool->next = hf.pools; + hf.pools = spanPool; + + // Add new spans to the free list. + rcSpan* freeList = hf.freelist; + rcSpan* head = &spanPool->items[0]; + rcSpan* it = &spanPool->items[RC_SPANS_PER_POOL]; do { --it; - it->next = freelist; - freelist = it; + it->next = freeList; + freeList = it; } while (it != head); hf.freelist = it; } - - // Pop item from in front of the free list. - rcSpan* it = hf.freelist; + + // Pop item from the front of the free list. + rcSpan* newSpan = hf.freelist; hf.freelist = hf.freelist->next; - return it; + return newSpan; } -static void freeSpan(rcHeightfield& hf, rcSpan* ptr) +/// Releases the memory used by the span back to the heightfield, so it can be re-used for new spans. +/// @param[in] hf The heightfield. +/// @param[in] span A pointer to the span to free +static void freeSpan(rcHeightfield& hf, rcSpan* span) { - if (!ptr) return; - // Add the node in front of the free list. - ptr->next = hf.freelist; - hf.freelist = ptr; -} - -static bool addSpan(rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr) -{ - - int idx = x + y*hf.width; - - rcSpan* s = allocSpan(hf); - if (!s) - return false; - s->smin = smin; - s->smax = smax; - s->area = area; - s->next = 0; - - // Empty cell, add the first span. - if (!hf.spans[idx]) + if (span == NULL) { - hf.spans[idx] = s; - return true; + return; } - rcSpan* prev = 0; - rcSpan* cur = hf.spans[idx]; - - // Insert and merge spans. - while (cur) + // Add the span to the front of the free list. + span->next = hf.freelist; + hf.freelist = span; +} + +/// Adds a span to the heightfield. If the new span overlaps existing spans, +/// it will merge the new span with the existing ones. +/// +/// @param[in] hf Heightfield to add spans to +/// @param[in] x The new span's column cell x index +/// @param[in] z The new span's column cell z index +/// @param[in] min The new span's minimum cell index +/// @param[in] max The new span's maximum cell index +/// @param[in] areaID The new span's area type ID +/// @param[in] flagMergeThreshold How close two spans maximum extents need to be to merge area type IDs +static bool addSpan(rcHeightfield& hf, + const int x, const int z, + const unsigned short min, const unsigned short max, + const unsigned char areaID, const int flagMergeThreshold) +{ + // Create the new span. + rcSpan* newSpan = allocSpan(hf); + if (newSpan == NULL) { - if (cur->smin > s->smax) + return false; + } + newSpan->smin = min; + newSpan->smax = max; + newSpan->area = areaID; + newSpan->next = NULL; + + const int columnIndex = x + z * hf.width; + rcSpan* previousSpan = NULL; + rcSpan* currentSpan = hf.spans[columnIndex]; + + // Insert the new span, possibly merging it with existing spans. + while (currentSpan != NULL) + { + if (currentSpan->smin > newSpan->smax) { - // Current span is further than the new span, break. + // Current span is completely after the new span, break. break; } - else if (cur->smax < s->smin) + + if (currentSpan->smax < newSpan->smin) { - // Current span is before the new span advance. - prev = cur; - cur = cur->next; + // Current span is completely before the new span. Keep going. + previousSpan = currentSpan; + currentSpan = currentSpan->next; } else { - // Merge spans. - if (cur->smin < s->smin) - s->smin = cur->smin; - if (cur->smax > s->smax) - s->smax = cur->smax; + // The new span overlaps with an existing span. Merge them. + if (currentSpan->smin < newSpan->smin) + { + newSpan->smin = currentSpan->smin; + } + if (currentSpan->smax > newSpan->smax) + { + newSpan->smax = currentSpan->smax; + } // Merge flags. - if (rcAbs((int)s->smax - (int)cur->smax) <= flagMergeThr) - s->area = rcMax(s->area, cur->area); + if (rcAbs((int)newSpan->smax - (int)currentSpan->smax) <= flagMergeThreshold) + { + // Higher area ID numbers indicate higher resolution priority. + newSpan->area = rcMax(newSpan->area, currentSpan->area); + } - // Remove current span. - rcSpan* next = cur->next; - freeSpan(hf, cur); - if (prev) - prev->next = next; + // Remove the current span since it's now merged with newSpan. + // Keep going because there might be other overlapping spans that also need to be merged. + rcSpan* next = currentSpan->next; + freeSpan(hf, currentSpan); + if (previousSpan) + { + previousSpan->next = next; + } else - hf.spans[idx] = next; - cur = next; + { + hf.spans[columnIndex] = next; + } + currentSpan = next; } } - // Insert new span. - if (prev) + // Insert new span after prev + if (previousSpan != NULL) { - s->next = prev->next; - prev->next = s; + newSpan->next = previousSpan->next; + previousSpan->next = newSpan; } else { - s->next = hf.spans[idx]; - hf.spans[idx] = s; + // This span should go before the others in the list + newSpan->next = hf.spans[columnIndex]; + hf.spans[columnIndex] = newSpan; } return true; } -/// @par -/// -/// The span addition can be set to favor flags. If the span is merged to -/// another span and the new @p smax is within @p flagMergeThr units -/// from the existing span, the span flags are merged. -/// -/// @see rcHeightfield, rcSpan. -bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr) +bool rcAddSpan(rcContext* context, rcHeightfield& heightfield, + const int x, const int z, + const unsigned short spanMin, const unsigned short spanMax, + const unsigned char areaID, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context); - if (!addSpan(hf, x, y, smin, smax, area, flagMergeThr)) + if (!addSpan(heightfield, x, z, spanMin, spanMax, areaID, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcAddSpan: Out of memory."); + context->log(RC_LOG_ERROR, "rcAddSpan: Out of memory."); return false; } return true; } -// divides a convex polygons into two convex polygons on both sides of a line -static void dividePoly(const float* in, int nin, - float* out1, int* nout1, - float* out2, int* nout2, - float x, int axis) +enum rcAxis { - float d[12]; - for (int i = 0; i < nin; ++i) - d[i] = x - in[i*3+axis]; + RC_AXIS_X = 0, + RC_AXIS_Y = 1, + RC_AXIS_Z = 2 +}; - int m = 0, n = 0; - for (int i = 0, j = nin-1; i < nin; j=i, ++i) +/// Divides a convex polygon of max 12 vertices into two convex polygons +/// across a separating axis. +/// +/// @param[in] inVerts The input polygon vertices +/// @param[in] inVertsCount The number of input polygon vertices +/// @param[out] outVerts1 Resulting polygon 1's vertices +/// @param[out] outVerts1Count The number of resulting polygon 1 vertices +/// @param[out] outVerts2 Resulting polygon 2's vertices +/// @param[out] outVerts2Count The number of resulting polygon 2 vertices +/// @param[in] axisOffset THe offset along the specified axis +/// @param[in] axis The separating axis +static void dividePoly(const float* inVerts, int inVertsCount, + float* outVerts1, int* outVerts1Count, + float* outVerts2, int* outVerts2Count, + float axisOffset, rcAxis axis) +{ + rcAssert(inVertsCount <= 12); + + // How far positive or negative away from the separating axis is each vertex. + float inVertAxisDelta[12]; + for (int inVert = 0; inVert < inVertsCount; ++inVert) { - bool ina = d[j] >= 0; - bool inb = d[i] >= 0; - if (ina != inb) + inVertAxisDelta[inVert] = axisOffset - inVerts[inVert * 3 + axis]; + } + + int poly1Vert = 0; + int poly2Vert = 0; + for (int inVertA = 0, inVertB = inVertsCount - 1; inVertA < inVertsCount; inVertB = inVertA, ++inVertA) + { + // If the two vertices are on the same side of the separating axis + bool sameSide = (inVertAxisDelta[inVertA] >= 0) == (inVertAxisDelta[inVertB] >= 0); + + if (!sameSide) { - float s = d[j] / (d[j] - d[i]); - out1[m*3+0] = in[j*3+0] + (in[i*3+0] - in[j*3+0])*s; - out1[m*3+1] = in[j*3+1] + (in[i*3+1] - in[j*3+1])*s; - out1[m*3+2] = in[j*3+2] + (in[i*3+2] - in[j*3+2])*s; - rcVcopy(out2 + n*3, out1 + m*3); - m++; - n++; - // add the i'th point to the right polygon. Do NOT add points that are on the dividing line + float s = inVertAxisDelta[inVertB] / (inVertAxisDelta[inVertB] - inVertAxisDelta[inVertA]); + outVerts1[poly1Vert * 3 + 0] = inVerts[inVertB * 3 + 0] + (inVerts[inVertA * 3 + 0] - inVerts[inVertB * 3 + 0]) * s; + outVerts1[poly1Vert * 3 + 1] = inVerts[inVertB * 3 + 1] + (inVerts[inVertA * 3 + 1] - inVerts[inVertB * 3 + 1]) * s; + outVerts1[poly1Vert * 3 + 2] = inVerts[inVertB * 3 + 2] + (inVerts[inVertA * 3 + 2] - inVerts[inVertB * 3 + 2]) * s; + rcVcopy(&outVerts2[poly2Vert * 3], &outVerts1[poly1Vert * 3]); + poly1Vert++; + poly2Vert++; + + // add the inVertA point to the right polygon. Do NOT add points that are on the dividing line // since these were already added above - if (d[i] > 0) + if (inVertAxisDelta[inVertA] > 0) { - rcVcopy(out1 + m*3, in + i*3); - m++; + rcVcopy(&outVerts1[poly1Vert * 3], &inVerts[inVertA * 3]); + poly1Vert++; } - else if (d[i] < 0) + else if (inVertAxisDelta[inVertA] < 0) { - rcVcopy(out2 + n*3, in + i*3); - n++; + rcVcopy(&outVerts2[poly2Vert * 3], &inVerts[inVertA * 3]); + poly2Vert++; } } - else // same side + else { - // add the i'th point to the right polygon. Addition is done even for points on the dividing line - if (d[i] >= 0) + // add the inVertA point to the right polygon. Addition is done even for points on the dividing line + if (inVertAxisDelta[inVertA] >= 0) { - rcVcopy(out1 + m*3, in + i*3); - m++; - if (d[i] != 0) + rcVcopy(&outVerts1[poly1Vert * 3], &inVerts[inVertA * 3]); + poly1Vert++; + if (inVertAxisDelta[inVertA] != 0) + { continue; + } } - rcVcopy(out2 + n*3, in + i*3); - n++; + rcVcopy(&outVerts2[poly2Vert * 3], &inVerts[inVertA * 3]); + poly2Vert++; } } - *nout1 = m; - *nout2 = n; + *outVerts1Count = poly1Vert; + *outVerts2Count = poly2Vert; } - - +/// Rasterize a single triangle to the heightfield. +/// +/// This code is extremely hot, so much care should be given to maintaining maximum perf here. +/// +/// @param[in] v0 Triangle vertex 0 +/// @param[in] v1 Triangle vertex 1 +/// @param[in] v2 Triangle vertex 2 +/// @param[in] areaID The area ID to assign to the rasterized spans +/// @param[in] hf Heightfield to rasterize into +/// @param[in] hfBBMin The min extents of the heightfield bounding box +/// @param[in] hfBBMax The max extents of the heightfield bounding box +/// @param[in] cellSize The x and z axis size of a voxel in the heightfield +/// @param[in] inverseCellSize 1 / cellSize +/// @param[in] inverseCellHeight 1 / cellHeight +/// @param[in] flagMergeThreshold The threshold in which area flags will be merged +/// @returns true if the operation completes successfully. false if there was an error adding spans to the heightfield. static bool rasterizeTri(const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& hf, - const float* bmin, const float* bmax, - const float cs, const float ics, const float ich, - const int flagMergeThr) + const unsigned char areaID, rcHeightfield& hf, + const float* hfBBMin, const float* hfBBMax, + const float cellSize, const float inverseCellSize, const float inverseCellHeight, + const int flagMergeThreshold) { + // Calculate the bounding box of the triangle. + float triBBMin[3]; + rcVcopy(triBBMin, v0); + rcVmin(triBBMin, v1); + rcVmin(triBBMin, v2); + + float triBBMax[3]; + rcVcopy(triBBMax, v0); + rcVmax(triBBMax, v1); + rcVmax(triBBMax, v2); + + // If the triangle does not touch the bounding box of the heightfield, skip the triangle. + if (!overlapBounds(triBBMin, triBBMax, hfBBMin, hfBBMax)) + { + return true; + } + const int w = hf.width; const int h = hf.height; - float tmin[3], tmax[3]; - const float by = bmax[1] - bmin[1]; - - // Calculate the bounding box of the triangle. - rcVcopy(tmin, v0); - rcVcopy(tmax, v0); - rcVmin(tmin, v1); - rcVmin(tmin, v2); - rcVmax(tmax, v1); - rcVmax(tmax, v2); - - // If the triangle does not touch the bbox of the heightfield, skip the triagle. - if (!overlapBounds(bmin, bmax, tmin, tmax)) - return true; - - // Calculate the footprint of the triangle on the grid's y-axis - int y0 = (int)((tmin[2] - bmin[2])*ics); - int y1 = (int)((tmax[2] - bmin[2])*ics); - y0 = rcClamp(y0, 0, h-1); - y1 = rcClamp(y1, 0, h-1); - + const float by = hfBBMax[1] - hfBBMin[1]; + + // Calculate the footprint of the triangle on the grid's z-axis + int z0 = (int)((triBBMin[2] - hfBBMin[2]) * inverseCellSize); + int z1 = (int)((triBBMax[2] - hfBBMin[2]) * inverseCellSize); + + // use -1 rather than 0 to cut the polygon properly at the start of the tile + z0 = rcClamp(z0, -1, h - 1); + z1 = rcClamp(z1, 0, h - 1); + // Clip the triangle into all grid cells it touches. - float buf[7*3*4]; - float *in = buf, *inrow = buf+7*3, *p1 = inrow+7*3, *p2 = p1+7*3; + float buf[7 * 3 * 4]; + float* in = buf; + float* inRow = buf + 7 * 3; + float* p1 = inRow + 7 * 3; + float* p2 = p1 + 7 * 3; rcVcopy(&in[0], v0); - rcVcopy(&in[1*3], v1); - rcVcopy(&in[2*3], v2); - int nvrow, nvIn = 3; - - for (int y = y0; y <= y1; ++y) + rcVcopy(&in[1 * 3], v1); + rcVcopy(&in[2 * 3], v2); + int nvRow; + int nvIn = 3; + + for (int z = z0; z <= z1; ++z) { // Clip polygon to row. Store the remaining polygon as well - const float cz = bmin[2] + y*cs; - dividePoly(in, nvIn, inrow, &nvrow, p1, &nvIn, cz+cs, 2); + const float cellZ = hfBBMin[2] + (float)z * cellSize; + dividePoly(in, nvIn, inRow, &nvRow, p1, &nvIn, cellZ + cellSize, RC_AXIS_Z); rcSwap(in, p1); - if (nvrow < 3) continue; - // find the horizontal bounds in the row - float minX = inrow[0], maxX = inrow[0]; - for (int i=1; i inrow[i*3]) minX = inrow[i*3]; - if (maxX < inrow[i*3]) maxX = inrow[i*3]; + continue; } - int x0 = (int)((minX - bmin[0])*ics); - int x1 = (int)((maxX - bmin[0])*ics); - x0 = rcClamp(x0, 0, w-1); - x1 = rcClamp(x1, 0, w-1); + if (z < 0) + { + continue; + } + + // find X-axis bounds of the row + float minX = inRow[0]; + float maxX = inRow[0]; + for (int vert = 1; vert < nvRow; ++vert) + { + if (minX > inRow[vert * 3]) + { + minX = inRow[vert * 3]; + } + if (maxX < inRow[vert * 3]) + { + maxX = inRow[vert * 3]; + } + } + int x0 = (int)((minX - hfBBMin[0]) * inverseCellSize); + int x1 = (int)((maxX - hfBBMin[0]) * inverseCellSize); + if (x1 < 0 || x0 >= w) + { + continue; + } + x0 = rcClamp(x0, -1, w - 1); + x1 = rcClamp(x1, 0, w - 1); - int nv, nv2 = nvrow; + int nv; + int nv2 = nvRow; for (int x = x0; x <= x1; ++x) { // Clip polygon to column. store the remaining polygon as well - const float cx = bmin[0] + x*cs; - dividePoly(inrow, nv2, p1, &nv, p2, &nv2, cx+cs, 0); - rcSwap(inrow, p2); - if (nv < 3) continue; + const float cx = hfBBMin[0] + (float)x * cellSize; + dividePoly(inRow, nv2, p1, &nv, p2, &nv2, cx + cellSize, RC_AXIS_X); + rcSwap(inRow, p2); + + if (nv < 3) + { + continue; + } + if (x < 0) + { + continue; + } // Calculate min and max of the span. - float smin = p1[1], smax = p1[1]; - for (int i = 1; i < nv; ++i) + float spanMin = p1[1]; + float spanMax = p1[1]; + for (int vert = 1; vert < nv; ++vert) { - smin = rcMin(smin, p1[i*3+1]); - smax = rcMax(smax, p1[i*3+1]); + spanMin = rcMin(spanMin, p1[vert * 3 + 1]); + spanMax = rcMax(spanMax, p1[vert * 3 + 1]); } - smin -= bmin[1]; - smax -= bmin[1]; - // Skip the span if it is outside the heightfield bbox - if (smax < 0.0f) continue; - if (smin > by) continue; - // Clamp the span to the heightfield bbox. - if (smin < 0.0f) smin = 0; - if (smax > by) smax = by; + spanMin -= hfBBMin[1]; + spanMax -= hfBBMin[1]; + // Skip the span if it's completely outside the heightfield bounding box + if (spanMax < 0.0f) + { + continue; + } + if (spanMin > by) + { + continue; + } + + // Clamp the span to the heightfield bounding box. + if (spanMin < 0.0f) + { + spanMin = 0; + } + if (spanMax > by) + { + spanMax = by; + } + // Snap the span to the heightfield height grid. - unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT); - unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); - - if (!addSpan(hf, x, y, ismin, ismax, area, flagMergeThr)) + unsigned short spanMinCellIndex = (unsigned short)rcClamp((int)floorf(spanMin * inverseCellHeight), 0, RC_SPAN_MAX_HEIGHT); + unsigned short spanMaxCellIndex = (unsigned short)rcClamp((int)ceilf(spanMax * inverseCellHeight), (int)spanMinCellIndex + 1, RC_SPAN_MAX_HEIGHT); + + if (!addSpan(hf, x, z, spanMinCellIndex, spanMaxCellIndex, areaID, flagMergeThreshold)) + { return false; + } } } return true; } -/// @par -/// -/// No spans will be added if the triangle does not overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& solid, - const int flagMergeThr) +bool rcRasterizeTriangle(rcContext* context, + const float* v0, const float* v1, const float* v2, + const unsigned char areaID, rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - if (!rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + // Rasterize the single triangle. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + if (!rasterizeTri(v0, v1, v2, areaID, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory."); return false; } return true; } -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, - const int* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const int /*nv*/, + const int* tris, const unsigned char* triAreaIDs, const int numTris, + rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) + // Rasterize the triangles. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + for (int triIndex = 0; triIndex < numTris; ++triIndex) { - const float* v0 = &verts[tris[i*3+0]*3]; - const float* v1 = &verts[tris[i*3+1]*3]; - const float* v2 = &verts[tris[i*3+2]*3]; - // Rasterize. - if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + const float* v0 = &verts[tris[triIndex * 3 + 0] * 3]; + const float* v1 = &verts[tris[triIndex * 3 + 1] * 3]; + const float* v2 = &verts[tris[triIndex * 3 + 2] * 3]; + if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } @@ -390,31 +502,26 @@ bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, return true; } -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, - const unsigned short* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const int /*nv*/, + const unsigned short* tris, const unsigned char* triAreaIDs, const int numTris, + rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); + + // Rasterize the triangles. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + for (int triIndex = 0; triIndex < numTris; ++triIndex) { - const float* v0 = &verts[tris[i*3+0]*3]; - const float* v1 = &verts[tris[i*3+1]*3]; - const float* v2 = &verts[tris[i*3+2]*3]; - // Rasterize. - if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + const float* v0 = &verts[tris[triIndex * 3 + 0] * 3]; + const float* v1 = &verts[tris[triIndex * 3 + 1] * 3]; + const float* v2 = &verts[tris[triIndex * 3 + 2] * 3]; + if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } @@ -422,30 +529,25 @@ bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, return true; } -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) +bool rcRasterizeTriangles(rcContext* context, + const float* verts, const unsigned char* triAreaIDs, const int numTris, + rcHeightfield& heightfield, const int flagMergeThreshold) { - rcAssert(ctx); + rcAssert(context != NULL); + + rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES); - rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) + // Rasterize the triangles. + const float inverseCellSize = 1.0f / heightfield.cs; + const float inverseCellHeight = 1.0f / heightfield.ch; + for (int triIndex = 0; triIndex < numTris; ++triIndex) { - const float* v0 = &verts[(i*3+0)*3]; - const float* v1 = &verts[(i*3+1)*3]; - const float* v2 = &verts[(i*3+2)*3]; - // Rasterize. - if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + const float* v0 = &verts[(triIndex * 3 + 0) * 3]; + const float* v1 = &verts[(triIndex * 3 + 1) * 3]; + const float* v2 = &verts[(triIndex * 3 + 2) * 3]; + if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold)) { - ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } diff --git a/Recast/Source/RecastRegion.cpp b/Recast/Source/RecastRegion.cpp index 38a2bd6b..4a7e841a 100644 --- a/Recast/Source/RecastRegion.cpp +++ b/Recast/Source/RecastRegion.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include #include @@ -25,8 +24,17 @@ #include "Recast.h" #include "RecastAlloc.h" #include "RecastAssert.h" -#include +namespace +{ +struct LevelStackEntry +{ + LevelStackEntry(int x_, int y_, int index_) : x(x_), y(y_), index(index_) {} + int x; + int y; + int index; +}; +} // namespace static void calculateDistanceField(rcCompactHeightfield& chf, unsigned short* src, unsigned short& maxDist) { @@ -245,17 +253,15 @@ static bool floodRegion(int x, int y, int i, unsigned short level, unsigned short r, rcCompactHeightfield& chf, unsigned short* srcReg, unsigned short* srcDist, - rcIntArray& stack) + rcTempVector& stack) { const int w = chf.width; const unsigned char area = chf.areas[i]; // Flood fill mark region. - stack.resize(0); - stack.push((int)x); - stack.push((int)y); - stack.push((int)i); + stack.clear(); + stack.push_back(LevelStackEntry(x, y, i)); srcReg[i] = r; srcDist[i] = 0; @@ -264,9 +270,11 @@ static bool floodRegion(int x, int y, int i, while (stack.size() > 0) { - int ci = stack.pop(); - int cy = stack.pop(); - int cx = stack.pop(); + LevelStackEntry& back = stack.back(); + int cx = back.x; + int cy = back.y; + int ci = back.index; + stack.pop_back(); const rcCompactSpan& cs = chf.spans[ci]; @@ -332,9 +340,7 @@ static bool floodRegion(int x, int y, int i, { srcReg[ai] = r; srcDist[ai] = 0; - stack.push(ax); - stack.push(ay); - stack.push(ai); + stack.push_back(LevelStackEntry(ax, ay, ai)); } } } @@ -343,12 +349,20 @@ static bool floodRegion(int x, int y, int i, return count > 0; } -static unsigned short* expandRegions(int maxIter, unsigned short level, - rcCompactHeightfield& chf, - unsigned short* srcReg, unsigned short* srcDist, - unsigned short* dstReg, unsigned short* dstDist, - rcIntArray& stack, - bool fillStack) +// Struct to keep track of entries in the region table that have been changed. +struct DirtyEntry +{ + DirtyEntry(int index_, unsigned short region_, unsigned short distance2_) + : index(index_), region(region_), distance2(distance2_) {} + int index; + unsigned short region; + unsigned short distance2; +}; +static void expandRegions(int maxIter, unsigned short level, + rcCompactHeightfield& chf, + unsigned short* srcReg, unsigned short* srcDist, + rcTempVector& stack, + bool fillStack) { const int w = chf.width; const int h = chf.height; @@ -356,7 +370,7 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, if (fillStack) { // Find cells revealed by the raised level. - stack.resize(0); + stack.clear(); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) @@ -366,9 +380,7 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, { if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA) { - stack.push(x); - stack.push(y); - stack.push(i); + stack.push_back(LevelStackEntry(x, y, i)); } } } @@ -377,27 +389,26 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, else // use cells in the input stack { // mark all cells which already have a region - for (int j=0; j dirtyEntries; int iter = 0; while (stack.size() > 0) { int failed = 0; + dirtyEntries.clear(); - memcpy(dstReg, srcReg, sizeof(unsigned short)*chf.spanCount); - memcpy(dstDist, srcDist, sizeof(unsigned short)*chf.spanCount); - - for (int j = 0; j < stack.size(); j += 3) + for (int j = 0; j < stack.size(); j++) { - int x = stack[j+0]; - int y = stack[j+1]; - int i = stack[j+2]; + int x = stack[j].x; + int y = stack[j].y; + int i = stack[j].index; if (i < 0) { failed++; @@ -426,9 +437,8 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, } if (r) { - stack[j+2] = -1; // mark as used - dstReg[i] = r; - dstDist[i] = d2; + stack[j].index = -1; // mark as used + dirtyEntries.push_back(DirtyEntry(i, r, d2)); } else { @@ -436,11 +446,14 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, } } - // rcSwap source and dest. - rcSwap(srcReg, dstReg); - rcSwap(srcDist, dstDist); + // Copy entries that differ between src and dst to keep them in sync. + for (int i = 0; i < dirtyEntries.size(); i++) { + int idx = dirtyEntries[i].index; + srcReg[idx] = dirtyEntries[i].region; + srcDist[idx] = dirtyEntries[i].distance2; + } - if (failed*3 == stack.size()) + if (failed == stack.size()) break; if (level > 0) @@ -450,16 +463,14 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, break; } } - - return srcReg; } static void sortCellsByLevel(unsigned short startLevel, rcCompactHeightfield& chf, - unsigned short* srcReg, - unsigned int nbStacks, rcIntArray* stacks, + const unsigned short* srcReg, + unsigned int nbStacks, rcTempVector* stacks, unsigned short loglevelsPerStack) // the levels per stack (2 in our case) as a bit shift { const int w = chf.width; @@ -467,7 +478,7 @@ static void sortCellsByLevel(unsigned short startLevel, startLevel = startLevel >> loglevelsPerStack; for (unsigned int j=0; j& srcStack, + rcTempVector& dstStack, + const unsigned short* srcReg) { - for (int j=0; j regions; + if (!regions.reserve(nreg)) { ctx->log(RC_LOG_ERROR, "mergeAndFilterRegions: Out of memory 'regions' (%d).", nreg); return false; } // Construct regions for (int i = 0; i < nreg; ++i) - new(®ions[i]) rcRegion((unsigned short)i); + regions.push_back(rcRegion((unsigned short) i)); // Find edge of a region and find connections around the contour. for (int y = 0; y < h; ++y) @@ -868,8 +875,8 @@ static bool mergeAndFilterRegions(rcContext* ctx, int minRegionArea, int mergeRe // Also keep track of the regions connects to a tile border. bool connectsToBorder = false; int spanCount = 0; - stack.resize(0); - trace.resize(0); + stack.clear(); + trace.clear(); reg.visited = true; stack.push(i); @@ -1021,11 +1028,6 @@ static bool mergeAndFilterRegions(rcContext* ctx, int minRegionArea, int mergeRe if (regions[i].overlap) overlaps.push(regions[i].id); - for (int i = 0; i < nreg; ++i) - regions[i].~rcRegion(); - rcFree(regions); - - return true; } @@ -1041,22 +1043,21 @@ static void addUniqueConnection(rcRegion& reg, int n) static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea, unsigned short& maxRegionId, rcCompactHeightfield& chf, - unsigned short* srcReg, rcIntArray& /*overlaps*/) + unsigned short* srcReg) { const int w = chf.width; const int h = chf.height; const int nreg = maxRegionId+1; - rcRegion* regions = (rcRegion*)rcAlloc(sizeof(rcRegion)*nreg, RC_ALLOC_TEMP); - if (!regions) - { + rcTempVector regions; + + // Construct regions + if (!regions.reserve(nreg)) { ctx->log(RC_LOG_ERROR, "mergeAndFilterLayerRegions: Out of memory 'regions' (%d).", nreg); return false; } - - // Construct regions for (int i = 0; i < nreg; ++i) - new(®ions[i]) rcRegion((unsigned short)i); + regions.push_back(rcRegion((unsigned short) i)); // Find region neighbours and overlapping regions. rcIntArray lregs(32); @@ -1066,7 +1067,7 @@ static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea, { const rcCompactCell& c = chf.cells[x+y*w]; - lregs.resize(0); + lregs.clear(); for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { @@ -1137,7 +1138,7 @@ static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea, // Start search. root.id = layerId; - stack.resize(0); + stack.clear(); stack.push(i); while (stack.size() > 0) @@ -1234,10 +1235,6 @@ static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea, srcReg[i] = regions[srcReg[i]].id; } - for (int i = 0; i < nreg; ++i) - regions[i].~rcRegion(); - rcFree(regions); - return true; } @@ -1391,9 +1388,9 @@ bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, paintRectRegion(w-bw, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, 0, bh, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, h-bh, h, id|RC_BORDER_REG, chf, srcReg); id++; - - chf.borderSize = borderSize; } + + chf.borderSize = borderSize; rcIntArray prev(256); @@ -1535,7 +1532,7 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, const int w = chf.width; const int h = chf.height; - rcScopedDelete buf((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP)); + rcScopedDelete buf((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*2, RC_ALLOC_TEMP)); if (!buf) { ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4); @@ -1546,17 +1543,15 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, const int LOG_NB_STACKS = 3; const int NB_STACKS = 1 << LOG_NB_STACKS; - rcIntArray lvlStacks[NB_STACKS]; + rcTempVector lvlStacks[NB_STACKS]; for (int i=0; i stack; + stack.reserve(256); unsigned short* srcReg = buf; unsigned short* srcDist = buf+chf.spanCount; - unsigned short* dstReg = buf+chf.spanCount*2; - unsigned short* dstDist = buf+chf.spanCount*3; memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount); memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount); @@ -1581,9 +1576,9 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; - - chf.borderSize = borderSize; } + + chf.borderSize = borderSize; int sId = -1; while (level > 0) @@ -1604,22 +1599,19 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, rcScopedTimer timerExpand(ctx, RC_TIMER_BUILD_REGIONS_EXPAND); // Expand current regions until no empty connected cells found. - if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, lvlStacks[sId], false) != srcReg) - { - rcSwap(srcReg, dstReg); - rcSwap(srcDist, dstDist); - } + expandRegions(expandIters, level, chf, srcReg, srcDist, lvlStacks[sId], false); } { rcScopedTimer timerFloor(ctx, RC_TIMER_BUILD_REGIONS_FLOOD); // Mark new regions with IDs. - for (int j = 0; j= 0 && srcReg[i] == 0) { if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) @@ -1638,11 +1630,7 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, } // Expand current regions until no empty connected cells found. - if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack, true) != srcReg) - { - rcSwap(srcReg, dstReg); - rcSwap(srcDist, dstDist); - } + expandRegions(expandIters*8, 0, chf, srcReg, srcDist, stack, true); ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); @@ -1709,9 +1697,9 @@ bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, paintRectRegion(w-bw, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, 0, bh, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, h-bh, h, id|RC_BORDER_REG, chf, srcReg); id++; - - chf.borderSize = borderSize; } + + chf.borderSize = borderSize; rcIntArray prev(256); @@ -1809,9 +1797,8 @@ bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER); // Merge monotone regions to layers and remove small regions. - rcIntArray overlaps; chf.maxRegions = id; - if (!mergeAndFilterLayerRegions(ctx, minRegionArea, chf.maxRegions, chf, srcReg, overlaps)) + if (!mergeAndFilterLayerRegions(ctx, minRegionArea, chf.maxRegions, chf, srcReg)) return false; } diff --git a/RecastDemo/Bin/.gitignore b/RecastDemo/Bin/.gitignore index c44262fa..f6861ba2 100644 --- a/RecastDemo/Bin/.gitignore +++ b/RecastDemo/Bin/.gitignore @@ -1 +1,2 @@ Recast.app +SDL2.framework \ No newline at end of file diff --git a/RecastDemo/Bin/Meshes/undulating.obj b/RecastDemo/Bin/Meshes/undulating.obj new file mode 100644 index 00000000..22bb8b08 --- /dev/null +++ b/RecastDemo/Bin/Meshes/undulating.obj @@ -0,0 +1,8002 @@ +v 5000 0 5000 +v 5002 1.9177022 5000 +v 5004 3.36588383 5000 +v 5006 3.98998 5000 +v 5008 3.63718963 5000 +v 5010 2.39388847 5000 +v 5012 0.56448 5000 +v 5014 -1.40313292 5000 +v 5016 -3.02721 5000 +v 5018 -3.91012049 5000 +v 5020 -3.83569717 5000 +v 5022 -2.8221612 5000 +v 5024 -1.117662 5000 +v 5026 0.860479951 5000 +v 5028 2.62794638 5000 +v 5030 3.75199986 5000 +v 5032 3.957433 5000 +v 5034 3.19394851 5000 +v 5036 1.648474 5000 +v 5038 -0.3006045 5000 +v 5040 -2.17608452 5000 +v 5042 -3.518783 5000 +v 5044 -3.999961 5000 +v 5046 -3.50180864 5000 +v 5048 -2.14629173 5000 +v 5050 -0.265287578 5000 +v 5052 1.68066812 5000 +v 5054 3.21513772 5000 +v 5056 3.96242952 5000 +v 5058 3.73958015 5000 +v 5060 2.60115147 5000 +v 5062 0.8258699 5000 +v 5064 -1.15161324 5000 +v 5066 -2.84714127 5000 +v 5068 -3.84558988 5000 +v 5070 -3.902504 5000 +v 5072 -3.003949 5000 +v 5074 -1.36992252 5000 +v 5076 0.5995088 5000 +v 5078 2.42215943 5000 +v 5080 3.651781 5000 +v 5082 3.98731923 5000 +v 5084 3.34662247 5000 +v 5086 1.886556 5000 +v 5088 -0.0354052372 5000 +v 5090 -1.948698 5000 +v 5092 -3.38488173 5000 +v 5094 -3.99232817 5000 +v 5096 -3.6223135 5000 +v 5098 -2.36543 5000 +v 5000 0 5002 +v 5002 1.682942 5002 +v 5004 2.953841 5002 +v 5006 3.50153685 5002 +v 5008 3.191934 5002 +v 5010 2.10083485 5002 +v 5012 0.4953778 5002 +v 5014 -1.231365 5002 +v 5016 -2.6566267 5002 +v 5018 -3.43145347 5002 +v 5020 -3.36614084 5002 +v 5022 -2.47667933 5002 +v 5024 -0.9808406 5002 +v 5026 0.7551422 5002 +v 5028 2.30623984 5002 +v 5030 3.29268956 5002 +v 5032 3.472974 5002 +v 5034 2.80295348 5002 +v 5036 1.446672 5002 +v 5038 -0.26380527 5002 +v 5040 -1.90969384 5002 +v 5042 -3.08802271 5002 +v 5044 -3.51029587 5002 +v 5046 -3.073126 5002 +v 5048 -1.88354814 5002 +v 5050 -0.232811749 5002 +v 5052 1.474925 5002 +v 5054 2.8215487 5002 +v 5056 3.477359 5002 +v 5058 3.28179026 5002 +v 5060 2.282725 5002 +v 5062 0.724769056 5002 +v 5064 -1.01063573 5002 +v 5066 -2.49860144 5002 +v 5068 -3.37482262 5002 +v 5070 -3.4247694 5002 +v 5072 -2.636213 5002 +v 5074 -1.20222008 5002 +v 5076 0.526118457 5002 +v 5078 2.125645 5002 +v 5080 3.20473933 5002 +v 5082 3.49920177 5002 +v 5084 2.93693757 5002 +v 5086 1.65560865 5002 +v 5088 -0.0310710184 5002 +v 5090 -1.71014345 5002 +v 5092 -2.970513 5002 +v 5094 -3.5035975 5002 +v 5096 -3.178879 5002 +v 5098 -2.07586026 5002 +v 5000 0 5004 +v 5002 1.03613889 5004 +v 5004 1.81859469 5004 +v 5006 2.15579534 5004 +v 5008 1.96518183 5004 +v 5010 1.29342341 5004 +v 5012 0.304989845 5004 +v 5014 -0.7581159 5004 +v 5016 -1.63560843 5004 +v 5018 -2.112647 5004 +v 5020 -2.07243586 5004 +v 5022 -1.52482009 5004 +v 5024 -0.6038753 5004 +v 5026 0.464919269 5004 +v 5028 1.4198854 5004 +v 5030 2.027214 5004 +v 5032 2.13821 5004 +v 5034 1.72569764 5004 +v 5036 0.890674233 5004 +v 5038 -0.1624173 5004 +v 5040 -1.17574346 5004 +v 5042 -1.90120649 5004 +v 5044 -2.161188 5004 +v 5046 -1.89203513 5004 +v 5048 -1.15964627 5004 +v 5050 -0.143335477 5004 +v 5052 0.908068836 5004 +v 5054 1.73714626 5004 +v 5056 2.14090967 5004 +v 5058 2.02050376 5004 +v 5060 1.405408 5004 +v 5062 0.446219385 5004 +v 5064 -0.622219265 5004 +v 5066 -1.538317 5004 +v 5068 -2.077781 5004 +v 5070 -2.10853171 5004 +v 5072 -1.62304044 5004 +v 5074 -0.740172267 5004 +v 5076 0.323916 5004 +v 5078 1.3086983 5004 +v 5080 1.97306561 5004 +v 5082 2.15435767 5004 +v 5084 1.80818772 5004 +v 5086 1.01931047 5004 +v 5088 -0.01912953 5004 +v 5090 -1.052886 5004 +v 5092 -1.82885933 5004 +v 5094 -2.157064 5004 +v 5096 -1.95714426 5004 +v 5098 -1.27804732 5004 +v 5000 0 5006 +v 5002 0.135652885 5006 +v 5004 0.2380932 5006 +v 5006 0.28224 5006 +v 5008 0.2572846 5006 +v 5010 0.16933696 5006 +v 5012 0.0399297327 5006 +v 5014 -0.09925369 5006 +v 5016 -0.214136347 5006 +v 5018 -0.276590973 5006 +v 5020 -0.271326482 5006 +v 5022 -0.19963178 5006 +v 5024 -0.07906027 5006 +v 5026 0.06086794 5006 +v 5028 0.185893565 5006 +v 5030 0.265405953 5006 +v 5032 0.2799377 5006 +v 5034 0.225930974 5006 +v 5036 0.116608433 5006 +v 5038 -0.02126392 5006 +v 5040 -0.153930128 5006 +v 5042 -0.248908862 5006 +v 5044 -0.28294602 5006 +v 5046 -0.247708127 5006 +v 5048 -0.151822656 5006 +v 5050 -0.0187657 5006 +v 5052 0.118885756 5006 +v 5054 0.227429837 5006 +v 5056 0.28029117 5006 +v 5058 0.2645274 5006 +v 5060 0.183998168 5006 +v 5062 0.0584197231 5006 +v 5064 -0.08146189 5006 +v 5066 -0.20139879 5006 +v 5068 -0.272026241 5006 +v 5070 -0.2760522 5006 +v 5072 -0.212490931 5006 +v 5074 -0.09690448 5006 +v 5076 0.042407576 5006 +v 5078 0.17133677 5006 +v 5080 0.258316755 5006 +v 5082 0.2820518 5006 +v 5084 0.2367307 5006 +v 5086 0.133449689 5006 +v 5088 -0.00250446727 5006 +v 5090 -0.137845442 5006 +v 5092 -0.239437044 5006 +v 5094 -0.282406121 5006 +v 5096 -0.256232321 5006 +v 5098 -0.1673239 5006 +v 5000 0 5008 +v 5002 -0.7980457 5008 +v 5004 -1.40070188 5008 +v 5006 -1.66041756 5008 +v 5008 -1.513605 5008 +v 5010 -0.996209145 5008 +v 5012 -0.234906569 5008 +v 5014 0.583909333 5008 +v 5016 1.25976384 5008 +v 5018 1.62718427 5008 +v 5020 1.59621322 5008 +v 5022 1.17443347 5008 +v 5024 0.4651115 5008 +v 5026 -0.35808602 5008 +v 5028 -1.0936116 5008 +v 5030 -1.56138289 5008 +v 5032 -1.64687324 5008 +v 5034 -1.32915163 5008 +v 5036 -0.686007261 5008 +v 5038 0.1250956 5008 +v 5040 0.9055707 5008 +v 5042 1.46433043 5008 +v 5044 1.66457117 5008 +v 5046 1.45726657 5008 +v 5048 0.8931725 5008 +v 5050 0.110398591 5008 +v 5052 -0.6994047 5008 +v 5054 -1.33796942 5008 +v 5056 -1.64895248 5008 +v 5058 -1.55621445 5008 +v 5060 -1.082461 5008 +v 5062 -0.343683153 5008 +v 5064 0.4792402 5008 +v 5066 1.18482888 5008 +v 5068 1.60033011 5008 +v 5070 1.62401474 5008 +v 5072 1.25008392 5008 +v 5074 0.5700889 5008 +v 5076 -0.2494837 5008 +v 5078 -1.007974 5008 +v 5080 -1.51967716 5008 +v 5082 -1.65931034 5008 +v 5084 -1.39268637 5008 +v 5086 -0.785084367 5008 +v 5088 0.0147337774 5008 +v 5090 0.810944557 5008 +v 5092 1.40860784 5008 +v 5094 1.66139472 5008 +v 5096 1.50741434 5008 +v 5098 0.9843663 5008 +v 5000 0 5010 +v 5002 -1.53635478 5010 +v 5004 -2.69655633 5010 +v 5006 -3.19654679 5010 +v 5008 -2.913911 5010 +v 5010 -1.91784835 5010 +v 5012 -0.45222953 5010 +v 5014 1.12411094 5010 +v 5016 2.42522979 5010 +v 5018 3.132568 5010 +v 5020 3.07294416 5010 +v 5022 2.26095629 5010 +v 5024 0.8954077 5010 +v 5026 -0.689368 5010 +v 5028 -2.10536242 5010 +v 5030 -3.00589061 5010 +v 5032 -3.17047215 5010 +v 5034 -2.55881143 5010 +v 5036 -1.32066441 5010 +v 5038 0.240827367 5010 +v 5040 1.74335611 5010 +v 5042 2.81905055 5010 +v 5044 3.204543 5010 +v 5046 2.80545163 5010 +v 5048 1.71948791 5010 +v 5050 0.212533444 5010 +v 5052 -1.34645653 5010 +v 5054 -2.575787 5010 +v 5056 -3.174475 5010 +v 5058 -2.99594069 5010 +v 5060 -2.083896 5010 +v 5062 -0.6616404 5010 +v 5064 0.922607541 5010 +v 5066 2.280969 5010 +v 5068 3.08086967 5010 +v 5070 3.126466 5010 +v 5072 2.40659451 5010 +v 5074 1.09750462 5010 +v 5076 -0.480292648 5010 +v 5078 -1.94049752 5010 +v 5080 -2.925601 5010 +v 5082 -3.19441533 5010 +v 5084 -2.68112516 5010 +v 5086 -1.51140225 5010 +v 5088 0.0283646788 5010 +v 5090 1.56118691 5010 +v 5092 2.71177626 5010 +v 5094 3.19842815 5010 +v 5096 2.90199327 5010 +v 5098 1.89504921 5010 +v 5000 0 5012 +v 5002 -1.89851081 5012 +v 5004 -3.33219981 5012 +v 5006 -3.95005035 5012 +v 5008 -3.6007905 5012 +v 5010 -2.3699317 5012 +v 5012 -0.558831 5012 +v 5014 1.389091 5012 +v 5016 2.996915 5012 +v 5018 3.87099 5012 +v 5020 3.79731154 5012 +v 5022 2.79391837 5012 +v 5024 1.1064769 5012 +v 5026 -0.8518687 5012 +v 5028 -2.60164714 5012 +v 5030 -3.71445179 5012 +v 5032 -3.917829 5012 +v 5034 -3.16198516 5012 +v 5036 -1.63197684 5012 +v 5038 0.2975962 5012 +v 5040 2.15430737 5012 +v 5042 3.483569 5012 +v 5044 3.95993137 5012 +v 5046 3.46676421 5012 +v 5048 2.1248126 5012 +v 5050 0.2626327 5012 +v 5052 -1.66384888 5012 +v 5054 -3.18296218 5012 +v 5056 -3.92277551 5012 +v 5058 -3.70215631 5012 +v 5060 -2.57512045 5012 +v 5062 -0.817605 5012 +v 5064 1.14008844 5012 +v 5066 2.81864858 5012 +v 5068 3.807105 5012 +v 5070 3.86344957 5012 +v 5072 2.973887 5012 +v 5074 1.356213 5012 +v 5076 -0.593509257 5012 +v 5078 -2.39791965 5012 +v 5080 -3.61523581 5012 +v 5082 -3.947416 5012 +v 5084 -3.313131 5012 +v 5086 -1.86767638 5012 +v 5088 0.03505092 5012 +v 5090 1.92919648 5012 +v 5092 3.35100746 5012 +v 5094 3.952375 5012 +v 5096 3.58606315 5012 +v 5098 2.341758 5012 +v 5000 0 5014 +v 5002 -1.795845 5014 +v 5004 -3.15200448 5014 +v 5006 -3.73644352 5014 +v 5008 -3.40607047 5014 +v 5010 -2.241773 5014 +v 5012 -0.528611064 5014 +v 5014 1.31397319 5014 +v 5016 2.834851 5014 +v 5018 3.66165853 5014 +v 5020 3.59196424 5014 +v 5022 2.6428318 5014 +v 5024 1.046642 5014 +v 5026 -0.8058022 5014 +v 5028 -2.460958 5014 +v 5030 -3.51358533 5014 +v 5032 -3.70596457 5014 +v 5034 -2.99099445 5014 +v 5036 -1.54372442 5014 +v 5038 0.281503081 5014 +v 5040 2.037809 5014 +v 5042 3.295188 5014 +v 5044 3.74579 5014 +v 5046 3.279292 5014 +v 5048 2.00990915 5014 +v 5050 0.248430327 5014 +v 5052 -1.57387292 5014 +v 5054 -3.010837 5014 +v 5056 -3.71064353 5014 +v 5058 -3.50195479 5014 +v 5060 -2.43586564 5014 +v 5062 -0.7733914 5014 +v 5064 1.0784359 5014 +v 5066 2.66622448 5014 +v 5068 3.60122824 5014 +v 5070 3.654526 5014 +v 5072 2.81306815 5014 +v 5074 1.28287315 5014 +v 5076 -0.561414063 5014 +v 5078 -2.26824737 5014 +v 5080 -3.41973472 5014 +v 5082 -3.73395181 5014 +v 5084 -3.133967 5014 +v 5086 -1.766678 5014 +v 5088 0.03315547 5014 +v 5090 1.8248713 5014 +v 5092 3.169795 5014 +v 5094 3.73864245 5014 +v 5096 3.39213967 5014 +v 5098 2.215123 5014 +v 5000 0 5016 +v 5002 -1.25349379 5016 +v 5004 -2.2000885 5016 +v 5006 -2.60802484 5016 +v 5008 -2.37742567 5016 +v 5010 -1.56475 5016 +v 5012 -0.368968755 5016 +v 5014 0.9171489 5016 +v 5016 1.97871649 5016 +v 5018 2.55582523 5016 +v 5020 2.507179 5016 +v 5022 1.84468758 5016 +v 5024 0.7305526 5016 +v 5026 -0.5624472 5016 +v 5028 -1.7177403 5016 +v 5030 -2.45247078 5016 +v 5032 -2.58675075 5016 +v 5034 -2.087704 5016 +v 5036 -1.07751453 5016 +v 5038 0.1964882 5016 +v 5040 1.42238379 5016 +v 5042 2.30003 5016 +v 5044 2.614549 5016 +v 5046 2.288935 5016 +v 5048 1.40290987 5016 +v 5050 0.173403531 5016 +v 5052 -1.098558 5016 +v 5054 -2.10155416 5016 +v 5056 -2.59001684 5016 +v 5058 -2.44435263 5016 +v 5060 -1.70022607 5016 +v 5062 -0.5398246 5016 +v 5064 0.7527446 5016 +v 5066 1.86101568 5016 +v 5068 2.51364517 5016 +v 5070 2.55084682 5016 +v 5072 1.96351206 5016 +v 5074 0.8954411 5016 +v 5076 -0.3918651 5016 +v 5078 -1.58322906 5016 +v 5080 -2.38696337 5016 +v 5082 -2.60628581 5016 +v 5084 -2.18749833 5016 +v 5086 -1.23313534 5016 +v 5088 0.0231424067 5016 +v 5090 1.273754 5016 +v 5092 2.21250629 5016 +v 5094 2.60955977 5016 +v 5096 2.367702 5016 +v 5098 1.5461483 5016 +v 5000 0 5018 +v 5002 -0.4042436 5018 +v 5004 -0.7095142 5018 +v 5006 -0.841071069 5018 +v 5008 -0.7667043 5018 +v 5010 -0.5046216 5018 +v 5012 -0.118990019 5018 +v 5014 0.295774519 5018 +v 5016 0.638123155 5018 +v 5018 0.824237 5018 +v 5020 0.808548868 5018 +v 5022 0.5948997 5018 +v 5024 0.235598445 5018 +v 5026 -0.181385562 5018 +v 5028 -0.5539601 5018 +v 5030 -0.790905833 5018 +v 5032 -0.8342103 5018 +v 5034 -0.673270941 5018 +v 5036 -0.3474914 5018 +v 5038 0.06336617 5018 +v 5040 0.458709478 5018 +v 5042 0.7417447 5018 +v 5044 0.843175 5018 +v 5046 0.7381666 5018 +v 5048 0.4524293 5018 +v 5050 0.05592151 5018 +v 5052 -0.3542778 5018 +v 5054 -0.677737534 5018 +v 5056 -0.83526355 5018 +v 5058 -0.7882878 5018 +v 5060 -0.5483118 5018 +v 5062 -0.174089909 5018 +v 5064 0.242755234 5018 +v 5066 0.6001654 5018 +v 5068 0.8106342 5018 +v 5070 0.8226315 5018 +v 5072 0.633219838 5018 +v 5074 0.288773924 5018 +v 5076 -0.126373947 5018 +v 5078 -0.5105811 5018 +v 5080 -0.769780159 5018 +v 5082 -0.8405102 5018 +v 5084 -0.705454 5018 +v 5086 -0.3976781 5018 +v 5088 0.00746327545 5018 +v 5090 0.41077736 5018 +v 5092 0.713518858 5018 +v 5094 0.841566 5018 +v 5096 0.763568461 5018 +v 5098 0.498622745 5018 +v 5000 0 5020 +v 5002 0.543979645 5020 +v 5004 0.954774 5020 +v 5006 1.13180649 5020 +v 5008 1.03173316 5020 +v 5010 0.6790557 5020 +v 5012 0.160121635 5020 +v 5014 -0.398015767 5020 +v 5016 -0.858705044 5020 +v 5018 -1.10915339 5020 +v 5020 -1.08804226 5020 +v 5022 -0.800540447 5020 +v 5024 -0.317038447 5020 +v 5026 0.24408564 5020 +v 5028 0.745449066 5020 +v 5030 1.06430054 5020 +v 5032 1.12257409 5020 +v 5034 0.906002462 5020 +v 5036 0.467609763 5020 +v 5038 -0.08527013 5020 +v 5040 -0.6172729 5020 +v 5042 -0.998145759 5020 +v 5044 -1.13463771 5020 +v 5046 -0.9933307 5020 +v 5048 -0.6088218 5020 +v 5050 -0.0752520561 5020 +v 5052 0.476742029 5020 +v 5054 0.912013054 5020 +v 5056 1.12399149 5020 +v 5058 1.06077754 5020 +v 5060 0.737848341 5020 +v 5062 0.234268084 5020 +v 5064 -0.326669157 5020 +v 5066 -0.807626367 5020 +v 5068 -1.09084845 5020 +v 5070 -1.10699284 5020 +v 5072 -0.85210675 5020 +v 5074 -0.388595223 5020 +v 5076 0.170058 5020 +v 5078 0.6870751 5020 +v 5080 1.03587222 5020 +v 5082 1.13105178 5020 +v 5084 0.9493103 5020 +v 5086 0.5351446 5020 +v 5088 -0.0100431275 5020 +v 5090 -0.552772 5020 +v 5092 -0.960163 5020 +v 5094 -1.13247263 5020 +v 5096 -1.02751338 5020 +v 5098 -0.670983136 5020 +v 5000 0 5022 +v 5002 1.35901761 5022 +v 5004 2.38530016 5022 +v 5006 2.82757831 5022 +v 5008 2.57756639 5022 +v 5010 1.69647646 5022 +v 5012 0.400029927 5022 +v 5014 -0.9943579 5022 +v 5016 -2.14529228 5022 +v 5018 -2.77098417 5022 +v 5020 -2.71824265 5022 +v 5022 -1.99998033 5022 +v 5024 -0.7920532 5022 +v 5026 0.609796166 5022 +v 5028 1.86234617 5022 +v 5030 2.65892887 5022 +v 5032 2.80451322 5022 +v 5034 2.26345468 5022 +v 5036 1.16822374 5022 +v 5038 -0.213029325 5022 +v 5040 -1.54212534 5022 +v 5042 -2.4936552 5022 +v 5044 -2.83465147 5022 +v 5046 -2.481626 5022 +v 5048 -1.52101207 5022 +v 5050 -0.18800129 5022 +v 5052 1.19103873 5022 +v 5054 2.278471 5022 +v 5056 2.808054 5022 +v 5058 2.65012741 5022 +v 5060 1.84335744 5022 +v 5062 0.585269034 5022 +v 5064 -0.8161135 5022 +v 5066 -2.017683 5022 +v 5068 -2.72525334 5022 +v 5070 -2.76558661 5022 +v 5072 -2.12880778 5022 +v 5074 -0.9708227 5022 +v 5076 0.424853772 5022 +v 5078 1.71651125 5022 +v 5080 2.58790684 5022 +v 5082 2.82569265 5022 +v 5084 2.37165022 5022 +v 5086 1.3369453 5022 +v 5088 -0.0250906218 5022 +v 5090 -1.38098347 5022 +v 5092 -2.39876342 5022 +v 5094 -2.82924223 5022 +v 5096 -2.56702423 5022 +v 5098 -1.67630887 5022 +v 5000 0 5024 +v 5002 1.84132063 5024 +v 5004 3.23182154 5024 +v 5006 3.83106017 5024 +v 5008 3.49232125 5024 +v 5010 2.29854059 5024 +v 5012 0.5419969 5024 +v 5014 -1.34724653 5024 +v 5016 -2.906637 5024 +v 5018 -3.75438142 5024 +v 5020 -3.68292236 5024 +v 5022 -2.70975518 5024 +v 5024 -1.07314575 5024 +v 5026 0.8262073 5024 +v 5028 2.523276 5024 +v 5030 3.60255861 5024 +v 5032 3.79980946 5024 +v 5034 3.06673431 5024 +v 5036 1.58281565 5024 +v 5038 -0.2886315 5024 +v 5040 -2.08941174 5024 +v 5042 -3.37863088 5024 +v 5044 -3.84064364 5024 +v 5046 -3.36233258 5024 +v 5048 -2.06080556 5024 +v 5050 -0.254721254 5024 +v 5052 1.61372757 5024 +v 5054 3.08707976 5024 +v 5056 3.804607 5024 +v 5058 3.59063363 5024 +v 5060 2.49754834 5024 +v 5062 0.7929757 5024 +v 5064 -1.10574484 5024 +v 5066 -2.73374033 5024 +v 5068 -3.692421 5024 +v 5070 -3.74706817 5024 +v 5072 -2.88430238 5024 +v 5074 -1.31535888 5024 +v 5076 0.575630546 5024 +v 5078 2.3256855 5024 +v 5080 3.50633168 5024 +v 5082 3.82850528 5024 +v 5084 3.21332741 5024 +v 5086 1.811415 5024 +v 5088 -0.0339950547 5024 +v 5090 -1.871082 5024 +v 5092 -3.2500627 5024 +v 5094 -3.833315 5024 +v 5096 -3.47803783 5024 +v 5098 -2.27121568 5024 +v 5000 0 5026 +v 5002 1.87280428 5026 +v 5004 3.28708053 5026 +v 5006 3.8965652 5026 +v 5008 3.55203438 5026 +v 5010 2.337842 5026 +v 5012 0.5512642 5026 +v 5014 -1.37028229 5026 +v 5016 -2.956336 5026 +v 5018 -3.81857538 5026 +v 5020 -3.74589443 5026 +v 5022 -2.75608778 5026 +v 5024 -1.09149492 5026 +v 5026 0.8403341 5026 +v 5028 2.56642 5026 +v 5030 3.66415668 5026 +v 5032 3.86478019 5026 +v 5034 3.11917067 5026 +v 5036 1.60987937 5026 +v 5038 -0.293566644 5026 +v 5040 -2.12513733 5026 +v 5042 -3.43640018 5026 +v 5044 -3.90631247 5026 +v 5046 -3.41982317 5026 +v 5048 -2.096042 5026 +v 5050 -0.259076566 5026 +v 5052 1.64131975 5026 +v 5054 3.13986373 5026 +v 5056 3.86965966 5026 +v 5058 3.65202785 5026 +v 5060 2.54025245 5026 +v 5062 0.80653435 5026 +v 5064 -1.12465131 5026 +v 5066 -2.780483 5026 +v 5068 -3.75555563 5026 +v 5070 -3.8111372 5026 +v 5072 -2.9336195 5026 +v 5074 -1.33784938 5026 +v 5076 0.585472941 5026 +v 5078 2.365451 5026 +v 5080 3.56628442 5026 +v 5082 3.89396667 5026 +v 5084 3.26827025 5026 +v 5086 1.84238732 5026 +v 5088 -0.03457632 5026 +v 5090 -1.9030745 5026 +v 5092 -3.30563378 5026 +v 5094 -3.89885831 5026 +v 5096 -3.53750658 5026 +v 5098 -2.31004977 5026 +v 5000 0 5028 +v 5002 1.44576 5028 +v 5004 2.53754735 5028 +v 5006 3.008055 5028 +v 5008 2.74208546 5028 +v 5010 1.804758 5028 +v 5012 0.425562739 5028 +v 5014 -1.05782509 5028 +v 5016 -2.28222036 5028 +v 5018 -2.94784856 5028 +v 5020 -2.8917408 5028 +v 5022 -2.12763381 5028 +v 5024 -0.842607856 5028 +v 5026 0.648717761 5028 +v 5028 1.98121476 5028 +v 5030 2.82864118 5028 +v 5032 2.98351765 5028 +v 5034 2.407925 5028 +v 5036 1.2427882 5028 +v 5038 -0.226626411 5028 +v 5040 -1.640555 5028 +v 5042 -2.65281844 5028 +v 5044 -3.01557946 5028 +v 5046 -2.64002132 5028 +v 5048 -1.61809421 5028 +v 5050 -0.2000009 5028 +v 5052 1.26705945 5028 +v 5054 2.42389965 5028 +v 5056 2.98728466 5028 +v 5058 2.819278 5028 +v 5060 1.96101391 5028 +v 5062 0.6226252 5028 +v 5064 -0.8682038 5028 +v 5066 -2.14646626 5028 +v 5068 -2.89919877 5028 +v 5070 -2.94210649 5028 +v 5072 -2.264684 5028 +v 5074 -1.03278768 5028 +v 5076 0.451971054 5028 +v 5078 1.8260715 5028 +v 5080 2.753086 5028 +v 5082 3.006049 5028 +v 5084 2.52302623 5028 +v 5086 1.42227888 5028 +v 5088 -0.0266920887 5028 +v 5090 -1.46912789 5028 +v 5092 -2.55186987 5028 +v 5094 -3.00982523 5028 +v 5096 -2.73087025 5028 +v 5098 -1.78330314 5028 +v 5000 0 5030 +v 5002 0.6647433 5030 +v 5004 1.16673422 5030 +v 5006 1.383068 5030 +v 5008 1.26077831 5030 +v 5010 0.829806268 5030 +v 5012 0.1956687 5030 +v 5014 -0.486375421 5030 +v 5016 -1.04933786 5030 +v 5018 -1.35538578 5030 +v 5020 -1.329588 5030 +v 5022 -0.978260756 5030 +v 5024 -0.3874211 5030 +v 5026 0.298272729 5030 +v 5028 0.910939038 5030 +v 5030 1.30057561 5030 +v 5032 1.371786 5030 +v 5034 1.1071353 5030 +v 5036 0.5714193 5030 +v 5038 -0.104200132 5030 +v 5040 -0.754307747 5030 +v 5042 -1.21973443 5030 +v 5044 -1.38652766 5030 +v 5046 -1.2138505 5030 +v 5048 -0.7439805 5030 +v 5050 -0.091958046 5030 +v 5052 0.5825789 5030 +v 5054 1.11448026 5030 +v 5056 1.373518 5030 +v 5058 1.29627049 5030 +v 5060 0.901650965 5030 +v 5062 0.286275685 5030 +v 5064 -0.3991898 5030 +v 5066 -0.9869197 5030 +v 5068 -1.33301723 5030 +v 5070 -1.35274565 5030 +v 5072 -1.04127479 5030 +v 5074 -0.474863529 5030 +v 5076 0.207810923 5030 +v 5078 0.839606 5030 +v 5080 1.26583624 5030 +v 5082 1.38214564 5030 +v 5084 1.16005754 5030 +v 5086 0.653946936 5030 +v 5088 -0.0122727053 5030 +v 5090 -0.6754876 5030 +v 5092 -1.17331958 5030 +v 5094 -1.38388193 5030 +v 5096 -1.25562179 5030 +v 5098 -0.8199416 5030 +v 5000 0 5032 +v 5002 -0.279025733 5032 +v 5004 -0.4897362 5032 +v 5006 -0.5805422 5032 +v 5008 -0.5292112 5032 +v 5010 -0.348310858 5032 +v 5012 -0.08213186 5032 +v 5014 0.204155892 5032 +v 5016 0.440459162 5032 +v 5018 0.568922639 5032 +v 5020 0.5580941 5032 +v 5022 0.410624564 5032 +v 5024 0.162619859 5032 +v 5026 -0.125199869 5032 +v 5028 -0.3823663 5032 +v 5030 -0.5459161 5032 +v 5032 -0.5758066 5032 +v 5034 -0.464719623 5032 +v 5036 -0.239853024 5032 +v 5038 0.0437379628 5032 +v 5040 0.31662038 5032 +v 5042 0.511983037 5032 +v 5044 0.5819945 5032 +v 5046 0.509513259 5032 +v 5048 0.3122855 5032 +v 5050 0.0385993533 5032 +v 5052 -0.244537264 5032 +v 5054 -0.467802644 5032 +v 5056 -0.5765336 5032 +v 5058 -0.544109046 5032 +v 5060 -0.378467619 5032 +v 5062 -0.120164104 5032 +v 5064 0.167559758 5032 +v 5066 0.414259166 5032 +v 5068 0.5595335 5032 +v 5070 0.567814469 5032 +v 5072 0.437074661 5032 +v 5074 0.199323773 5032 +v 5076 -0.08722855 5032 +v 5078 -0.3524243 5032 +v 5080 -0.5313343 5032 +v 5082 -0.5801551 5032 +v 5084 -0.486933678 5032 +v 5086 -0.274493963 5032 +v 5088 0.005151463 5032 +v 5090 0.28353563 5032 +v 5092 0.4925004 5032 +v 5094 0.580883861 5032 +v 5096 0.52704674 5032 +v 5098 0.344170153 5032 +v 5000 0 5034 +v 5002 -1.15447962 5034 +v 5004 -2.026302 5034 +v 5006 -2.40201545 5034 +v 5008 -2.18963146 5034 +v 5010 -1.44114935 5034 +v 5012 -0.3398237 5034 +v 5014 0.8447027 5034 +v 5016 1.82241654 5034 +v 5018 2.353939 5034 +v 5020 2.30913544 5034 +v 5022 1.69897473 5034 +v 5024 0.67284584 5034 +v 5026 -0.5180192 5034 +v 5028 -1.58205509 5034 +v 5030 -2.25874853 5034 +v 5032 -2.38242173 5034 +v 5034 -1.922795 5034 +v 5036 -0.992401 5034 +v 5038 0.18096748 5034 +v 5040 1.31002879 5034 +v 5042 2.11834931 5034 +v 5044 2.408024 5034 +v 5046 2.10813046 5034 +v 5048 1.29209316 5034 +v 5050 0.15970628 5034 +v 5052 -1.01178229 5034 +v 5054 -1.93555129 5034 +v 5056 -2.38542986 5034 +v 5058 -2.25127172 5034 +v 5060 -1.56592417 5034 +v 5062 -0.497183532 5034 +v 5064 0.693284869 5034 +v 5066 1.714013 5034 +v 5068 2.315091 5034 +v 5070 2.34935379 5034 +v 5072 1.808413 5034 +v 5074 0.8247097 5034 +v 5076 -0.360911459 5034 +v 5078 -1.45816886 5034 +v 5080 -2.19841576 5034 +v 5082 -2.40041375 5034 +v 5084 -2.01470661 5034 +v 5086 -1.13572919 5034 +v 5088 0.0213143751 5034 +v 5090 1.17313945 5034 +v 5092 2.037739 5034 +v 5094 2.403429 5034 +v 5096 2.180676 5034 +v 5098 1.42401707 5034 +v 5000 0 5036 +v 5002 -1.74727643 5036 +v 5004 -3.06675863 5036 +v 5006 -3.63539147 5036 +v 5008 -3.3139534 5036 +v 5010 -2.18114424 5036 +v 5012 -0.51431483 5036 +v 5014 1.2784369 5036 +v 5016 2.75818253 5036 +v 5018 3.562629 5036 +v 5020 3.49481964 5036 +v 5022 2.57135653 5036 +v 5024 1.01833558 5036 +v 5026 -0.784009337 5036 +v 5028 -2.39440155 5036 +v 5030 -3.4185605 5036 +v 5032 -3.605737 5036 +v 5034 -2.910103 5036 +v 5036 -1.50197446 5036 +v 5038 0.27388984 5036 +v 5040 1.98269641 5036 +v 5042 3.20606971 5036 +v 5044 3.64448547 5036 +v 5046 3.19060373 5036 +v 5048 1.95555127 5036 +v 5050 0.241711542 5036 +v 5052 -1.53130758 5036 +v 5054 -2.92940927 5036 +v 5056 -3.61028934 5036 +v 5058 -3.40724468 5036 +v 5060 -2.36998773 5036 +v 5062 -0.7524751 5036 +v 5064 1.04926968 5036 +v 5066 2.59411645 5036 +v 5068 3.50383329 5036 +v 5070 3.55568933 5036 +v 5072 2.73698878 5036 +v 5074 1.24817789 5036 +v 5076 -0.5462306 5036 +v 5078 -2.20690274 5036 +v 5080 -3.327248 5036 +v 5082 -3.63296723 5036 +v 5084 -3.04920888 5036 +v 5086 -1.7188983 5036 +v 5088 0.0322587825 5036 +v 5090 1.7755177 5036 +v 5092 3.084068 5036 +v 5094 3.637531 5036 +v 5096 3.3003993 5036 +v 5098 2.155215 5036 +v 5000 0 5038 +v 5002 -1.91227925 5038 +v 5004 -3.35636568 5038 +v 5006 -3.978697 5038 +v 5008 -3.62690425 5038 +v 5010 -2.387119 5038 +v 5012 -0.562883735 5038 +v 5014 1.39916515 5038 +v 5016 3.01864958 5038 +v 5018 3.89906335 5038 +v 5020 3.82485056 5038 +v 5022 2.81418061 5038 +v 5024 1.11450136 5038 +v 5026 -0.858046651 5038 +v 5028 -2.620515 5038 +v 5030 -3.74138975 5038 +v 5032 -3.946242 5038 +v 5034 -3.1849165 5038 +v 5036 -1.64381242 5038 +v 5038 0.299754441 5038 +v 5040 2.169931 5038 +v 5042 3.50883269 5038 +v 5044 3.98864961 5038 +v 5046 3.49190617 5038 +v 5048 2.14022231 5038 +v 5050 0.2645374 5038 +v 5052 -1.67591548 5038 +v 5054 -3.20604587 5038 +v 5056 -3.95122457 5038 +v 5058 -3.72900534 5038 +v 5060 -2.59379578 5038 +v 5062 -0.8235345 5038 +v 5064 1.14835668 5038 +v 5066 2.83909 5038 +v 5068 3.83471513 5038 +v 5070 3.89146829 5038 +v 5072 2.99545431 5038 +v 5074 1.36604857 5038 +v 5076 -0.597813547 5038 +v 5078 -2.41531 5038 +v 5080 -3.64145446 5038 +v 5082 -3.9760437 5038 +v 5084 -3.337159 5038 +v 5086 -1.88122118 5038 +v 5088 0.0353051163 5038 +v 5090 1.94318748 5038 +v 5092 3.37531 5038 +v 5094 3.98103857 5038 +v 5096 3.61207032 5038 +v 5098 2.358741 5038 +v 5000 0 5040 +v 5002 -1.60908926 5040 +v 5004 -2.82421732 5040 +v 5006 -3.34787846 5040 +v 5008 -3.05186224 5040 +v 5010 -2.00864363 5040 +v 5012 -0.4736391 5040 +v 5014 1.17732882 5040 +v 5016 2.54004574 5040 +v 5018 3.28087068 5040 +v 5020 3.21842432 5040 +v 5022 2.367995 5040 +v 5024 0.9377983 5040 +v 5026 -0.722004235 5040 +v 5028 -2.205035 5040 +v 5030 -3.14819622 5040 +v 5032 -3.32056928 5040 +v 5034 -2.67995119 5040 +v 5036 -1.38318753 5040 +v 5038 0.252228677 5040 +v 5040 1.82589054 5040 +v 5042 2.9525106 5040 +v 5044 3.35625315 5040 +v 5046 2.938268 5040 +v 5048 1.80089223 5040 +v 5050 0.222595245 5040 +v 5052 -1.41020072 5040 +v 5054 -2.69773054 5040 +v 5056 -3.32476163 5040 +v 5058 -3.13777518 5040 +v 5060 -2.182552 5040 +v 5062 -0.6929639 5040 +v 5064 0.9662859 5040 +v 5066 2.388955 5040 +v 5068 3.22672486 5040 +v 5070 3.27447987 5040 +v 5072 2.520528 5040 +v 5074 1.14946294 5040 +v 5076 -0.5030308 5040 +v 5078 -2.032365 5040 +v 5080 -3.06410551 5040 +v 5082 -3.345646 5040 +v 5084 -2.80805564 5040 +v 5086 -1.58295548 5040 +v 5088 0.0297075268 5040 +v 5090 1.635097 5040 +v 5092 2.84015775 5040 +v 5094 3.34984875 5040 +v 5096 3.03938 5040 +v 5098 1.984765 5040 +v 5000 0 5042 +v 5002 -0.91193825 5042 +v 5004 -1.60060215 5042 +v 5006 -1.89738286 5042 +v 5008 -1.72961807 5042 +v 5010 -1.13838243 5042 +v 5012 -0.2684311 5042 +v 5014 0.6672415 5042 +v 5016 1.43955016 5042 +v 5018 1.85940671 5042 +v 5020 1.82401574 5042 +v 5022 1.34204185 5042 +v 5024 0.531489551 5042 +v 5026 -0.40919 5042 +v 5028 -1.24968553 5042 +v 5030 -1.7842145 5042 +v 5032 -1.88190556 5042 +v 5034 -1.51884055 5042 +v 5036 -0.7839103 5042 +v 5038 0.142948538 5042 +v 5040 1.03480864 5042 +v 5042 1.67331135 5042 +v 5044 1.90212917 5042 +v 5046 1.66523933 5042 +v 5048 1.020641 5042 +v 5050 0.12615405 5042 +v 5052 -0.7992198 5042 +v 5054 -1.52891672 5042 +v 5056 -1.88428164 5042 +v 5058 -1.77830851 5042 +v 5060 -1.2369436 5042 +v 5062 -0.392731667 5042 +v 5064 0.547634661 5042 +v 5066 1.35392082 5042 +v 5068 1.82872009 5042 +v 5070 1.85578477 5042 +v 5072 1.42848873 5042 +v 5074 0.6514488 5042 +v 5076 -0.2850886 5042 +v 5078 -1.15182626 5042 +v 5080 -1.73655677 5042 +v 5082 -1.89611757 5042 +v 5084 -1.59144258 5042 +v 5086 -0.8971271 5042 +v 5088 0.0168364979 5042 +v 5090 0.9266779 5042 +v 5092 1.60963631 5042 +v 5094 1.89849949 5042 +v 5096 1.72254384 5042 +v 5098 1.12484944 5042 +v 5000 0 5044 +v 5002 0.008487171 5044 +v 5004 0.0148963854 5044 +v 5006 0.017658446 5044 +v 5008 0.0160971023 5044 +v 5010 0.0105946269 5044 +v 5012 0.002498218 5044 +v 5014 -0.0062098424 5044 +v 5016 -0.0133975167 5044 +v 5018 -0.0173050128 5044 +v 5020 -0.0169756375 5044 +v 5022 -0.0124900332 5044 +v 5024 -0.0049464344 5044 +v 5026 0.00380822434 5044 +v 5028 0.0116304969 5044 +v 5030 0.0166052189 5044 +v 5032 0.0175144039 5044 +v 5034 0.0141354511 5044 +v 5036 0.007295648 5044 +v 5038 -0.00133038464 5044 +v 5040 -0.009630693 5044 +v 5042 -0.0155730713 5044 +v 5044 -0.0177026186 5044 +v 5046 -0.0154979471 5044 +v 5048 -0.009498839 5044 +v 5050 -0.00117408275 5044 +v 5052 0.00743812928 5044 +v 5054 0.0142292287 5044 +v 5056 0.0175365154 5044 +v 5058 0.0165502522 5044 +v 5060 0.0115119107 5044 +v 5062 0.00365505088 5044 +v 5064 -0.005096692 5044 +v 5066 -0.0126005877 5044 +v 5068 -0.017019419 5044 +v 5070 -0.0172713045 5044 +v 5072 -0.0132945711 5044 +v 5074 -0.00606286339 5044 +v 5076 0.00265324488 5044 +v 5078 0.0107197464 5044 +v 5080 0.01616168 5044 +v 5082 0.01764667 5044 +v 5084 0.0148111405 5044 +v 5086 0.008349327 5044 +v 5088 -0.000156692884 5044 +v 5090 -0.008624349 5044 +v 5092 -0.0149804642 5044 +v 5094 -0.01766884 5044 +v 5096 -0.0160312653 5044 +v 5098 -0.0104686795 5044 +v 5000 0 5046 +v 5002 0.926834643 5046 +v 5004 1.62674773 5046 +v 5006 1.92837632 5046 +v 5008 1.75787115 5046 +v 5010 1.15697777 5046 +v 5012 0.272815883 5046 +v 5014 -0.6781408 5046 +v 5016 -1.463065 5046 +v 5018 -1.88977993 5046 +v 5020 -1.85381079 5046 +v 5022 -1.363964 5046 +v 5024 -0.5401713 5046 +v 5026 0.415874064 5046 +v 5028 1.270099 5046 +v 5030 1.81335938 5046 +v 5032 1.91264617 5046 +v 5034 1.54365051 5046 +v 5036 0.7967153 5046 +v 5038 -0.14528358 5046 +v 5040 -1.051712 5046 +v 5042 -1.70064461 5046 +v 5044 -1.93320012 5046 +v 5046 -1.69244087 5046 +v 5048 -1.037313 5046 +v 5050 -0.128214747 5046 +v 5052 0.812274933 5046 +v 5054 1.55389142 5046 +v 5056 1.91506112 5046 +v 5058 1.807357 5046 +v 5060 1.25714886 5046 +v 5062 0.399146855 5046 +v 5064 -0.5565802 5046 +v 5066 -1.376037 5046 +v 5068 -1.85859191 5046 +v 5070 -1.88609874 5046 +v 5072 -1.45182288 5046 +v 5074 -0.662090063 5046 +v 5076 0.28974548 5046 +v 5078 1.17064118 5046 +v 5080 1.76492321 5046 +v 5082 1.92709041 5046 +v 5084 1.61743855 5046 +v 5086 0.911781549 5046 +v 5088 -0.01711152 5046 +v 5090 -0.9418151 5046 +v 5092 -1.63592947 5046 +v 5094 -1.92951119 5046 +v 5096 -1.7506814 5046 +v 5098 -1.14322364 5046 +v 5000 0 5048 +v 5002 1.61826062 5048 +v 5004 2.84031439 5048 +v 5006 3.36696029 5048 +v 5008 3.06925678 5048 +v 5010 2.02009225 5048 +v 5012 0.476338685 5048 +v 5014 -1.18403924 5048 +v 5016 -2.55452323 5048 +v 5018 -3.29957056 5048 +v 5020 -3.23676825 5048 +v 5022 -2.381492 5048 +v 5024 -0.9431434 5048 +v 5026 0.7261194 5048 +v 5028 2.217603 5048 +v 5030 3.16613984 5048 +v 5032 3.33949542 5048 +v 5034 2.695226 5048 +v 5036 1.39107132 5048 +v 5038 -0.253666282 5048 +v 5040 -1.83629751 5048 +v 5042 -2.96933913 5048 +v 5044 -3.375383 5048 +v 5046 -2.955015 5048 +v 5048 -1.81115675 5048 +v 5050 -0.223863974 5048 +v 5052 1.4182384 5048 +v 5054 2.71310663 5048 +v 5056 3.34371185 5048 +v 5058 3.15565944 5048 +v 5060 2.19499183 5048 +v 5062 0.6969136 5048 +v 5064 -0.971793354 5048 +v 5066 -2.40257144 5048 +v 5068 -3.24511623 5048 +v 5070 -3.29314327 5048 +v 5072 -2.53489423 5048 +v 5074 -1.15601456 5048 +v 5076 0.5058979 5048 +v 5078 2.043949 5048 +v 5080 3.08157 5048 +v 5082 3.364715 5048 +v 5084 2.82406068 5048 +v 5086 1.59197772 5048 +v 5088 -0.0298768487 5048 +v 5090 -1.64441657 5048 +v 5092 -2.856346 5048 +v 5094 -3.36894178 5048 +v 5096 -3.05670357 5048 +v 5098 -1.99607754 5048 +v 5000 0 5050 +v 5002 1.91347992 5050 +v 5004 3.358473 5050 +v 5006 3.98119521 5050 +v 5008 3.62918139 5050 +v 5010 2.38861775 5050 +v 5012 0.5632372 5050 +v 5014 -1.40004361 5050 +v 5016 -3.02054477 5050 +v 5018 -3.90151143 5050 +v 5020 -3.827252 5050 +v 5022 -2.81594753 5050 +v 5024 -1.11520112 5050 +v 5026 0.8585854 5050 +v 5028 2.62216043 5050 +v 5030 3.743739 5050 +v 5032 3.94871974 5050 +v 5034 3.18691635 5050 +v 5036 1.64484453 5050 +v 5038 -0.299942642 5050 +v 5040 -2.17129326 5050 +v 5042 -3.51103568 5050 +v 5044 -3.991154 5050 +v 5046 -3.49409866 5050 +v 5048 -2.14156628 5050 +v 5050 -0.264703482 5050 +v 5052 1.67696774 5050 +v 5054 3.20805883 5050 +v 5056 3.95370531 5050 +v 5058 3.73134661 5050 +v 5060 2.59542441 5050 +v 5062 0.824051559 5050 +v 5064 -1.14907765 5050 +v 5066 -2.84087253 5050 +v 5068 -3.837123 5050 +v 5070 -3.8939116 5050 +v 5072 -2.997335 5050 +v 5074 -1.36690629 5050 +v 5076 0.5981889 5050 +v 5078 2.41682649 5050 +v 5080 3.643741 5050 +v 5082 3.97854018 5050 +v 5084 3.33925414 5050 +v 5086 1.8824023 5050 +v 5088 -0.0353272855 5050 +v 5090 -1.94440758 5050 +v 5092 -3.377429 5050 +v 5094 -3.98353815 5050 +v 5096 -3.61433816 5050 +v 5098 -2.360222 5050 +v 5000 0 5052 +v 5002 1.74021268 5052 +v 5004 3.05436063 5052 +v 5006 3.62069464 5052 +v 5008 3.30055618 5052 +v 5010 2.17232633 5052 +v 5012 0.5122356 5052 +v 5014 -1.27326846 5052 +v 5016 -2.747032 5052 +v 5018 -3.54822636 5052 +v 5020 -3.48069119 5052 +v 5022 -2.56096125 5052 +v 5024 -1.01421881 5052 +v 5026 0.7808398 5052 +v 5028 2.38472152 5052 +v 5030 3.40474033 5052 +v 5032 3.59115982 5052 +v 5034 2.89833832 5052 +v 5036 1.49590242 5052 +v 5038 -0.2727826 5052 +v 5040 -1.9746809 5052 +v 5042 -3.19310856 5052 +v 5044 -3.62975168 5052 +v 5046 -3.177705 5052 +v 5048 -1.94764555 5052 +v 5050 -0.240734369 5052 +v 5052 1.52511692 5052 +v 5054 2.91756654 5052 +v 5056 3.595694 5052 +v 5058 3.39347 5052 +v 5060 2.36040664 5052 +v 5062 0.749433041 5052 +v 5064 -1.04502773 5052 +v 5066 -2.58362913 5052 +v 5068 -3.48966813 5052 +v 5070 -3.54131484 5052 +v 5072 -2.72592378 5052 +v 5074 -1.24313176 5052 +v 5076 0.5440224 5052 +v 5078 2.19798088 5052 +v 5080 3.313797 5052 +v 5082 3.61828017 5052 +v 5084 3.036882 5052 +v 5086 1.71194923 5052 +v 5088 -0.0321283676 5052 +v 5090 -1.76833975 5052 +v 5092 -3.0716002 5052 +v 5094 -3.62282538 5052 +v 5096 -3.28705668 5052 +v 5098 -2.146502 5052 +v 5000 0 5054 +v 5002 1.14088058 5054 +v 5004 2.00243378 5054 +v 5006 2.37372136 5054 +v 5008 2.163839 5054 +v 5010 1.42417359 5054 +v 5012 0.3358208 5054 +v 5014 -0.834752738 5054 +v 5016 -1.80094969 5054 +v 5018 -2.32621145 5054 +v 5020 -2.28193545 5054 +v 5022 -1.67896187 5054 +v 5024 -0.664920151 5054 +v 5026 0.5119173 5054 +v 5028 1.56341958 5054 +v 5030 2.23214221 5054 +v 5032 2.35435843 5054 +v 5034 1.90014589 5054 +v 5036 0.980711162 5054 +v 5038 -0.178835809 5054 +v 5040 -1.29459763 5054 +v 5042 -2.09339666 5054 +v 5044 -2.37965918 5054 +v 5046 -2.08329821 5054 +v 5048 -1.27687323 5054 +v 5050 -0.157825053 5054 +v 5052 0.999864161 5054 +v 5054 1.91275179 5054 +v 5056 2.357331 5054 +v 5058 2.22475338 5054 +v 5060 1.54747868 5054 +v 5062 0.491327047 5054 +v 5064 -0.6851185 5054 +v 5066 -1.6938231 5054 +v 5068 -2.28782082 5054 +v 5070 -2.32168 5054 +v 5072 -1.78711116 5054 +v 5074 -0.81499517 5054 +v 5076 0.356660157 5054 +v 5078 1.44099259 5054 +v 5080 2.17252 5054 +v 5082 2.3721385 5054 +v 5084 1.99097478 5054 +v 5086 1.12235117 5054 +v 5088 -0.0210633054 5054 +v 5090 -1.15932071 5054 +v 5092 -2.013736 5054 +v 5094 -2.37511849 5054 +v 5096 -2.154989 5054 +v 5098 -1.40724313 5054 +v 5000 0 5056 +v 5002 0.262221247 5056 +v 5004 0.4602416 5056 +v 5006 0.5455787 5056 +v 5008 0.497339159 5056 +v 5010 0.327333629 5056 +v 5012 0.07718542 5056 +v 5014 -0.191860482 5056 +v 5016 -0.413932264 5056 +v 5018 -0.534658968 5056 +v 5020 -0.524482548 5056 +v 5022 -0.385894448 5056 +v 5024 -0.152825981 5056 +v 5026 0.117659628 5056 +v 5028 0.359338075 5056 +v 5030 0.513038 5056 +v 5032 0.541128337 5056 +v 5034 0.4367316 5056 +v 5036 0.225407735 5056 +v 5038 -0.04110382 5056 +v 5040 -0.297551721 5056 +v 5042 -0.4811486 5056 +v 5044 -0.5469435 5056 +v 5046 -0.478827566 5056 +v 5048 -0.293477952 5056 +v 5050 -0.0362746827 5056 +v 5052 0.22980988 5056 +v 5054 0.439628959 5056 +v 5056 0.5418116 5056 +v 5058 0.5113398 5056 +v 5060 0.3556742 5056 +v 5062 0.112927154 5056 +v 5064 -0.157468379 5056 +v 5066 -0.389310151 5056 +v 5068 -0.5258352 5056 +v 5070 -0.5336175 5056 +v 5072 -0.4107516 5056 +v 5074 -0.187319383 5056 +v 5076 0.08197516 5056 +v 5078 0.331199318 5056 +v 5080 0.499334365 5056 +v 5082 0.5452149 5056 +v 5084 0.457607836 5056 +v 5086 0.2579624 5056 +v 5088 -0.00484121358 5056 +v 5090 -0.266459525 5056 +v 5092 -0.4628393 5056 +v 5094 -0.5458998 5056 +v 5096 -0.495305061 5056 +v 5098 -0.3234423 5056 +v 5000 0 5058 +v 5002 -0.680639 5058 +v 5004 -1.19463384 5058 +v 5006 -1.41614068 5058 +v 5008 -1.29092681 5058 +v 5010 -0.8496491 5058 +v 5012 -0.200347647 5058 +v 5014 0.498005927 5058 +v 5016 1.07443023 5058 +v 5018 1.38779664 5058 +v 5020 1.361382 5058 +v 5022 1.00165343 5058 +v 5024 0.396685332 5058 +v 5026 -0.3054052 5058 +v 5028 -0.9327219 5058 +v 5030 -1.33167577 5058 +v 5032 -1.40458894 5058 +v 5034 -1.13360977 5058 +v 5036 -0.5850834 5058 +v 5038 0.10669183 5058 +v 5040 0.7723452 5058 +v 5042 1.24890149 5058 +v 5044 1.41968322 5058 +v 5046 1.24287689 5058 +v 5048 0.761771 5058 +v 5050 0.0941569954 5058 +v 5052 -0.5965099 5058 +v 5054 -1.14113033 5058 +v 5056 -1.40636241 5058 +v 5058 -1.32726777 5058 +v 5060 -0.923211753 5058 +v 5062 -0.293121278 5058 +v 5064 0.408735484 5058 +v 5066 1.0105195 5058 +v 5068 1.3648932 5058 +v 5070 1.38509333 5058 +v 5072 1.06617439 5058 +v 5074 0.48621875 5058 +v 5076 -0.212780222 5058 +v 5078 -0.859683156 5058 +v 5080 -1.29610574 5058 +v 5082 -1.4151963 5058 +v 5084 -1.18779755 5058 +v 5086 -0.6695845 5058 +v 5088 0.0125661781 5058 +v 5090 0.6916402 5058 +v 5092 1.20137668 5058 +v 5094 1.41697407 5058 +v 5096 1.28564692 5058 +v 5098 0.8395485 5058 +v 5000 0 5060 +v 5002 -1.45685518 5060 +v 5004 -2.55702114 5060 +v 5006 -3.03113961 5060 +v 5008 -2.763129 5060 +v 5010 -1.81860816 5060 +v 5012 -0.428828627 5060 +v 5014 1.06594312 5060 +v 5016 2.29973483 5060 +v 5018 2.97047114 5060 +v 5020 2.9139328 5060 +v 5022 2.14396167 5060 +v 5024 0.849074244 5060 +v 5026 -0.6536962 5060 +v 5028 -1.99641907 5060 +v 5030 -2.850349 5060 +v 5032 -3.006414 5060 +v 5034 -2.426404 5060 +v 5036 -1.25232577 5060 +v 5038 0.2283656 5060 +v 5040 1.65314507 5060 +v 5042 2.673177 5060 +v 5044 3.0387218 5060 +v 5046 2.66028166 5060 +v 5048 1.63051188 5060 +v 5050 0.201535761 5060 +v 5052 -1.27678323 5060 +v 5054 -2.44250131 5060 +v 5056 -3.0102098 5060 +v 5058 -2.84091377 5060 +v 5060 -1.97606325 5060 +v 5062 -0.6274034 5060 +v 5064 0.874866664 5060 +v 5066 2.16293883 5060 +v 5068 2.921448 5060 +v 5070 2.964685 5060 +v 5072 2.28206372 5060 +v 5074 1.04071355 5060 +v 5076 -0.4554396 5060 +v 5078 -1.84008527 5060 +v 5080 -2.77421379 5060 +v 5082 -3.029118 5060 +v 5084 -2.54238868 5060 +v 5086 -1.4331938 5060 +v 5088 0.0268969312 5060 +v 5090 1.48040235 5060 +v 5092 2.57145381 5060 +v 5094 3.03292346 5060 +v 5096 2.75182772 5060 +v 5098 1.79698861 5060 +v 5000 0 5062 +v 5002 -1.87638235 5062 +v 5004 -3.29336071 5062 +v 5006 -3.90400982 5062 +v 5008 -3.55882072 5062 +v 5010 -2.34230852 5062 +v 5012 -0.552317441 5062 +v 5014 1.37290025 5062 +v 5016 2.96198416 5062 +v 5018 3.825871 5062 +v 5020 3.753051 5062 +v 5022 2.76135349 5062 +v 5024 1.09358025 5062 +v 5026 -0.841939569 5062 +v 5028 -2.57132316 5062 +v 5030 -3.67115712 5062 +v 5032 -3.872164 5062 +v 5034 -3.12513 5062 +v 5036 -1.61295509 5062 +v 5038 0.2941275 5062 +v 5040 2.12919736 5062 +v 5042 3.44296551 5062 +v 5044 3.91377568 5062 +v 5046 3.42635679 5062 +v 5048 2.10004663 5062 +v 5050 0.259571552 5062 +v 5052 -1.64445555 5062 +v 5054 -3.14586258 5062 +v 5056 -3.87705278 5062 +v 5058 -3.65900517 5062 +v 5060 -2.5451057 5062 +v 5062 -0.808075249 5062 +v 5064 1.1268 5062 +v 5066 2.78579521 5062 +v 5068 3.7627306 5062 +v 5070 3.8184185 5062 +v 5072 2.93922424 5062 +v 5074 1.34040546 5062 +v 5076 -0.5865915 5062 +v 5078 -2.36997032 5062 +v 5080 -3.573098 5062 +v 5082 -3.90140629 5062 +v 5084 -3.27451444 5062 +v 5086 -1.84590721 5062 +v 5088 0.034642376 5062 +v 5090 1.90671039 5062 +v 5092 3.31194925 5062 +v 5094 3.90630722 5062 +v 5096 3.54426527 5062 +v 5098 2.31446338 5062 +v 5000 0 5064 +v 5002 -1.83650565 5064 +v 5004 -3.22337055 5064 +v 5006 -3.821042 5064 +v 5008 -3.483189 5064 +v 5010 -2.29253 5064 +v 5012 -0.5405796 5064 +v 5014 1.34372354 5064 +v 5016 2.89903641 5064 +v 5018 3.744564 5064 +v 5020 3.67329168 5064 +v 5022 2.70266938 5064 +v 5024 1.07033956 5064 +v 5026 -0.8240468 5064 +v 5028 -2.51667786 5064 +v 5030 -3.59313822 5064 +v 5032 -3.78987312 5064 +v 5034 -3.058715 5064 +v 5036 -1.5786767 5064 +v 5038 0.287876755 5064 +v 5040 2.083948 5064 +v 5042 3.369796 5064 +v 5044 3.8306005 5064 +v 5046 3.35354018 5064 +v 5048 2.05541658 5064 +v 5050 0.254055172 5064 +v 5052 -1.6095078 5064 +v 5054 -3.07900715 5064 +v 5056 -3.79465818 5064 +v 5058 -3.58124447 5064 +v 5060 -2.49101734 5064 +v 5062 -0.790902138 5064 +v 5064 1.1028533 5064 +v 5066 2.72659183 5064 +v 5068 3.68276572 5064 +v 5070 3.73726988 5064 +v 5072 2.87676024 5064 +v 5074 1.31191933 5064 +v 5076 -0.5741253 5064 +v 5078 -2.319604 5064 +v 5080 -3.49716282 5064 +v 5082 -3.818494 5064 +v 5084 -3.20492482 5064 +v 5086 -1.8066783 5064 +v 5088 0.03390616 5064 +v 5090 1.86618912 5064 +v 5092 3.241564 5064 +v 5094 3.82329082 5064 +v 5096 3.46894288 5064 +v 5098 2.26527667 5064 +v 5000 0 5066 +v 5002 -1.34698832 5066 +v 5004 -2.36418676 5066 +v 5006 -2.80255 5066 +v 5008 -2.55475116 5066 +v 5010 -1.68146014 5066 +v 5012 -0.396489084 5066 +v 5014 0.9855564 5066 +v 5016 2.12630343 5066 +v 5018 2.746457 5066 +v 5020 2.6941824 5066 +v 5022 1.98227775 5066 +v 5024 0.785042465 5066 +v 5026 -0.604398549 5066 +v 5028 -1.84586179 5066 +v 5030 -2.63539362 5066 +v 5032 -2.77968931 5066 +v 5034 -2.24342 5066 +v 5036 -1.15788329 5066 +v 5038 0.2111437 5066 +v 5040 1.52847528 5066 +v 5042 2.471583 5066 +v 5044 2.80956078 5066 +v 5046 2.45966 5066 +v 5048 1.50754893 5066 +v 5050 0.186337218 5066 +v 5052 -1.18049634 5066 +v 5054 -2.25830317 5066 +v 5056 -2.78319883 5066 +v 5058 -2.62667012 5066 +v 5060 -1.82704115 5066 +v 5062 -0.5800886 5066 +v 5064 0.808889747 5066 +v 5066 1.99982357 5066 +v 5068 2.70113087 5066 +v 5070 2.74110723 5066 +v 5072 2.10996485 5066 +v 5074 0.96222955 5066 +v 5076 -0.421093225 5066 +v 5078 -1.70131767 5066 +v 5080 -2.5650003 5066 +v 5082 -2.80068135 5066 +v 5084 -2.3506577 5066 +v 5086 -1.32511139 5066 +v 5088 0.0248685349 5066 +v 5090 1.36875975 5066 +v 5092 2.377531 5066 +v 5094 2.80419946 5066 +v 5096 2.54430223 5066 +v 5098 1.66147113 5066 +v 5000 0 5068 +v 5002 -0.527681351 5068 +v 5004 -0.9261679 5068 +v 5006 -1.09789622 5068 +v 5008 -1.00082123 5068 +v 5010 -0.658710361 5068 +v 5012 -0.1553242 5068 +v 5014 0.386090755 5068 +v 5016 0.832977235 5068 +v 5018 1.07592189 5068 +v 5020 1.05544329 5068 +v 5022 0.776555359 5068 +v 5024 0.3075396 5068 +v 5026 -0.236772552 5068 +v 5028 -0.72311455 5068 +v 5030 -1.03241289 5068 +v 5032 -1.0889405 5068 +v 5034 -0.878857553 5068 +v 5036 -0.453599632 5068 +v 5038 0.08271534 5068 +v 5040 0.5987787 5068 +v 5042 0.968240142 5068 +v 5044 1.10064268 5068 +v 5046 0.9635694 5068 +v 5048 0.5905808 5068 +v 5050 0.07299742 5068 +v 5052 -0.462458283 5068 +v 5054 -0.8846881 5068 +v 5056 -1.09031534 5068 +v 5058 -1.02899539 5068 +v 5060 -0.7157416 5068 +v 5062 -0.227249131 5068 +v 5064 0.316881746 5068 +v 5066 0.7834289 5068 +v 5068 1.05816543 5068 +v 5070 1.07382607 5068 +v 5072 0.82657665 5068 +v 5074 0.376952469 5068 +v 5076 -0.164962858 5068 +v 5078 -0.6664895 5068 +v 5080 -1.00483632 5068 +v 5082 -1.09716415 5068 +v 5084 -0.92086786 5068 +v 5086 -0.5191111 5068 +v 5088 0.009742224 5068 +v 5090 0.5362103 5068 +v 5092 0.9313954 5068 +v 5094 1.09854245 5068 +v 5096 0.996727943 5068 +v 5098 0.6508797 5068 +v 5000 0 5070 +v 5002 0.4208205 5070 +v 5004 0.738609433 5070 +v 5006 0.875561059 5070 +v 5008 0.798144758 5070 +v 5010 0.5253148 5070 +v 5012 0.123869471 5070 +v 5014 -0.307903439 5070 +v 5016 -0.664290845 5070 +v 5018 -0.8580367 5070 +v 5020 -0.841705263 5070 +v 5022 -0.619294941 5070 +v 5024 -0.2452597 5070 +v 5026 0.1888237 5070 +v 5028 0.5766765 5070 +v 5030 0.823338747 5070 +v 5032 0.868419 5070 +v 5034 0.700879931 5070 +v 5036 0.361741066 5070 +v 5038 -0.06596464 5070 +v 5040 -0.477519929 5070 +v 5042 -0.772161663 5070 +v 5044 -0.8777513 5070 +v 5046 -0.7684368 5070 +v 5048 -0.4709822 5070 +v 5050 -0.058214698 5070 +v 5052 0.368805766 5070 +v 5054 0.705529749 5070 +v 5056 0.8695154 5070 +v 5058 0.8206133 5070 +v 5060 0.5707966 5070 +v 5062 0.181228876 5070 +v 5064 -0.252709985 5070 +v 5066 -0.6247766 5070 +v 5068 -0.8438761 5070 +v 5070 -0.8563653 5070 +v 5072 -0.6591865 5070 +v 5074 -0.300615758 5070 +v 5076 0.1315562 5070 +v 5078 0.5315186 5070 +v 5080 0.8013467 5070 +v 5082 0.874977231 5070 +v 5084 0.734382749 5070 +v 5086 0.4139858 5070 +v 5088 -0.00776932435 5070 +v 5090 -0.427622229 5070 +v 5092 -0.742778361 5070 +v 5094 -0.876076341 5070 +v 5096 -0.794880331 5070 +v 5098 -0.5190699 5070 +v 5000 0 5072 +v 5002 1.26629078 5072 +v 5004 2.22254944 5072 +v 5006 2.63465047 5072 +v 5008 2.40169716 5072 +v 5010 1.5807246 5072 +v 5012 0.3727356 5072 +v 5014 -0.9265121 5072 +v 5016 -1.99891734 5072 +v 5018 -2.58191776 5072 +v 5020 -2.532775 5072 +v 5022 -1.86352015 5072 +v 5024 -0.7380109 5072 +v 5026 0.568189263 5072 +v 5028 1.73527694 5072 +v 5030 2.477508 5072 +v 5032 2.61315918 5072 +v 5034 2.10901761 5072 +v 5036 1.08851492 5072 +v 5038 -0.198494166 5072 +v 5040 -1.43690491 5072 +v 5042 -2.32351136 5072 +v 5044 -2.641241 5072 +v 5046 -2.31230283 5072 +v 5048 -1.41723228 5072 +v 5050 -0.175173819 5072 +v 5052 1.10977328 5072 +v 5054 2.1230092 5072 +v 5056 2.61645842 5072 +v 5058 2.46930718 5072 +v 5060 1.71758378 5072 +v 5062 0.5453357 5072 +v 5064 -0.760429442 5072 +v 5066 -1.8800149 5072 +v 5068 -2.53930736 5072 +v 5070 -2.57688856 5072 +v 5072 -1.9835577 5072 +v 5074 -0.904582739 5072 +v 5076 0.395865679 5072 +v 5078 1.59939229 5072 +v 5080 2.41133213 5072 +v 5082 2.63289356 5072 +v 5084 2.20983076 5072 +v 5086 1.24572444 5072 +v 5088 -0.02337867 5072 +v 5090 -1.28675783 5072 +v 5092 -2.235094 5072 +v 5094 -2.636201 5072 +v 5096 -2.391874 5072 +v 5098 -1.561933 5072 +v 5000 0 5074 +v 5002 1.801729 5074 +v 5004 3.16233158 5074 +v 5006 3.7486856 5074 +v 5008 3.41723013 5074 +v 5010 2.24911785 5074 +v 5012 0.530343 5074 +v 5014 -1.31827831 5074 +v 5016 -2.844139 5074 +v 5018 -3.67365551 5074 +v 5020 -3.603733 5074 +v 5022 -2.65149069 5074 +v 5024 -1.05007124 5074 +v 5026 0.808442354 5074 +v 5028 2.469021 5074 +v 5030 3.52509713 5074 +v 5032 3.71810675 5074 +v 5034 3.00079417 5074 +v 5036 1.54878235 5074 +v 5038 -0.2824254 5074 +v 5040 -2.04448557 5074 +v 5042 -3.30598426 5074 +v 5044 -3.75806284 5074 +v 5046 -3.29003644 5074 +v 5048 -2.01649451 5074 +v 5050 -0.249244288 5074 +v 5052 1.57902956 5074 +v 5054 3.020702 5074 +v 5056 3.72280121 5074 +v 5058 3.51342869 5074 +v 5060 2.44384646 5074 +v 5062 0.775925338 5074 +v 5064 -1.08196926 5074 +v 5066 -2.67496014 5074 +v 5068 -3.61302733 5074 +v 5070 -3.66649961 5074 +v 5072 -2.8222847 5074 +v 5074 -1.28707635 5074 +v 5076 0.563253462 5074 +v 5078 2.275679 5074 +v 5080 3.4309392 5074 +v 5082 3.74618554 5074 +v 5084 3.14423513 5074 +v 5086 1.7724663 5074 +v 5088 -0.0332641 5074 +v 5090 -1.83085036 5074 +v 5092 -3.18018055 5074 +v 5094 -3.75089169 5074 +v 5096 -3.40325379 5074 +v 5098 -2.2223804 5074 +v 5000 0 5076 +v 5002 1.896041 5076 +v 5004 3.327865 5076 +v 5006 3.94491172 5076 +v 5008 3.59610629 5076 +v 5010 2.36684871 5076 +v 5012 0.558104 5076 +v 5014 -1.387284 5076 +v 5016 -2.99301648 5076 +v 5018 -3.86595416 5076 +v 5020 -3.79237151 5076 +v 5022 -2.790284 5076 +v 5024 -1.10503757 5076 +v 5026 0.8507605 5076 +v 5028 2.59826279 5076 +v 5030 3.70961952 5076 +v 5032 3.91273236 5076 +v 5034 3.15787172 5076 +v 5036 1.62985384 5076 +v 5038 -0.297209054 5076 +v 5040 -2.15150476 5076 +v 5042 -3.479037 5076 +v 5044 -3.95477986 5076 +v 5046 -3.46225429 5076 +v 5048 -2.12204862 5076 +v 5050 -0.262291044 5076 +v 5052 1.66168427 5076 +v 5054 3.17882156 5076 +v 5056 3.9176724 5076 +v 5058 3.69734025 5076 +v 5060 2.57177043 5076 +v 5062 0.816541433 5076 +v 5064 -1.13860536 5076 +v 5066 -2.8149817 5076 +v 5068 -3.8021524 5076 +v 5070 -3.85842371 5076 +v 5072 -2.97001815 5076 +v 5074 -1.35444868 5076 +v 5076 0.592737138 5076 +v 5078 2.39480019 5076 +v 5080 3.61053276 5076 +v 5082 3.942281 5076 +v 5084 3.3088212 5076 +v 5086 1.86524665 5076 +v 5088 -0.03500532 5076 +v 5090 -1.92668676 5076 +v 5092 -3.34664822 5076 +v 5094 -3.9472332 5076 +v 5096 -3.581398 5076 +v 5098 -2.33871174 5076 +v 5000 0 5078 +v 5002 1.52613616 5078 +v 5004 2.67862082 5078 +v 5006 3.17528582 5078 +v 5008 2.89453 5078 +v 5010 1.90509236 5078 +v 5012 0.449221641 5078 +v 5014 -1.11663425 5078 +v 5016 -2.409099 5078 +v 5018 -3.11173248 5078 +v 5020 -3.05250525 5078 +v 5022 -2.24591827 5078 +v 5024 -0.889452159 5078 +v 5026 0.684782863 5078 +v 5028 2.09135914 5078 +v 5030 2.98589778 5078 +v 5032 3.1493845 5078 +v 5034 2.54179215 5078 +v 5036 1.31188035 5078 +v 5038 -0.239225566 5078 +v 5040 -1.73176074 5078 +v 5042 -2.80030036 5078 +v 5044 -3.18322873 5078 +v 5046 -2.7867918 5078 +v 5048 -1.70805109 5078 +v 5050 -0.211119831 5078 +v 5052 1.33750093 5078 +v 5054 2.55865479 5078 +v 5056 3.15336084 5078 +v 5058 2.976014 5078 +v 5060 2.07003522 5078 +v 5062 0.6572397 5078 +v 5064 -0.916471064 5078 +v 5066 -2.26579762 5078 +v 5068 -3.060378 5078 +v 5070 -3.10567117 5078 +v 5072 -2.39058757 5078 +v 5074 -1.09020483 5078 +v 5076 0.4770981 5078 +v 5078 1.92759085 5078 +v 5080 2.90614223 5078 +v 5082 3.17316842 5078 +v 5084 2.66329241 5078 +v 5086 1.50134957 5078 +v 5088 -0.028176019 5078 +v 5090 -1.55080307 5078 +v 5092 -2.69373965 5078 +v 5094 -3.17715454 5078 +v 5096 -2.88269138 5078 +v 5098 -1.88244474 5078 +v 5000 0 5080 +v 5002 0.7825799 5080 +v 5004 1.37355685 5080 +v 5006 1.62823927 5080 +v 5008 1.48427188 5080 +v 5010 0.976902962 5080 +v 5012 0.230354175 5080 +v 5014 -0.5725934 5080 +v 5016 -1.23535013 5080 +v 5018 -1.59565008 5080 +v 5020 -1.56527925 5080 +v 5022 -1.15167344 5080 +v 5024 -0.4560978 5080 +v 5026 0.35114643 5080 +v 5028 1.07241774 5080 +v 5030 1.53112388 5080 +v 5032 1.61495745 5080 +v 5034 1.30339313 5080 +v 5036 0.6727127 5080 +v 5038 -0.122671306 5080 +v 5040 -0.888021052 5080 +v 5042 -1.43595231 5080 +v 5044 -1.6323123 5080 +v 5046 -1.42902529 5080 +v 5048 -0.8758632 5080 +v 5050 -0.108259104 5080 +v 5052 0.6858505 5080 +v 5054 1.31204009 5080 +v 5056 1.61699641 5080 +v 5058 1.52605557 5080 +v 5060 1.06148326 5080 +v 5062 0.3370227 5080 +v 5064 -0.4699527 5080 +v 5066 -1.16186726 5080 +v 5068 -1.56931627 5080 +v 5070 -1.59254193 5080 +v 5072 -1.22585773 5080 +v 5074 -0.5590408 5080 +v 5076 0.2446488 5080 +v 5078 0.988439858 5080 +v 5080 1.49022639 5080 +v 5082 1.62715352 5080 +v 5084 1.36569667 5080 +v 5086 0.7698697 5080 +v 5088 -0.0144482423 5080 +v 5090 -0.7952287 5080 +v 5092 -1.38130951 5080 +v 5094 -1.62919748 5080 +v 5096 -1.47820115 5080 +v 5098 -0.9652896 5080 +v 5000 0 5082 +v 5002 -0.152579218 5082 +v 5004 -0.267801732 5082 +v 5006 -0.31745702 5082 +v 5008 -0.289387763 5082 +v 5010 -0.1904663 5082 +v 5012 -0.04491204 5082 +v 5014 0.111638255 5082 +v 5016 0.240855619 5082 +v 5018 0.311103135 5082 +v 5020 0.305181742 5082 +v 5022 0.2245412 5082 +v 5024 0.08892517 5082 +v 5026 -0.0684628561 5082 +v 5028 -0.209088787 5082 +v 5030 -0.298522472 5082 +v 5032 -0.314867467 5082 +v 5034 -0.254121929 5082 +v 5036 -0.131158471 5082 +v 5038 0.0239171647 5082 +v 5040 0.173137039 5082 +v 5042 0.279966921 5082 +v 5044 0.318251163 5082 +v 5046 0.278616369 5082 +v 5048 0.170766622 5082 +v 5050 0.0211072247 5082 +v 5052 -0.133719951 5082 +v 5054 -0.255807817 5082 +v 5056 -0.31526503 5082 +v 5058 -0.297534317 5082 +v 5060 -0.206956878 5082 +v 5062 -0.06570916 5082 +v 5064 0.09162646 5082 +v 5066 0.2265287 5082 +v 5068 0.305968851 5082 +v 5070 0.310497135 5082 +v 5072 0.23900488 5082 +v 5074 0.108995922 5082 +v 5076 -0.04769906 5082 +v 5078 -0.192715645 5082 +v 5080 -0.2905487 5082 +v 5082 -0.317245334 5082 +v 5084 -0.2662692 5082 +v 5086 -0.150101125 5082 +v 5088 0.002816967 5082 +v 5090 0.15504536 5082 +v 5092 0.269313246 5082 +v 5094 0.317643851 5082 +v 5096 0.288204163 5082 +v 5098 0.188202053 5082 +v 5000 0 5084 +v 5002 -1.05038154 5084 +v 5004 -1.843593 5084 +v 5006 -2.18542886 5084 +v 5008 -1.99219513 5084 +v 5010 -1.31120276 5084 +v 5012 -0.309182227 5084 +v 5014 0.7685369 5084 +v 5016 1.65809143 5084 +v 5018 2.14168739 5084 +v 5020 2.10092354 5084 +v 5022 1.5457803 5084 +v 5024 0.6121761 5084 +v 5026 -0.471310049 5084 +v 5028 -1.439403 5084 +v 5030 -2.05508018 5084 +v 5032 -2.16760182 5084 +v 5034 -1.749419 5084 +v 5036 -0.902917445 5084 +v 5038 0.164649874 5084 +v 5040 1.19190514 5084 +v 5042 1.92734039 5084 +v 5044 2.19089556 5084 +v 5046 1.918043 5084 +v 5048 1.17558682 5084 +v 5050 0.145305768 5084 +v 5052 -0.9205511 5084 +v 5054 -1.761025 5084 +v 5056 -2.17033863 5084 +v 5058 -2.04827738 5084 +v 5060 -1.42472672 5084 +v 5062 -0.45235312 5084 +v 5064 0.630772233 5084 +v 5066 1.55946255 5084 +v 5068 2.106342 5084 +v 5070 2.13751554 5084 +v 5072 1.64535069 5084 +v 5074 0.750346661 5084 +v 5076 -0.3283685 5084 +v 5078 -1.32668757 5084 +v 5080 -2.0001874 5084 +v 5082 -2.18397141 5084 +v 5084 -1.833043 5084 +v 5086 -1.033322 5084 +v 5088 0.0193924848 5084 +v 5090 1.067359 5084 +v 5092 1.85399878 5084 +v 5094 2.186715 5084 +v 5096 1.984047 5084 +v 5098 1.29561532 5084 +v 5000 0 5086 +v 5002 -1.69101393 5086 +v 5004 -2.96800852 5086 +v 5006 -3.51833129 5086 +v 5008 -3.20724368 5086 +v 5010 -2.11091113 5086 +v 5012 -0.4977538 5086 +v 5014 1.237271 5086 +v 5016 2.66936874 5086 +v 5018 3.44791174 5086 +v 5020 3.382286 5086 +v 5022 2.48855829 5086 +v 5024 0.985545039 5086 +v 5026 -0.7587641 5086 +v 5028 -2.31730127 5086 +v 5030 -3.30848241 5086 +v 5032 -3.48963165 5086 +v 5034 -2.81639743 5086 +v 5036 -1.45361066 5086 +v 5038 0.265070558 5086 +v 5040 1.91885328 5086 +v 5042 3.10283375 5086 +v 5044 3.52713227 5086 +v 5046 3.08786583 5086 +v 5048 1.8925823 5086 +v 5050 0.233928382 5086 +v 5052 -1.48199916 5086 +v 5054 -2.83508182 5086 +v 5056 -3.49403739 5086 +v 5058 -3.297531 5086 +v 5060 -2.29367375 5086 +v 5062 -0.728245258 5086 +v 5064 1.015483 5086 +v 5066 2.51058555 5086 +v 5068 3.39100933 5086 +v 5070 3.44119573 5086 +v 5072 2.64885736 5086 +v 5074 1.20798635 5086 +v 5076 -0.528641939 5086 +v 5078 -2.13584018 5086 +v 5080 -3.22011018 5086 +v 5082 -3.515985 5086 +v 5084 -2.951024 5086 +v 5086 -1.66354942 5086 +v 5088 0.0312200449 5086 +v 5090 1.71834576 5086 +v 5092 2.98476076 5086 +v 5094 3.520402 5086 +v 5096 3.19412613 5086 +v 5098 2.08581662 5086 +v 5000 0 5088 +v 5002 -1.9176271 5088 +v 5004 -3.365752 5088 +v 5006 -3.98982382 5088 +v 5008 -3.63704729 5088 +v 5010 -2.39379478 5088 +v 5012 -0.5644579 5088 +v 5014 1.403078 5088 +v 5016 3.0270915 5088 +v 5018 3.90996742 5088 +v 5020 3.835547 5088 +v 5022 2.82205057 5088 +v 5024 1.1176182 5088 +v 5026 -0.8604463 5088 +v 5028 -2.62784338 5088 +v 5030 -3.751853 5088 +v 5032 -3.957278 5088 +v 5034 -3.19382334 5088 +v 5036 -1.64840937 5088 +v 5038 0.300592721 5088 +v 5040 2.1759994 5088 +v 5042 3.51864529 5088 +v 5044 3.99980426 5088 +v 5046 3.50167155 5088 +v 5048 2.14620757 5088 +v 5050 0.265277177 5088 +v 5052 -1.68060231 5088 +v 5054 -3.21501184 5088 +v 5056 -3.96227431 5088 +v 5058 -3.73943377 5088 +v 5060 -2.60104966 5088 +v 5062 -0.825837553 5088 +v 5064 1.15156817 5088 +v 5066 2.84702969 5088 +v 5068 3.8454392 5088 +v 5070 3.90235114 5088 +v 5072 3.00383139 5088 +v 5074 1.36986887 5088 +v 5076 -0.599485338 5088 +v 5078 -2.42206454 5088 +v 5080 -3.651638 5088 +v 5082 -3.987163 5088 +v 5084 -3.34649134 5088 +v 5086 -1.88648212 5088 +v 5088 0.03540385 5088 +v 5090 1.94862175 5088 +v 5092 3.38474917 5088 +v 5094 3.99217176 5088 +v 5096 3.62217164 5088 +v 5098 2.36533737 5088 +v 5000 0 5090 +v 5002 -1.67473829 5090 +v 5004 -2.93944216 5090 +v 5006 -3.48446822 5090 +v 5008 -3.17637467 5090 +v 5010 -2.090594 5090 +v 5012 -0.492963016 5090 +v 5014 1.22536254 5090 +v 5016 2.64367652 5090 +v 5018 3.4147265 5090 +v 5020 3.34973216 5090 +v 5022 2.46460652 5090 +v 5024 0.9760594 5090 +v 5026 -0.751461148 5090 +v 5028 -2.294998 5090 +v 5030 -3.276639 5090 +v 5032 -3.45604467 5090 +v 5034 -2.78929019 5090 +v 5036 -1.43962 5090 +v 5038 0.2625193 5090 +v 5040 1.90038478 5090 +v 5042 3.07296968 5090 +v 5044 3.49318457 5090 +v 5046 3.05814576 5090 +v 5048 1.87436664 5090 +v 5050 0.231676877 5090 +v 5052 -1.46773529 5090 +v 5054 -2.80779481 5090 +v 5056 -3.46040821 5090 +v 5058 -3.26579285 5090 +v 5060 -2.27159762 5090 +v 5062 -0.72123605 5090 +v 5064 1.00570917 5090 +v 5066 2.48642182 5090 +v 5068 3.3583715 5090 +v 5070 3.40807486 5090 +v 5072 2.62336254 5090 +v 5074 1.19635975 5090 +v 5076 -0.523553848 5090 +v 5078 -2.11528325 5090 +v 5080 -3.18911743 5090 +v 5082 -3.48214459 5090 +v 5084 -2.922621 5090 +v 5086 -1.64753819 5090 +v 5088 0.03091956 5090 +v 5090 1.70180714 5090 +v 5092 2.956033 5090 +v 5094 3.48651886 5090 +v 5096 3.16338325 5090 +v 5098 2.065741 5090 +v 5000 0 5092 +v 5002 -1.02181506 5092 +v 5004 -1.79345417 5092 +v 5006 -2.12599325 5092 +v 5008 -1.93801475 5092 +v 5010 -1.27554286 5092 +v 5012 -0.3007736 5092 +v 5014 0.7476356 5092 +v 5016 1.61299753 5092 +v 5018 2.0834415 5092 +v 5020 2.04378629 5092 +v 5022 1.50374079 5092 +v 5024 0.595527232 5092 +v 5026 -0.45849216 5092 +v 5028 -1.40025663 5092 +v 5030 -1.9991895 5092 +v 5032 -2.10865116 5092 +v 5034 -1.70184124 5092 +v 5036 -0.8783614 5092 +v 5038 0.160172 5092 +v 5040 1.15948975 5092 +v 5042 1.874924 5092 +v 5044 2.13131142 5092 +v 5046 1.8658793 5092 +v 5048 1.14361513 5092 +v 5050 0.14135398 5092 +v 5052 -0.8955155 5092 +v 5054 -1.71313155 5092 +v 5056 -2.11131334 5092 +v 5058 -1.99257183 5092 +v 5060 -1.38597941 5092 +v 5062 -0.440050781 5092 +v 5064 0.6136176 5092 +v 5066 1.517051 5092 +v 5068 2.04905725 5092 +v 5070 2.07938313 5092 +v 5072 1.60060322 5092 +v 5074 0.72994 5092 +v 5076 -0.3194381 5092 +v 5078 -1.29060662 5092 +v 5080 -1.94578958 5092 +v 5082 -2.12457538 5092 +v 5084 -1.783191 5092 +v 5086 -1.00521934 5092 +v 5088 0.01886508 5092 +v 5090 1.03833067 5092 +v 5092 1.80357683 5092 +v 5094 2.12724423 5092 +v 5096 1.93008828 5092 +v 5098 1.26037931 5092 +v 5000 0 5094 +v 5002 -0.11871592 5094 +v 5004 -0.208366036 5094 +v 5006 -0.247000888 5094 +v 5008 -0.2251613 5094 +v 5010 -0.148194373 5094 +v 5012 -0.0349443 5094 +v 5014 0.08686136 5094 +v 5016 0.187400326 5094 +v 5018 0.24205716 5094 +v 5020 0.237449959 5094 +v 5022 0.174706727 5094 +v 5024 0.06918919 5094 +v 5026 -0.053268265 5094 +v 5028 -0.1626838 5094 +v 5030 -0.232268661 5094 +v 5032 -0.244986057 5094 +v 5034 -0.197722331 5094 +v 5036 -0.102049269 5094 +v 5038 0.01860901 5094 +v 5040 0.134711146 5094 +v 5042 0.2178313 5094 +v 5044 0.247618765 5094 +v 5046 0.2167805 5094 +v 5048 0.132866815 5094 +v 5050 0.0164227057 5094 +v 5052 -0.104042254 5094 +v 5054 -0.19903405 5094 +v 5056 -0.245295361 5094 +v 5058 -0.2314998 5094 +v 5060 -0.161025047 5094 +v 5062 -0.05112572 5094 +v 5064 0.0712909549 5094 +v 5066 0.176253125 5094 +v 5068 0.238062382 5094 +v 5070 0.241585657 5094 +v 5072 0.185960338 5094 +v 5074 0.08480546 5094 +v 5076 -0.03711277 5094 +v 5078 -0.1499445 5094 +v 5080 -0.226064578 5094 +v 5082 -0.246836171 5094 +v 5084 -0.207173645 5094 +v 5086 -0.116787806 5094 +v 5088 0.00219177175 5094 +v 5090 0.120634727 5094 +v 5092 0.2095421 5094 +v 5094 0.247146249 5094 +v 5096 0.224240378 5094 +v 5098 0.146432653 5094 +v 5000 0 5096 +v 5002 0.813449 5096 +v 5004 1.42773724 5096 +v 5006 1.69246578 5096 +v 5008 1.5428195 5096 +v 5010 1.01543725 5096 +v 5012 0.239440575 5096 +v 5014 -0.595179558 5096 +v 5016 -1.284079 5096 +v 5018 -1.658591 5096 +v 5020 -1.62702227 5096 +v 5022 -1.19710159 5096 +v 5024 -0.474088758 5096 +v 5026 0.364997536 5096 +v 5028 1.11471975 5096 +v 5030 1.59151959 5096 +v 5032 1.67866 5096 +v 5034 1.354806 5096 +v 5036 0.6992481 5096 +v 5038 -0.127510116 5096 +v 5040 -0.9230494 5096 +v 5042 -1.492594 5096 +v 5044 -1.6966995 5096 +v 5046 -1.48539376 5096 +v 5048 -0.9104119 5096 +v 5050 -0.112529427 5096 +v 5052 0.712904155 5096 +v 5054 1.363794 5096 +v 5056 1.68077946 5096 +v 5058 1.58625138 5096 +v 5060 1.10335386 5096 +v 5062 0.3503167 5096 +v 5064 -0.488490164 5096 +v 5066 -1.20769763 5096 +v 5068 -1.63121855 5096 +v 5070 -1.65536034 5096 +v 5072 -1.27421212 5096 +v 5074 -0.5810924 5096 +v 5076 0.254299074 5096 +v 5078 1.02742922 5096 +v 5080 1.549009 5096 +v 5082 1.69133711 5096 +v 5084 1.419567 5096 +v 5086 0.8002375 5096 +v 5088 -0.0150181586 5096 +v 5090 -0.8265968 5096 +v 5092 -1.43579578 5096 +v 5094 -1.69346189 5096 +v 5096 -1.53650939 5096 +v 5098 -1.00336587 5096 +v 5000 0 5098 +v 5002 1.54645324 5098 +v 5004 2.71428061 5098 +v 5006 3.21755767 5098 +v 5008 2.93306422 5098 +v 5010 1.93045437 5098 +v 5012 0.455202 5098 +v 5014 -1.13149965 5098 +v 5016 -2.441171 5098 +v 5018 -3.15315819 5098 +v 5020 -3.09314251 5098 +v 5022 -2.27581763 5098 +v 5024 -0.9012932 5098 +v 5026 0.6938992 5098 +v 5028 2.119201 5098 +v 5030 3.02564836 5098 +v 5032 3.19131136 5098 +v 5034 2.57563043 5098 +v 5036 1.329345 5098 +v 5038 -0.242410317 5098 +v 5040 -1.75481522 5098 +v 5042 -2.83758 5098 +v 5044 -3.22560644 5098 +v 5046 -2.82389164 5098 +v 5048 -1.73079 5098 +v 5050 -0.213930413 5098 +v 5052 1.35530674 5098 +v 5054 2.59271765 5098 +v 5056 3.19534063 5098 +v 5058 3.01563287 5098 +v 5060 2.09759331 5098 +v 5062 0.665989339 5098 +v 5064 -0.928671837 5098 +v 5066 -2.29596162 5098 +v 5068 -3.10112023 5098 +v 5070 -3.14701629 5098 +v 5072 -2.42241287 5098 +v 5074 -1.10471845 5098 +v 5076 0.4834496 5098 +v 5078 1.95325232 5098 +v 5080 2.944831 5098 +v 5082 3.215412 5098 +v 5084 2.698748 5098 +v 5086 1.52133667 5098 +v 5088 -0.0285511184 5098 +v 5090 -1.57144856 5098 +v 5092 -2.72960067 5098 +v 5094 -3.21945119 5098 +v 5096 -2.921068 5098 +v 5098 -1.90750527 5098 +v 5041.501 3.00283384 5086.939 +v 5038.10645 0.200592726 5087.356 +v 5039.2207 2.0759995 5088.569 +v 5041.501 6.002834 5086.939 +v 5038.10645 3.20059276 5087.356 +v 5039.2207 5.07599926 5088.569 +v 5086.71533 -0.216787815 5094.024 +v 5086.91553 -0.216787815 5093.42139 +v 5085.695 -1.74753821 5090.151 +v 5086.71533 2.78321218 5094.024 +v 5086.91553 2.78321218 5093.42139 +v 5085.695 1.25246179 5090.151 +v 5051.193 -1.198558 5015.15039 +v 5056.2583 -3.81064343 5014.17139 +v 5051.712 -1.7638489 5011.50146 +v 5051.193 1.801442 5015.15039 +v 5056.2583 -0.810643435 5014.17139 +v 5051.712 1.2361511 5011.50146 +v 5049.01074 -0.323863983 5048.376 +v 5046.071 -3.59409857 5049.386 +v 5050.30469 -0.340734363 5051.956 +v 5049.01074 2.676136 5048.376 +v 5046.071 -0.594098568 5049.386 +v 5050.30469 2.65926552 5051.956 +v 5084.278 1.89097476 5054.02637 +v 5082.528 3.51828027 5052.56152 +v 5083.732 1.89097476 5054.808 +v 5084.278 4.890975 5054.02637 +v 5082.528 6.51828 5052.56152 +v 5083.732 4.890975 5054.808 +v 5030.64062 1.43112385 5079.487 +v 5029.85645 3.42509723 5073.223 +v 5029.19238 2.88589787 5078.751 +v 5030.64062 4.43112373 5079.487 +v 5029.85645 6.42509747 5073.223 +v 5029.19238 5.88589764 5078.751 +v 5030.248 -3.10589051 5010.90527 +v 5031.07861 -4.017829 5012.503 +v 5031.709 -4.017829 5011.73047 +v 5030.248 -0.105890512 5010.90527 +v 5031.07861 -1.017829 5012.503 +v 5031.709 -1.017829 5011.73047 +v 5001.95459 -1.77473831 5089.92773 +v 5000.69238 -0.1 5089.886 +v 5001.50049 -1.12181509 5091.37744 +v 5001.95459 1.22526169 5089.92773 +v 5000.69238 2.9 5089.886 +v 5001.50049 1.87818491 5091.37744 +v 5040.72949 2.02919745 5061.974 +v 5045.284 3.25354028 5064.201 +v 5044.61475 3.81377578 5062.441 +v 5040.72949 5.02919769 5061.974 +v 5045.284 6.25354 5064.201 +v 5044.61475 6.813776 5062.441 +v 5002.348 1.582942 5002.953 +v 5001.407 0.936138868 5003.50537 +v 5000.959 -0.1 5004.85 +v 5002.348 4.582942 5002.953 +v 5001.407 3.93613887 5003.50537 +v 5000.959 2.9 5004.85 +v 5012.365 -0.218990028 5017.604 +v 5019.32959 2.407179 5015.48926 +v 5015.416 2.73485112 5014.367 +v 5012.365 2.78101 5017.604 +v 5019.32959 5.407179 5015.48926 +v 5015.416 5.734851 5014.367 +v 5005.55371 -0.680542231 5032.6626 +v 5005.91455 -3.73539138 5036.80664 +v 5007.769 -3.72690415 5037.0376 +v 5005.55371 2.31945777 5032.6626 +v 5005.91455 -0.7353914 5036.80664 +v 5007.769 -0.726904154 5037.0376 +v 5036.351 -0.4474914 5018.515 +v 5035.85156 -0.4474914 5018.811 +v 5039.55 0.358709484 5018.87549 +v 5036.351 2.55250859 5018.515 +v 5035.85156 2.55250859 5018.811 +v 5039.55 3.35870957 5018.87549 +v 5054.95947 -3.30604577 5038.862 +v 5056.4917 -3.42476153 5039.885 +v 5056.22266 -4.05122471 5037.2334 +v 5054.95947 -0.306045771 5038.862 +v 5056.4917 -0.424761534 5039.885 +v 5056.22266 -1.05122471 5037.2334 +v 5044.463 2.938722 5059.55029 +v 5044.81934 2.938722 5059.581 +v 5046.154 2.56028175 5059.35449 +v 5044.463 5.93872166 5059.55029 +v 5044.81934 5.93872166 5059.581 +v 5046.154 5.56028175 5059.35449 +v 5062.009 -0.393121272 5057.53271 +v 5061.40869 0.0129271522 5055.79834 +v 5060.50732 -1.02321172 5057.67773 +v 5062.009 2.60687876 5057.53271 +v 5061.40869 3.012927 5055.79834 +v 5060.50732 1.97678828 5057.67773 +v 5043.37 1.31968319 5057.062 +v 5045.03125 1.14287686 5057.47266 +v 5042.75635 -0.5811486 5055.11572 +v 5043.37 4.319683 5057.062 +v 5045.03125 4.14287663 5057.47266 +v 5042.75635 2.41885138 5055.11572 +v 5079.895 -1.10483634 5067.397 +v 5076.17871 -0.521093249 5066.159 +v 5077.734 -0.7664895 5067.25439 +v 5079.895 1.89516366 5067.397 +v 5076.17871 2.47890663 5066.159 +v 5077.734 2.23351049 5067.25439 +v 5027.43262 -1.03272188 5057.022 +v 5027.70166 -2.096419 5060.51074 +v 5028.95361 -1.03272188 5057.58057 +v 5027.43262 1.96727812 5057.022 +v 5027.70166 0.9035809 5060.51074 +v 5028.95361 1.96727812 5057.58057 +v 5075.59229 -0.137112767 5094.07 +v 5077.655 1.85325229 5098.797 +v 5077.478 0.9274292 5095.37549 +v 5075.59229 2.86288714 5094.07 +v 5077.655 4.85325241 5098.797 +v 5077.478 3.9274292 5095.37549 +v 5086.14746 0.669869661 5079.745 +v 5087.09033 -0.114448242 5079.22754 +v 5082.1 1.52715349 5079.721 +v 5086.14746 3.66986966 5079.745 +v 5087.09033 2.88555169 5079.22754 +v 5082.1 4.52715349 5079.721 +v 5064.568 -0.1814619 5006.51855 +v 5064.481 -0.1814619 5005.633 +v 5062.94 -0.443683147 5007.66846 +v 5064.568 2.81853819 5006.51855 +v 5064.481 2.81853819 5005.633 +v 5062.94 2.55631685 5007.66846 +v 5070.00146 3.554526 5014.98828 +v 5074.778 0.18877393 5018.29736 +v 5073.228 1.18287313 5013.448 +v 5070.00146 6.55452633 5014.98828 +v 5074.778 3.18877387 5018.29736 +v 5073.228 4.18287325 5013.448 +v 5033.448 1.2033931 5079.58838 +v 5032.487 3.04938459 5077.64551 +v 5032.123 3.04938459 5077.667 +v 5033.448 4.203393 5079.58838 +v 5032.487 6.04938459 5077.64551 +v 5032.123 6.04938459 5077.667 +v 5034.62549 1.80014586 5053.30225 +v 5034.233 2.79833841 5051.644 +v 5033.71045 1.80014586 5054.40674 +v 5034.62549 4.800146 5053.30225 +v 5034.233 5.79833841 5051.644 +v 5033.71045 4.800146 5054.40674 +v 5082.72656 -4.047416 5012.93457 +v 5082.686 -4.047416 5012.163 +v 5082.49072 -3.83395171 5013.39258 +v 5082.72656 -1.04741621 5012.93457 +v 5082.686 -1.04741621 5012.163 +v 5082.49072 -0.8339517 5013.39258 +v 5094.47656 -2.02951121 5045.41846 +v 5088.06738 -0.117111519 5046.66748 +v 5092.07275 -3.477429 5050.01025 +v 5094.47656 0.9704888 5045.41846 +v 5088.06738 2.88288856 5046.66748 +v 5092.07275 -0.4774289 5050.01025 +v 5061.31934 0.5659893 5097.684 +v 5062.54053 0.2503167 5095.99 +v 5059.555 1.00335383 5095.18164 +v 5061.31934 3.56598926 5097.684 +v 5062.54053 3.25031662 5095.99 +v 5059.555 4.003354 5095.18164 +v 5066.71045 -2.24646616 5027.023 +v 5066.585 -1.08691967 5029.829 +v 5069.96826 -3.04210639 5027.868 +v 5066.71045 0.75353384 5027.023 +v 5066.585 1.91308033 5029.829 +v 5069.96826 -0.04210639 5027.868 +v 5027.434 -0.3090888 5081.00049 +v 5024.72754 -0.5560978 5079.247 +v 5024.972 -0.0110748336 5081.43066 +v 5027.434 2.69091129 5081.00049 +v 5024.72754 2.44390225 5079.247 +v 5024.972 2.98892522 5081.43066 +v 5039.673 -2.00969386 5002.206 +v 5040.70166 -2.27608442 5000.578 +v 5041.24463 -3.618783 4999.552 +v 5039.673 0.990306139 5002.206 +v 5040.70166 0.7239156 5000.578 +v 5041.24463 -0.618783 4999.552 +v 5045.89648 3.326357 5061.94775 +v 5049.449 0.159571558 5061.818 +v 5051.299 -0.6965099 5058.60156 +v 5045.89648 6.326357 5061.94775 +v 5049.449 3.15957165 5061.818 +v 5051.299 2.30349016 5058.60156 +v 5077.183 1.94394886 5048.27832 +v 5076.885 0.4058979 5048.098 +v 5075.47852 0.4058979 5048.346 +v 5077.183 4.94394875 5048.27832 +v 5076.885 3.40589786 5048.098 +v 5075.47852 3.40589786 5048.346 +v 5088.019 -0.126692086 5027.35156 +v 5087 1.74238729 5026.012 +v 5087.373 -0.126692086 5027.49951 +v 5088.019 2.873308 5027.35156 +v 5087 4.74238729 5026.012 +v 5087.373 2.873308 5027.49951 +v 5050.05957 0.159571558 5062.399 +v 5042.823 2.573177 5059.9126 +v 5049.487 0.159571558 5062.8877 +v 5050.05957 3.15957165 5062.399 +v 5042.823 5.57317734 5059.9126 +v 5049.487 3.15957165 5062.8877 +v 5023.12939 -1.04314339 5047.76953 +v 5019.976 -1.95381081 5045.138 +v 5017.418 -4.00151157 5049.247 +v 5023.12939 1.95685661 5047.76953 +v 5019.976 1.04618919 5045.138 +v 5017.418 -1.00151157 5049.247 +v 5079.38867 0.399334371 5055.6377 +v 5078.63525 0.231199324 5055.705 +v 5081.033 0.4452149 5056.07129 +v 5079.38867 3.39933443 5055.6377 +v 5078.63525 3.23119926 5055.705 +v 5081.033 3.445215 5056.07129 +v 5043.314 -4.05478 5075.02539 +v 5041.356 -3.579037 5075.05029 +v 5043.212 -4.05478 5075.87 +v 5043.314 -1.05478 5075.02539 +v 5041.356 -0.579036951 5075.05029 +v 5043.212 -1.05478 5075.87 +v 5043.87549 -4.00631237 5026.29541 +v 5041.95752 -2.75281835 5028.235 +v 5044.0957 -1.48652768 5030.41162 +v 5043.87549 -1.00631237 5026.29541 +v 5041.95752 0.247181654 5028.235 +v 5044.0957 1.51347232 5030.41162 +v 5034.946 -2.91639733 5086.22168 +v 5033.58252 -3.29382324 5087.355 +v 5034.54736 -3.29382324 5087.987 +v 5034.946 0.08360267 5086.22168 +v 5033.58252 -0.293823242 5087.355 +v 5034.54736 -0.293823242 5087.987 +v 5081.63574 3.51828027 5052.64844 +v 5081.33252 2.2721386 5053.18848 +v 5084.23633 2.936882 5051.9624 +v 5081.63574 6.51828 5052.64844 +v 5081.33252 5.2721386 5053.18848 +v 5084.23633 5.936882 5051.9624 +v 5073.869 -1.1327877 5028.433 +v 5074.00537 -0.574863553 5030.79248 +v 5073.924 -1.1327877 5027.992 +v 5073.869 1.8672123 5028.433 +v 5074.00537 2.42513657 5030.79248 +v 5073.924 1.8672123 5027.992 +v 5052.847 0.268805772 5069.295 +v 5053.066 2.0230093 5072.524 +v 5054.304 -0.9846881 5068.85938 +v 5052.847 3.26880574 5069.295 +v 5053.066 5.0230093 5072.524 +v 5054.304 2.015312 5068.85938 +v 5085.903 1.14572442 5071.377 +v 5086.53955 1.14572442 5071.08936 +v 5085.64648 0.3139858 5069.964 +v 5085.903 4.1457243 5071.377 +v 5086.53955 4.1457243 5071.08936 +v 5085.64648 3.31398582 5069.964 +v 5004.832 -2.79655623 5010.06348 +v 5002.728 -1.6363548 5010.255 +v 5006.65967 -3.83644342 5013.115 +v 5004.832 0.203443766 5010.06348 +v 5002.728 1.3636452 5010.255 +v 5006.65967 -0.8364434 5013.115 +v 5003.639 -1.5007019 5008.185 +v 5005.62939 -4.05005026 5011.354 +v 5009.30371 -1.09620917 5008.21631 +v 5003.639 1.4992981 5008.185 +v 5005.62939 -1.05005026 5011.354 +v 5009.30371 1.90379083 5008.21631 +v 5023.68066 -0.3452597 5070.689 +v 5024.76465 -0.3452597 5069.889 +v 5021.40967 -0.719294965 5069.14648 +v 5023.68066 2.65474033 5070.689 +v 5024.76465 2.65474033 5069.889 +v 5021.40967 2.280705 5069.14648 +v 5015.511 0.7329772 5067.56348 +v 5015.25 0.7329772 5068.911 +v 5020.43164 0.955443263 5067.149 +v 5015.511 3.73297715 5067.56348 +v 5015.25 3.73297715 5068.911 +v 5020.43164 3.95544338 5067.149 +v 5001.436 1.796041 5076.08545 +v 5001.12744 1.70172894 5073.84961 +v 4999.106 -0.1 5075.011 +v 5001.436 4.796041 5076.08545 +v 5001.12744 4.701729 5073.84961 +v 4999.106 2.9 5075.011 +v 5066.82 -2.59860134 5001.79 +v 5065.211 -2.94714117 5000.97949 +v 5066.36328 -2.59860134 5002.433 +v 5066.82 0.401398659 5001.79 +v 5065.211 0.05285883 5000.97949 +v 5066.36328 0.401398659 5002.433 +f 2 1 51 +f 52 2 51 +f 3 2 52 +f 53 3 52 +f 4 3 53 +f 54 4 53 +f 5 4 54 +f 55 5 54 +f 6 5 55 +f 56 6 55 +f 7 6 56 +f 57 7 56 +f 8 7 57 +f 58 8 57 +f 9 8 58 +f 59 9 58 +f 10 9 59 +f 60 10 59 +f 11 10 60 +f 61 11 60 +f 12 11 61 +f 62 12 61 +f 13 12 62 +f 63 13 62 +f 14 13 63 +f 64 14 63 +f 15 14 64 +f 65 15 64 +f 16 15 65 +f 66 16 65 +f 17 16 66 +f 67 17 66 +f 18 17 67 +f 68 18 67 +f 19 18 68 +f 69 19 68 +f 20 19 69 +f 70 20 69 +f 21 20 70 +f 71 21 70 +f 22 21 71 +f 72 22 71 +f 23 22 72 +f 73 23 72 +f 24 23 73 +f 74 24 73 +f 25 24 74 +f 75 25 74 +f 26 25 75 +f 76 26 75 +f 27 26 76 +f 77 27 76 +f 28 27 77 +f 78 28 77 +f 29 28 78 +f 79 29 78 +f 30 29 79 +f 80 30 79 +f 31 30 80 +f 81 31 80 +f 32 31 81 +f 82 32 81 +f 33 32 82 +f 83 33 82 +f 34 33 83 +f 84 34 83 +f 35 34 84 +f 85 35 84 +f 36 35 85 +f 86 36 85 +f 37 36 86 +f 87 37 86 +f 38 37 87 +f 88 38 87 +f 39 38 88 +f 89 39 88 +f 40 39 89 +f 90 40 89 +f 41 40 90 +f 91 41 90 +f 42 41 91 +f 92 42 91 +f 43 42 92 +f 93 43 92 +f 44 43 93 +f 94 44 93 +f 45 44 94 +f 95 45 94 +f 46 45 95 +f 96 46 95 +f 47 46 96 +f 97 47 96 +f 48 47 97 +f 98 48 97 +f 49 48 98 +f 99 49 98 +f 50 49 99 +f 100 50 99 +f 52 51 101 +f 102 52 101 +f 53 52 102 +f 103 53 102 +f 54 53 103 +f 104 54 103 +f 55 54 104 +f 105 55 104 +f 56 55 105 +f 106 56 105 +f 57 56 106 +f 107 57 106 +f 58 57 107 +f 108 58 107 +f 59 58 108 +f 109 59 108 +f 60 59 109 +f 110 60 109 +f 61 60 110 +f 111 61 110 +f 62 61 111 +f 112 62 111 +f 63 62 112 +f 113 63 112 +f 64 63 113 +f 114 64 113 +f 65 64 114 +f 115 65 114 +f 66 65 115 +f 116 66 115 +f 67 66 116 +f 117 67 116 +f 68 67 117 +f 118 68 117 +f 69 68 118 +f 119 69 118 +f 70 69 119 +f 120 70 119 +f 71 70 120 +f 121 71 120 +f 72 71 121 +f 122 72 121 +f 73 72 122 +f 123 73 122 +f 74 73 123 +f 124 74 123 +f 75 74 124 +f 125 75 124 +f 76 75 125 +f 126 76 125 +f 77 76 126 +f 127 77 126 +f 78 77 127 +f 128 78 127 +f 79 78 128 +f 129 79 128 +f 80 79 129 +f 130 80 129 +f 81 80 130 +f 131 81 130 +f 82 81 131 +f 132 82 131 +f 83 82 132 +f 133 83 132 +f 84 83 133 +f 134 84 133 +f 85 84 134 +f 135 85 134 +f 86 85 135 +f 136 86 135 +f 87 86 136 +f 137 87 136 +f 88 87 137 +f 138 88 137 +f 89 88 138 +f 139 89 138 +f 90 89 139 +f 140 90 139 +f 91 90 140 +f 141 91 140 +f 92 91 141 +f 142 92 141 +f 93 92 142 +f 143 93 142 +f 94 93 143 +f 144 94 143 +f 95 94 144 +f 145 95 144 +f 96 95 145 +f 146 96 145 +f 97 96 146 +f 147 97 146 +f 98 97 147 +f 148 98 147 +f 99 98 148 +f 149 99 148 +f 100 99 149 +f 150 100 149 +f 102 101 151 +f 152 102 151 +f 103 102 152 +f 153 103 152 +f 104 103 153 +f 154 104 153 +f 105 104 154 +f 155 105 154 +f 106 105 155 +f 156 106 155 +f 107 106 156 +f 157 107 156 +f 108 107 157 +f 158 108 157 +f 109 108 158 +f 159 109 158 +f 110 109 159 +f 160 110 159 +f 111 110 160 +f 161 111 160 +f 112 111 161 +f 162 112 161 +f 113 112 162 +f 163 113 162 +f 114 113 163 +f 164 114 163 +f 115 114 164 +f 165 115 164 +f 116 115 165 +f 166 116 165 +f 117 116 166 +f 167 117 166 +f 118 117 167 +f 168 118 167 +f 119 118 168 +f 169 119 168 +f 120 119 169 +f 170 120 169 +f 121 120 170 +f 171 121 170 +f 122 121 171 +f 172 122 171 +f 123 122 172 +f 173 123 172 +f 124 123 173 +f 174 124 173 +f 125 124 174 +f 175 125 174 +f 126 125 175 +f 176 126 175 +f 127 126 176 +f 177 127 176 +f 128 127 177 +f 178 128 177 +f 129 128 178 +f 179 129 178 +f 130 129 179 +f 180 130 179 +f 131 130 180 +f 181 131 180 +f 132 131 181 +f 182 132 181 +f 133 132 182 +f 183 133 182 +f 134 133 183 +f 184 134 183 +f 135 134 184 +f 185 135 184 +f 136 135 185 +f 186 136 185 +f 137 136 186 +f 187 137 186 +f 138 137 187 +f 188 138 187 +f 139 138 188 +f 189 139 188 +f 140 139 189 +f 190 140 189 +f 141 140 190 +f 191 141 190 +f 142 141 191 +f 192 142 191 +f 143 142 192 +f 193 143 192 +f 144 143 193 +f 194 144 193 +f 145 144 194 +f 195 145 194 +f 146 145 195 +f 196 146 195 +f 147 146 196 +f 197 147 196 +f 148 147 197 +f 198 148 197 +f 149 148 198 +f 199 149 198 +f 150 149 199 +f 200 150 199 +f 152 151 201 +f 202 152 201 +f 153 152 202 +f 203 153 202 +f 154 153 203 +f 204 154 203 +f 155 154 204 +f 205 155 204 +f 156 155 205 +f 206 156 205 +f 157 156 206 +f 207 157 206 +f 158 157 207 +f 208 158 207 +f 159 158 208 +f 209 159 208 +f 160 159 209 +f 210 160 209 +f 161 160 210 +f 211 161 210 +f 162 161 211 +f 212 162 211 +f 163 162 212 +f 213 163 212 +f 164 163 213 +f 214 164 213 +f 165 164 214 +f 215 165 214 +f 166 165 215 +f 216 166 215 +f 167 166 216 +f 217 167 216 +f 168 167 217 +f 218 168 217 +f 169 168 218 +f 219 169 218 +f 170 169 219 +f 220 170 219 +f 171 170 220 +f 221 171 220 +f 172 171 221 +f 222 172 221 +f 173 172 222 +f 223 173 222 +f 174 173 223 +f 224 174 223 +f 175 174 224 +f 225 175 224 +f 176 175 225 +f 226 176 225 +f 177 176 226 +f 227 177 226 +f 178 177 227 +f 228 178 227 +f 179 178 228 +f 229 179 228 +f 180 179 229 +f 230 180 229 +f 181 180 230 +f 231 181 230 +f 182 181 231 +f 232 182 231 +f 183 182 232 +f 233 183 232 +f 184 183 233 +f 234 184 233 +f 185 184 234 +f 235 185 234 +f 186 185 235 +f 236 186 235 +f 187 186 236 +f 237 187 236 +f 188 187 237 +f 238 188 237 +f 189 188 238 +f 239 189 238 +f 190 189 239 +f 240 190 239 +f 191 190 240 +f 241 191 240 +f 192 191 241 +f 242 192 241 +f 193 192 242 +f 243 193 242 +f 194 193 243 +f 244 194 243 +f 195 194 244 +f 245 195 244 +f 196 195 245 +f 246 196 245 +f 197 196 246 +f 247 197 246 +f 198 197 247 +f 248 198 247 +f 199 198 248 +f 249 199 248 +f 200 199 249 +f 250 200 249 +f 202 201 251 +f 252 202 251 +f 203 202 252 +f 253 203 252 +f 204 203 253 +f 254 204 253 +f 205 204 254 +f 255 205 254 +f 206 205 255 +f 256 206 255 +f 207 206 256 +f 257 207 256 +f 208 207 257 +f 258 208 257 +f 209 208 258 +f 259 209 258 +f 210 209 259 +f 260 210 259 +f 211 210 260 +f 261 211 260 +f 212 211 261 +f 262 212 261 +f 213 212 262 +f 263 213 262 +f 214 213 263 +f 264 214 263 +f 215 214 264 +f 265 215 264 +f 216 215 265 +f 266 216 265 +f 217 216 266 +f 267 217 266 +f 218 217 267 +f 268 218 267 +f 219 218 268 +f 269 219 268 +f 220 219 269 +f 270 220 269 +f 221 220 270 +f 271 221 270 +f 222 221 271 +f 272 222 271 +f 223 222 272 +f 273 223 272 +f 224 223 273 +f 274 224 273 +f 225 224 274 +f 275 225 274 +f 226 225 275 +f 276 226 275 +f 227 226 276 +f 277 227 276 +f 228 227 277 +f 278 228 277 +f 229 228 278 +f 279 229 278 +f 230 229 279 +f 280 230 279 +f 231 230 280 +f 281 231 280 +f 232 231 281 +f 282 232 281 +f 233 232 282 +f 283 233 282 +f 234 233 283 +f 284 234 283 +f 235 234 284 +f 285 235 284 +f 236 235 285 +f 286 236 285 +f 237 236 286 +f 287 237 286 +f 238 237 287 +f 288 238 287 +f 239 238 288 +f 289 239 288 +f 240 239 289 +f 290 240 289 +f 241 240 290 +f 291 241 290 +f 242 241 291 +f 292 242 291 +f 243 242 292 +f 293 243 292 +f 244 243 293 +f 294 244 293 +f 245 244 294 +f 295 245 294 +f 246 245 295 +f 296 246 295 +f 247 246 296 +f 297 247 296 +f 248 247 297 +f 298 248 297 +f 249 248 298 +f 299 249 298 +f 250 249 299 +f 300 250 299 +f 252 251 301 +f 302 252 301 +f 253 252 302 +f 303 253 302 +f 254 253 303 +f 304 254 303 +f 255 254 304 +f 305 255 304 +f 256 255 305 +f 306 256 305 +f 257 256 306 +f 307 257 306 +f 258 257 307 +f 308 258 307 +f 259 258 308 +f 309 259 308 +f 260 259 309 +f 310 260 309 +f 261 260 310 +f 311 261 310 +f 262 261 311 +f 312 262 311 +f 263 262 312 +f 313 263 312 +f 264 263 313 +f 314 264 313 +f 265 264 314 +f 315 265 314 +f 266 265 315 +f 316 266 315 +f 267 266 316 +f 317 267 316 +f 268 267 317 +f 318 268 317 +f 269 268 318 +f 319 269 318 +f 270 269 319 +f 320 270 319 +f 271 270 320 +f 321 271 320 +f 272 271 321 +f 322 272 321 +f 273 272 322 +f 323 273 322 +f 274 273 323 +f 324 274 323 +f 275 274 324 +f 325 275 324 +f 276 275 325 +f 326 276 325 +f 277 276 326 +f 327 277 326 +f 278 277 327 +f 328 278 327 +f 279 278 328 +f 329 279 328 +f 280 279 329 +f 330 280 329 +f 281 280 330 +f 331 281 330 +f 282 281 331 +f 332 282 331 +f 283 282 332 +f 333 283 332 +f 284 283 333 +f 334 284 333 +f 285 284 334 +f 335 285 334 +f 286 285 335 +f 336 286 335 +f 287 286 336 +f 337 287 336 +f 288 287 337 +f 338 288 337 +f 289 288 338 +f 339 289 338 +f 290 289 339 +f 340 290 339 +f 291 290 340 +f 341 291 340 +f 292 291 341 +f 342 292 341 +f 293 292 342 +f 343 293 342 +f 294 293 343 +f 344 294 343 +f 295 294 344 +f 345 295 344 +f 296 295 345 +f 346 296 345 +f 297 296 346 +f 347 297 346 +f 298 297 347 +f 348 298 347 +f 299 298 348 +f 349 299 348 +f 300 299 349 +f 350 300 349 +f 302 301 351 +f 352 302 351 +f 303 302 352 +f 353 303 352 +f 304 303 353 +f 354 304 353 +f 305 304 354 +f 355 305 354 +f 306 305 355 +f 356 306 355 +f 307 306 356 +f 357 307 356 +f 308 307 357 +f 358 308 357 +f 309 308 358 +f 359 309 358 +f 310 309 359 +f 360 310 359 +f 311 310 360 +f 361 311 360 +f 312 311 361 +f 362 312 361 +f 313 312 362 +f 363 313 362 +f 314 313 363 +f 364 314 363 +f 315 314 364 +f 365 315 364 +f 316 315 365 +f 366 316 365 +f 317 316 366 +f 367 317 366 +f 318 317 367 +f 368 318 367 +f 319 318 368 +f 369 319 368 +f 320 319 369 +f 370 320 369 +f 321 320 370 +f 371 321 370 +f 322 321 371 +f 372 322 371 +f 323 322 372 +f 373 323 372 +f 324 323 373 +f 374 324 373 +f 325 324 374 +f 375 325 374 +f 326 325 375 +f 376 326 375 +f 327 326 376 +f 377 327 376 +f 328 327 377 +f 378 328 377 +f 329 328 378 +f 379 329 378 +f 330 329 379 +f 380 330 379 +f 331 330 380 +f 381 331 380 +f 332 331 381 +f 382 332 381 +f 333 332 382 +f 383 333 382 +f 334 333 383 +f 384 334 383 +f 335 334 384 +f 385 335 384 +f 336 335 385 +f 386 336 385 +f 337 336 386 +f 387 337 386 +f 338 337 387 +f 388 338 387 +f 339 338 388 +f 389 339 388 +f 340 339 389 +f 390 340 389 +f 341 340 390 +f 391 341 390 +f 342 341 391 +f 392 342 391 +f 343 342 392 +f 393 343 392 +f 344 343 393 +f 394 344 393 +f 345 344 394 +f 395 345 394 +f 346 345 395 +f 396 346 395 +f 347 346 396 +f 397 347 396 +f 348 347 397 +f 398 348 397 +f 349 348 398 +f 399 349 398 +f 350 349 399 +f 400 350 399 +f 352 351 401 +f 402 352 401 +f 353 352 402 +f 403 353 402 +f 354 353 403 +f 404 354 403 +f 355 354 404 +f 405 355 404 +f 356 355 405 +f 406 356 405 +f 357 356 406 +f 407 357 406 +f 358 357 407 +f 408 358 407 +f 359 358 408 +f 409 359 408 +f 360 359 409 +f 410 360 409 +f 361 360 410 +f 411 361 410 +f 362 361 411 +f 412 362 411 +f 363 362 412 +f 413 363 412 +f 364 363 413 +f 414 364 413 +f 365 364 414 +f 415 365 414 +f 366 365 415 +f 416 366 415 +f 367 366 416 +f 417 367 416 +f 368 367 417 +f 418 368 417 +f 369 368 418 +f 419 369 418 +f 370 369 419 +f 420 370 419 +f 371 370 420 +f 421 371 420 +f 372 371 421 +f 422 372 421 +f 373 372 422 +f 423 373 422 +f 374 373 423 +f 424 374 423 +f 375 374 424 +f 425 375 424 +f 376 375 425 +f 426 376 425 +f 377 376 426 +f 427 377 426 +f 378 377 427 +f 428 378 427 +f 379 378 428 +f 429 379 428 +f 380 379 429 +f 430 380 429 +f 381 380 430 +f 431 381 430 +f 382 381 431 +f 432 382 431 +f 383 382 432 +f 433 383 432 +f 384 383 433 +f 434 384 433 +f 385 384 434 +f 435 385 434 +f 386 385 435 +f 436 386 435 +f 387 386 436 +f 437 387 436 +f 388 387 437 +f 438 388 437 +f 389 388 438 +f 439 389 438 +f 390 389 439 +f 440 390 439 +f 391 390 440 +f 441 391 440 +f 392 391 441 +f 442 392 441 +f 393 392 442 +f 443 393 442 +f 394 393 443 +f 444 394 443 +f 395 394 444 +f 445 395 444 +f 396 395 445 +f 446 396 445 +f 397 396 446 +f 447 397 446 +f 398 397 447 +f 448 398 447 +f 399 398 448 +f 449 399 448 +f 400 399 449 +f 450 400 449 +f 402 401 451 +f 452 402 451 +f 403 402 452 +f 453 403 452 +f 404 403 453 +f 454 404 453 +f 405 404 454 +f 455 405 454 +f 406 405 455 +f 456 406 455 +f 407 406 456 +f 457 407 456 +f 408 407 457 +f 458 408 457 +f 409 408 458 +f 459 409 458 +f 410 409 459 +f 460 410 459 +f 411 410 460 +f 461 411 460 +f 412 411 461 +f 462 412 461 +f 413 412 462 +f 463 413 462 +f 414 413 463 +f 464 414 463 +f 415 414 464 +f 465 415 464 +f 416 415 465 +f 466 416 465 +f 417 416 466 +f 467 417 466 +f 418 417 467 +f 468 418 467 +f 419 418 468 +f 469 419 468 +f 420 419 469 +f 470 420 469 +f 421 420 470 +f 471 421 470 +f 422 421 471 +f 472 422 471 +f 423 422 472 +f 473 423 472 +f 424 423 473 +f 474 424 473 +f 425 424 474 +f 475 425 474 +f 426 425 475 +f 476 426 475 +f 427 426 476 +f 477 427 476 +f 428 427 477 +f 478 428 477 +f 429 428 478 +f 479 429 478 +f 430 429 479 +f 480 430 479 +f 431 430 480 +f 481 431 480 +f 432 431 481 +f 482 432 481 +f 433 432 482 +f 483 433 482 +f 434 433 483 +f 484 434 483 +f 435 434 484 +f 485 435 484 +f 436 435 485 +f 486 436 485 +f 437 436 486 +f 487 437 486 +f 438 437 487 +f 488 438 487 +f 439 438 488 +f 489 439 488 +f 440 439 489 +f 490 440 489 +f 441 440 490 +f 491 441 490 +f 442 441 491 +f 492 442 491 +f 443 442 492 +f 493 443 492 +f 444 443 493 +f 494 444 493 +f 445 444 494 +f 495 445 494 +f 446 445 495 +f 496 446 495 +f 447 446 496 +f 497 447 496 +f 448 447 497 +f 498 448 497 +f 449 448 498 +f 499 449 498 +f 450 449 499 +f 500 450 499 +f 452 451 501 +f 502 452 501 +f 453 452 502 +f 503 453 502 +f 454 453 503 +f 504 454 503 +f 455 454 504 +f 505 455 504 +f 456 455 505 +f 506 456 505 +f 457 456 506 +f 507 457 506 +f 458 457 507 +f 508 458 507 +f 459 458 508 +f 509 459 508 +f 460 459 509 +f 510 460 509 +f 461 460 510 +f 511 461 510 +f 462 461 511 +f 512 462 511 +f 463 462 512 +f 513 463 512 +f 464 463 513 +f 514 464 513 +f 465 464 514 +f 515 465 514 +f 466 465 515 +f 516 466 515 +f 467 466 516 +f 517 467 516 +f 468 467 517 +f 518 468 517 +f 469 468 518 +f 519 469 518 +f 470 469 519 +f 520 470 519 +f 471 470 520 +f 521 471 520 +f 472 471 521 +f 522 472 521 +f 473 472 522 +f 523 473 522 +f 474 473 523 +f 524 474 523 +f 475 474 524 +f 525 475 524 +f 476 475 525 +f 526 476 525 +f 477 476 526 +f 527 477 526 +f 478 477 527 +f 528 478 527 +f 479 478 528 +f 529 479 528 +f 480 479 529 +f 530 480 529 +f 481 480 530 +f 531 481 530 +f 482 481 531 +f 532 482 531 +f 483 482 532 +f 533 483 532 +f 484 483 533 +f 534 484 533 +f 485 484 534 +f 535 485 534 +f 486 485 535 +f 536 486 535 +f 487 486 536 +f 537 487 536 +f 488 487 537 +f 538 488 537 +f 489 488 538 +f 539 489 538 +f 490 489 539 +f 540 490 539 +f 491 490 540 +f 541 491 540 +f 492 491 541 +f 542 492 541 +f 493 492 542 +f 543 493 542 +f 494 493 543 +f 544 494 543 +f 495 494 544 +f 545 495 544 +f 496 495 545 +f 546 496 545 +f 497 496 546 +f 547 497 546 +f 498 497 547 +f 548 498 547 +f 499 498 548 +f 549 499 548 +f 500 499 549 +f 550 500 549 +f 502 501 551 +f 552 502 551 +f 503 502 552 +f 553 503 552 +f 504 503 553 +f 554 504 553 +f 505 504 554 +f 555 505 554 +f 506 505 555 +f 556 506 555 +f 507 506 556 +f 557 507 556 +f 508 507 557 +f 558 508 557 +f 509 508 558 +f 559 509 558 +f 510 509 559 +f 560 510 559 +f 511 510 560 +f 561 511 560 +f 512 511 561 +f 562 512 561 +f 513 512 562 +f 563 513 562 +f 514 513 563 +f 564 514 563 +f 515 514 564 +f 565 515 564 +f 516 515 565 +f 566 516 565 +f 517 516 566 +f 567 517 566 +f 518 517 567 +f 568 518 567 +f 519 518 568 +f 569 519 568 +f 520 519 569 +f 570 520 569 +f 521 520 570 +f 571 521 570 +f 522 521 571 +f 572 522 571 +f 523 522 572 +f 573 523 572 +f 524 523 573 +f 574 524 573 +f 525 524 574 +f 575 525 574 +f 526 525 575 +f 576 526 575 +f 527 526 576 +f 577 527 576 +f 528 527 577 +f 578 528 577 +f 529 528 578 +f 579 529 578 +f 530 529 579 +f 580 530 579 +f 531 530 580 +f 581 531 580 +f 532 531 581 +f 582 532 581 +f 533 532 582 +f 583 533 582 +f 534 533 583 +f 584 534 583 +f 535 534 584 +f 585 535 584 +f 536 535 585 +f 586 536 585 +f 537 536 586 +f 587 537 586 +f 538 537 587 +f 588 538 587 +f 539 538 588 +f 589 539 588 +f 540 539 589 +f 590 540 589 +f 541 540 590 +f 591 541 590 +f 542 541 591 +f 592 542 591 +f 543 542 592 +f 593 543 592 +f 544 543 593 +f 594 544 593 +f 545 544 594 +f 595 545 594 +f 546 545 595 +f 596 546 595 +f 547 546 596 +f 597 547 596 +f 548 547 597 +f 598 548 597 +f 549 548 598 +f 599 549 598 +f 550 549 599 +f 600 550 599 +f 552 551 601 +f 602 552 601 +f 553 552 602 +f 603 553 602 +f 554 553 603 +f 604 554 603 +f 555 554 604 +f 605 555 604 +f 556 555 605 +f 606 556 605 +f 557 556 606 +f 607 557 606 +f 558 557 607 +f 608 558 607 +f 559 558 608 +f 609 559 608 +f 560 559 609 +f 610 560 609 +f 561 560 610 +f 611 561 610 +f 562 561 611 +f 612 562 611 +f 563 562 612 +f 613 563 612 +f 564 563 613 +f 614 564 613 +f 565 564 614 +f 615 565 614 +f 566 565 615 +f 616 566 615 +f 567 566 616 +f 617 567 616 +f 568 567 617 +f 618 568 617 +f 569 568 618 +f 619 569 618 +f 570 569 619 +f 620 570 619 +f 571 570 620 +f 621 571 620 +f 572 571 621 +f 622 572 621 +f 573 572 622 +f 623 573 622 +f 574 573 623 +f 624 574 623 +f 575 574 624 +f 625 575 624 +f 576 575 625 +f 626 576 625 +f 577 576 626 +f 627 577 626 +f 578 577 627 +f 628 578 627 +f 579 578 628 +f 629 579 628 +f 580 579 629 +f 630 580 629 +f 581 580 630 +f 631 581 630 +f 582 581 631 +f 632 582 631 +f 583 582 632 +f 633 583 632 +f 584 583 633 +f 634 584 633 +f 585 584 634 +f 635 585 634 +f 586 585 635 +f 636 586 635 +f 587 586 636 +f 637 587 636 +f 588 587 637 +f 638 588 637 +f 589 588 638 +f 639 589 638 +f 590 589 639 +f 640 590 639 +f 591 590 640 +f 641 591 640 +f 592 591 641 +f 642 592 641 +f 593 592 642 +f 643 593 642 +f 594 593 643 +f 644 594 643 +f 595 594 644 +f 645 595 644 +f 596 595 645 +f 646 596 645 +f 597 596 646 +f 647 597 646 +f 598 597 647 +f 648 598 647 +f 599 598 648 +f 649 599 648 +f 600 599 649 +f 650 600 649 +f 602 601 651 +f 652 602 651 +f 603 602 652 +f 653 603 652 +f 604 603 653 +f 654 604 653 +f 605 604 654 +f 655 605 654 +f 606 605 655 +f 656 606 655 +f 607 606 656 +f 657 607 656 +f 608 607 657 +f 658 608 657 +f 609 608 658 +f 659 609 658 +f 610 609 659 +f 660 610 659 +f 611 610 660 +f 661 611 660 +f 612 611 661 +f 662 612 661 +f 613 612 662 +f 663 613 662 +f 614 613 663 +f 664 614 663 +f 615 614 664 +f 665 615 664 +f 616 615 665 +f 666 616 665 +f 617 616 666 +f 667 617 666 +f 618 617 667 +f 668 618 667 +f 619 618 668 +f 669 619 668 +f 620 619 669 +f 670 620 669 +f 621 620 670 +f 671 621 670 +f 622 621 671 +f 672 622 671 +f 623 622 672 +f 673 623 672 +f 624 623 673 +f 674 624 673 +f 625 624 674 +f 675 625 674 +f 626 625 675 +f 676 626 675 +f 627 626 676 +f 677 627 676 +f 628 627 677 +f 678 628 677 +f 629 628 678 +f 679 629 678 +f 630 629 679 +f 680 630 679 +f 631 630 680 +f 681 631 680 +f 632 631 681 +f 682 632 681 +f 633 632 682 +f 683 633 682 +f 634 633 683 +f 684 634 683 +f 635 634 684 +f 685 635 684 +f 636 635 685 +f 686 636 685 +f 637 636 686 +f 687 637 686 +f 638 637 687 +f 688 638 687 +f 639 638 688 +f 689 639 688 +f 640 639 689 +f 690 640 689 +f 641 640 690 +f 691 641 690 +f 642 641 691 +f 692 642 691 +f 643 642 692 +f 693 643 692 +f 644 643 693 +f 694 644 693 +f 645 644 694 +f 695 645 694 +f 646 645 695 +f 696 646 695 +f 647 646 696 +f 697 647 696 +f 648 647 697 +f 698 648 697 +f 649 648 698 +f 699 649 698 +f 650 649 699 +f 700 650 699 +f 652 651 701 +f 702 652 701 +f 653 652 702 +f 703 653 702 +f 654 653 703 +f 704 654 703 +f 655 654 704 +f 705 655 704 +f 656 655 705 +f 706 656 705 +f 657 656 706 +f 707 657 706 +f 658 657 707 +f 708 658 707 +f 659 658 708 +f 709 659 708 +f 660 659 709 +f 710 660 709 +f 661 660 710 +f 711 661 710 +f 662 661 711 +f 712 662 711 +f 663 662 712 +f 713 663 712 +f 664 663 713 +f 714 664 713 +f 665 664 714 +f 715 665 714 +f 666 665 715 +f 716 666 715 +f 667 666 716 +f 717 667 716 +f 668 667 717 +f 718 668 717 +f 669 668 718 +f 719 669 718 +f 670 669 719 +f 720 670 719 +f 671 670 720 +f 721 671 720 +f 672 671 721 +f 722 672 721 +f 673 672 722 +f 723 673 722 +f 674 673 723 +f 724 674 723 +f 675 674 724 +f 725 675 724 +f 676 675 725 +f 726 676 725 +f 677 676 726 +f 727 677 726 +f 678 677 727 +f 728 678 727 +f 679 678 728 +f 729 679 728 +f 680 679 729 +f 730 680 729 +f 681 680 730 +f 731 681 730 +f 682 681 731 +f 732 682 731 +f 683 682 732 +f 733 683 732 +f 684 683 733 +f 734 684 733 +f 685 684 734 +f 735 685 734 +f 686 685 735 +f 736 686 735 +f 687 686 736 +f 737 687 736 +f 688 687 737 +f 738 688 737 +f 689 688 738 +f 739 689 738 +f 690 689 739 +f 740 690 739 +f 691 690 740 +f 741 691 740 +f 692 691 741 +f 742 692 741 +f 693 692 742 +f 743 693 742 +f 694 693 743 +f 744 694 743 +f 695 694 744 +f 745 695 744 +f 696 695 745 +f 746 696 745 +f 697 696 746 +f 747 697 746 +f 698 697 747 +f 748 698 747 +f 699 698 748 +f 749 699 748 +f 700 699 749 +f 750 700 749 +f 702 701 751 +f 752 702 751 +f 703 702 752 +f 753 703 752 +f 704 703 753 +f 754 704 753 +f 705 704 754 +f 755 705 754 +f 706 705 755 +f 756 706 755 +f 707 706 756 +f 757 707 756 +f 708 707 757 +f 758 708 757 +f 709 708 758 +f 759 709 758 +f 710 709 759 +f 760 710 759 +f 711 710 760 +f 761 711 760 +f 712 711 761 +f 762 712 761 +f 713 712 762 +f 763 713 762 +f 714 713 763 +f 764 714 763 +f 715 714 764 +f 765 715 764 +f 716 715 765 +f 766 716 765 +f 717 716 766 +f 767 717 766 +f 718 717 767 +f 768 718 767 +f 719 718 768 +f 769 719 768 +f 720 719 769 +f 770 720 769 +f 721 720 770 +f 771 721 770 +f 722 721 771 +f 772 722 771 +f 723 722 772 +f 773 723 772 +f 724 723 773 +f 774 724 773 +f 725 724 774 +f 775 725 774 +f 726 725 775 +f 776 726 775 +f 727 726 776 +f 777 727 776 +f 728 727 777 +f 778 728 777 +f 729 728 778 +f 779 729 778 +f 730 729 779 +f 780 730 779 +f 731 730 780 +f 781 731 780 +f 732 731 781 +f 782 732 781 +f 733 732 782 +f 783 733 782 +f 734 733 783 +f 784 734 783 +f 735 734 784 +f 785 735 784 +f 736 735 785 +f 786 736 785 +f 737 736 786 +f 787 737 786 +f 738 737 787 +f 788 738 787 +f 739 738 788 +f 789 739 788 +f 740 739 789 +f 790 740 789 +f 741 740 790 +f 791 741 790 +f 742 741 791 +f 792 742 791 +f 743 742 792 +f 793 743 792 +f 744 743 793 +f 794 744 793 +f 745 744 794 +f 795 745 794 +f 746 745 795 +f 796 746 795 +f 747 746 796 +f 797 747 796 +f 748 747 797 +f 798 748 797 +f 749 748 798 +f 799 749 798 +f 750 749 799 +f 800 750 799 +f 752 751 801 +f 802 752 801 +f 753 752 802 +f 803 753 802 +f 754 753 803 +f 804 754 803 +f 755 754 804 +f 805 755 804 +f 756 755 805 +f 806 756 805 +f 757 756 806 +f 807 757 806 +f 758 757 807 +f 808 758 807 +f 759 758 808 +f 809 759 808 +f 760 759 809 +f 810 760 809 +f 761 760 810 +f 811 761 810 +f 762 761 811 +f 812 762 811 +f 763 762 812 +f 813 763 812 +f 764 763 813 +f 814 764 813 +f 765 764 814 +f 815 765 814 +f 766 765 815 +f 816 766 815 +f 767 766 816 +f 817 767 816 +f 768 767 817 +f 818 768 817 +f 769 768 818 +f 819 769 818 +f 770 769 819 +f 820 770 819 +f 771 770 820 +f 821 771 820 +f 772 771 821 +f 822 772 821 +f 773 772 822 +f 823 773 822 +f 774 773 823 +f 824 774 823 +f 775 774 824 +f 825 775 824 +f 776 775 825 +f 826 776 825 +f 777 776 826 +f 827 777 826 +f 778 777 827 +f 828 778 827 +f 779 778 828 +f 829 779 828 +f 780 779 829 +f 830 780 829 +f 781 780 830 +f 831 781 830 +f 782 781 831 +f 832 782 831 +f 783 782 832 +f 833 783 832 +f 784 783 833 +f 834 784 833 +f 785 784 834 +f 835 785 834 +f 786 785 835 +f 836 786 835 +f 787 786 836 +f 837 787 836 +f 788 787 837 +f 838 788 837 +f 789 788 838 +f 839 789 838 +f 790 789 839 +f 840 790 839 +f 791 790 840 +f 841 791 840 +f 792 791 841 +f 842 792 841 +f 793 792 842 +f 843 793 842 +f 794 793 843 +f 844 794 843 +f 795 794 844 +f 845 795 844 +f 796 795 845 +f 846 796 845 +f 797 796 846 +f 847 797 846 +f 798 797 847 +f 848 798 847 +f 799 798 848 +f 849 799 848 +f 800 799 849 +f 850 800 849 +f 802 801 851 +f 852 802 851 +f 803 802 852 +f 853 803 852 +f 804 803 853 +f 854 804 853 +f 805 804 854 +f 855 805 854 +f 806 805 855 +f 856 806 855 +f 807 806 856 +f 857 807 856 +f 808 807 857 +f 858 808 857 +f 809 808 858 +f 859 809 858 +f 810 809 859 +f 860 810 859 +f 811 810 860 +f 861 811 860 +f 812 811 861 +f 862 812 861 +f 813 812 862 +f 863 813 862 +f 814 813 863 +f 864 814 863 +f 815 814 864 +f 865 815 864 +f 816 815 865 +f 866 816 865 +f 817 816 866 +f 867 817 866 +f 818 817 867 +f 868 818 867 +f 819 818 868 +f 869 819 868 +f 820 819 869 +f 870 820 869 +f 821 820 870 +f 871 821 870 +f 822 821 871 +f 872 822 871 +f 823 822 872 +f 873 823 872 +f 824 823 873 +f 874 824 873 +f 825 824 874 +f 875 825 874 +f 826 825 875 +f 876 826 875 +f 827 826 876 +f 877 827 876 +f 828 827 877 +f 878 828 877 +f 829 828 878 +f 879 829 878 +f 830 829 879 +f 880 830 879 +f 831 830 880 +f 881 831 880 +f 832 831 881 +f 882 832 881 +f 833 832 882 +f 883 833 882 +f 834 833 883 +f 884 834 883 +f 835 834 884 +f 885 835 884 +f 836 835 885 +f 886 836 885 +f 837 836 886 +f 887 837 886 +f 838 837 887 +f 888 838 887 +f 839 838 888 +f 889 839 888 +f 840 839 889 +f 890 840 889 +f 841 840 890 +f 891 841 890 +f 842 841 891 +f 892 842 891 +f 843 842 892 +f 893 843 892 +f 844 843 893 +f 894 844 893 +f 845 844 894 +f 895 845 894 +f 846 845 895 +f 896 846 895 +f 847 846 896 +f 897 847 896 +f 848 847 897 +f 898 848 897 +f 849 848 898 +f 899 849 898 +f 850 849 899 +f 900 850 899 +f 852 851 901 +f 902 852 901 +f 853 852 902 +f 903 853 902 +f 854 853 903 +f 904 854 903 +f 855 854 904 +f 905 855 904 +f 856 855 905 +f 906 856 905 +f 857 856 906 +f 907 857 906 +f 858 857 907 +f 908 858 907 +f 859 858 908 +f 909 859 908 +f 860 859 909 +f 910 860 909 +f 861 860 910 +f 911 861 910 +f 862 861 911 +f 912 862 911 +f 863 862 912 +f 913 863 912 +f 864 863 913 +f 914 864 913 +f 865 864 914 +f 915 865 914 +f 866 865 915 +f 916 866 915 +f 867 866 916 +f 917 867 916 +f 868 867 917 +f 918 868 917 +f 869 868 918 +f 919 869 918 +f 870 869 919 +f 920 870 919 +f 871 870 920 +f 921 871 920 +f 872 871 921 +f 922 872 921 +f 873 872 922 +f 923 873 922 +f 874 873 923 +f 924 874 923 +f 875 874 924 +f 925 875 924 +f 876 875 925 +f 926 876 925 +f 877 876 926 +f 927 877 926 +f 878 877 927 +f 928 878 927 +f 879 878 928 +f 929 879 928 +f 880 879 929 +f 930 880 929 +f 881 880 930 +f 931 881 930 +f 882 881 931 +f 932 882 931 +f 883 882 932 +f 933 883 932 +f 884 883 933 +f 934 884 933 +f 885 884 934 +f 935 885 934 +f 886 885 935 +f 936 886 935 +f 887 886 936 +f 937 887 936 +f 888 887 937 +f 938 888 937 +f 889 888 938 +f 939 889 938 +f 890 889 939 +f 940 890 939 +f 891 890 940 +f 941 891 940 +f 892 891 941 +f 942 892 941 +f 893 892 942 +f 943 893 942 +f 894 893 943 +f 944 894 943 +f 895 894 944 +f 945 895 944 +f 896 895 945 +f 946 896 945 +f 897 896 946 +f 947 897 946 +f 898 897 947 +f 948 898 947 +f 899 898 948 +f 949 899 948 +f 900 899 949 +f 950 900 949 +f 902 901 951 +f 952 902 951 +f 903 902 952 +f 953 903 952 +f 904 903 953 +f 954 904 953 +f 905 904 954 +f 955 905 954 +f 906 905 955 +f 956 906 955 +f 907 906 956 +f 957 907 956 +f 908 907 957 +f 958 908 957 +f 909 908 958 +f 959 909 958 +f 910 909 959 +f 960 910 959 +f 911 910 960 +f 961 911 960 +f 912 911 961 +f 962 912 961 +f 913 912 962 +f 963 913 962 +f 914 913 963 +f 964 914 963 +f 915 914 964 +f 965 915 964 +f 916 915 965 +f 966 916 965 +f 917 916 966 +f 967 917 966 +f 918 917 967 +f 968 918 967 +f 919 918 968 +f 969 919 968 +f 920 919 969 +f 970 920 969 +f 921 920 970 +f 971 921 970 +f 922 921 971 +f 972 922 971 +f 923 922 972 +f 973 923 972 +f 924 923 973 +f 974 924 973 +f 925 924 974 +f 975 925 974 +f 926 925 975 +f 976 926 975 +f 927 926 976 +f 977 927 976 +f 928 927 977 +f 978 928 977 +f 929 928 978 +f 979 929 978 +f 930 929 979 +f 980 930 979 +f 931 930 980 +f 981 931 980 +f 932 931 981 +f 982 932 981 +f 933 932 982 +f 983 933 982 +f 934 933 983 +f 984 934 983 +f 935 934 984 +f 985 935 984 +f 936 935 985 +f 986 936 985 +f 937 936 986 +f 987 937 986 +f 938 937 987 +f 988 938 987 +f 939 938 988 +f 989 939 988 +f 940 939 989 +f 990 940 989 +f 941 940 990 +f 991 941 990 +f 942 941 991 +f 992 942 991 +f 943 942 992 +f 993 943 992 +f 944 943 993 +f 994 944 993 +f 945 944 994 +f 995 945 994 +f 946 945 995 +f 996 946 995 +f 947 946 996 +f 997 947 996 +f 948 947 997 +f 998 948 997 +f 949 948 998 +f 999 949 998 +f 950 949 999 +f 1000 950 999 +f 952 951 1001 +f 1002 952 1001 +f 953 952 1002 +f 1003 953 1002 +f 954 953 1003 +f 1004 954 1003 +f 955 954 1004 +f 1005 955 1004 +f 956 955 1005 +f 1006 956 1005 +f 957 956 1006 +f 1007 957 1006 +f 958 957 1007 +f 1008 958 1007 +f 959 958 1008 +f 1009 959 1008 +f 960 959 1009 +f 1010 960 1009 +f 961 960 1010 +f 1011 961 1010 +f 962 961 1011 +f 1012 962 1011 +f 963 962 1012 +f 1013 963 1012 +f 964 963 1013 +f 1014 964 1013 +f 965 964 1014 +f 1015 965 1014 +f 966 965 1015 +f 1016 966 1015 +f 967 966 1016 +f 1017 967 1016 +f 968 967 1017 +f 1018 968 1017 +f 969 968 1018 +f 1019 969 1018 +f 970 969 1019 +f 1020 970 1019 +f 971 970 1020 +f 1021 971 1020 +f 972 971 1021 +f 1022 972 1021 +f 973 972 1022 +f 1023 973 1022 +f 974 973 1023 +f 1024 974 1023 +f 975 974 1024 +f 1025 975 1024 +f 976 975 1025 +f 1026 976 1025 +f 977 976 1026 +f 1027 977 1026 +f 978 977 1027 +f 1028 978 1027 +f 979 978 1028 +f 1029 979 1028 +f 980 979 1029 +f 1030 980 1029 +f 981 980 1030 +f 1031 981 1030 +f 982 981 1031 +f 1032 982 1031 +f 983 982 1032 +f 1033 983 1032 +f 984 983 1033 +f 1034 984 1033 +f 985 984 1034 +f 1035 985 1034 +f 986 985 1035 +f 1036 986 1035 +f 987 986 1036 +f 1037 987 1036 +f 988 987 1037 +f 1038 988 1037 +f 989 988 1038 +f 1039 989 1038 +f 990 989 1039 +f 1040 990 1039 +f 991 990 1040 +f 1041 991 1040 +f 992 991 1041 +f 1042 992 1041 +f 993 992 1042 +f 1043 993 1042 +f 994 993 1043 +f 1044 994 1043 +f 995 994 1044 +f 1045 995 1044 +f 996 995 1045 +f 1046 996 1045 +f 997 996 1046 +f 1047 997 1046 +f 998 997 1047 +f 1048 998 1047 +f 999 998 1048 +f 1049 999 1048 +f 1000 999 1049 +f 1050 1000 1049 +f 1002 1001 1051 +f 1052 1002 1051 +f 1003 1002 1052 +f 1053 1003 1052 +f 1004 1003 1053 +f 1054 1004 1053 +f 1005 1004 1054 +f 1055 1005 1054 +f 1006 1005 1055 +f 1056 1006 1055 +f 1007 1006 1056 +f 1057 1007 1056 +f 1008 1007 1057 +f 1058 1008 1057 +f 1009 1008 1058 +f 1059 1009 1058 +f 1010 1009 1059 +f 1060 1010 1059 +f 1011 1010 1060 +f 1061 1011 1060 +f 1012 1011 1061 +f 1062 1012 1061 +f 1013 1012 1062 +f 1063 1013 1062 +f 1014 1013 1063 +f 1064 1014 1063 +f 1015 1014 1064 +f 1065 1015 1064 +f 1016 1015 1065 +f 1066 1016 1065 +f 1017 1016 1066 +f 1067 1017 1066 +f 1018 1017 1067 +f 1068 1018 1067 +f 1019 1018 1068 +f 1069 1019 1068 +f 1020 1019 1069 +f 1070 1020 1069 +f 1021 1020 1070 +f 1071 1021 1070 +f 1022 1021 1071 +f 1072 1022 1071 +f 1023 1022 1072 +f 1073 1023 1072 +f 1024 1023 1073 +f 1074 1024 1073 +f 1025 1024 1074 +f 1075 1025 1074 +f 1026 1025 1075 +f 1076 1026 1075 +f 1027 1026 1076 +f 1077 1027 1076 +f 1028 1027 1077 +f 1078 1028 1077 +f 1029 1028 1078 +f 1079 1029 1078 +f 1030 1029 1079 +f 1080 1030 1079 +f 1031 1030 1080 +f 1081 1031 1080 +f 1032 1031 1081 +f 1082 1032 1081 +f 1033 1032 1082 +f 1083 1033 1082 +f 1034 1033 1083 +f 1084 1034 1083 +f 1035 1034 1084 +f 1085 1035 1084 +f 1036 1035 1085 +f 1086 1036 1085 +f 1037 1036 1086 +f 1087 1037 1086 +f 1038 1037 1087 +f 1088 1038 1087 +f 1039 1038 1088 +f 1089 1039 1088 +f 1040 1039 1089 +f 1090 1040 1089 +f 1041 1040 1090 +f 1091 1041 1090 +f 1042 1041 1091 +f 1092 1042 1091 +f 1043 1042 1092 +f 1093 1043 1092 +f 1044 1043 1093 +f 1094 1044 1093 +f 1045 1044 1094 +f 1095 1045 1094 +f 1046 1045 1095 +f 1096 1046 1095 +f 1047 1046 1096 +f 1097 1047 1096 +f 1048 1047 1097 +f 1098 1048 1097 +f 1049 1048 1098 +f 1099 1049 1098 +f 1050 1049 1099 +f 1100 1050 1099 +f 1052 1051 1101 +f 1102 1052 1101 +f 1053 1052 1102 +f 1103 1053 1102 +f 1054 1053 1103 +f 1104 1054 1103 +f 1055 1054 1104 +f 1105 1055 1104 +f 1056 1055 1105 +f 1106 1056 1105 +f 1057 1056 1106 +f 1107 1057 1106 +f 1058 1057 1107 +f 1108 1058 1107 +f 1059 1058 1108 +f 1109 1059 1108 +f 1060 1059 1109 +f 1110 1060 1109 +f 1061 1060 1110 +f 1111 1061 1110 +f 1062 1061 1111 +f 1112 1062 1111 +f 1063 1062 1112 +f 1113 1063 1112 +f 1064 1063 1113 +f 1114 1064 1113 +f 1065 1064 1114 +f 1115 1065 1114 +f 1066 1065 1115 +f 1116 1066 1115 +f 1067 1066 1116 +f 1117 1067 1116 +f 1068 1067 1117 +f 1118 1068 1117 +f 1069 1068 1118 +f 1119 1069 1118 +f 1070 1069 1119 +f 1120 1070 1119 +f 1071 1070 1120 +f 1121 1071 1120 +f 1072 1071 1121 +f 1122 1072 1121 +f 1073 1072 1122 +f 1123 1073 1122 +f 1074 1073 1123 +f 1124 1074 1123 +f 1075 1074 1124 +f 1125 1075 1124 +f 1076 1075 1125 +f 1126 1076 1125 +f 1077 1076 1126 +f 1127 1077 1126 +f 1078 1077 1127 +f 1128 1078 1127 +f 1079 1078 1128 +f 1129 1079 1128 +f 1080 1079 1129 +f 1130 1080 1129 +f 1081 1080 1130 +f 1131 1081 1130 +f 1082 1081 1131 +f 1132 1082 1131 +f 1083 1082 1132 +f 1133 1083 1132 +f 1084 1083 1133 +f 1134 1084 1133 +f 1085 1084 1134 +f 1135 1085 1134 +f 1086 1085 1135 +f 1136 1086 1135 +f 1087 1086 1136 +f 1137 1087 1136 +f 1088 1087 1137 +f 1138 1088 1137 +f 1089 1088 1138 +f 1139 1089 1138 +f 1090 1089 1139 +f 1140 1090 1139 +f 1091 1090 1140 +f 1141 1091 1140 +f 1092 1091 1141 +f 1142 1092 1141 +f 1093 1092 1142 +f 1143 1093 1142 +f 1094 1093 1143 +f 1144 1094 1143 +f 1095 1094 1144 +f 1145 1095 1144 +f 1096 1095 1145 +f 1146 1096 1145 +f 1097 1096 1146 +f 1147 1097 1146 +f 1098 1097 1147 +f 1148 1098 1147 +f 1099 1098 1148 +f 1149 1099 1148 +f 1100 1099 1149 +f 1150 1100 1149 +f 1102 1101 1151 +f 1152 1102 1151 +f 1103 1102 1152 +f 1153 1103 1152 +f 1104 1103 1153 +f 1154 1104 1153 +f 1105 1104 1154 +f 1155 1105 1154 +f 1106 1105 1155 +f 1156 1106 1155 +f 1107 1106 1156 +f 1157 1107 1156 +f 1108 1107 1157 +f 1158 1108 1157 +f 1109 1108 1158 +f 1159 1109 1158 +f 1110 1109 1159 +f 1160 1110 1159 +f 1111 1110 1160 +f 1161 1111 1160 +f 1112 1111 1161 +f 1162 1112 1161 +f 1113 1112 1162 +f 1163 1113 1162 +f 1114 1113 1163 +f 1164 1114 1163 +f 1115 1114 1164 +f 1165 1115 1164 +f 1116 1115 1165 +f 1166 1116 1165 +f 1117 1116 1166 +f 1167 1117 1166 +f 1118 1117 1167 +f 1168 1118 1167 +f 1119 1118 1168 +f 1169 1119 1168 +f 1120 1119 1169 +f 1170 1120 1169 +f 1121 1120 1170 +f 1171 1121 1170 +f 1122 1121 1171 +f 1172 1122 1171 +f 1123 1122 1172 +f 1173 1123 1172 +f 1124 1123 1173 +f 1174 1124 1173 +f 1125 1124 1174 +f 1175 1125 1174 +f 1126 1125 1175 +f 1176 1126 1175 +f 1127 1126 1176 +f 1177 1127 1176 +f 1128 1127 1177 +f 1178 1128 1177 +f 1129 1128 1178 +f 1179 1129 1178 +f 1130 1129 1179 +f 1180 1130 1179 +f 1131 1130 1180 +f 1181 1131 1180 +f 1132 1131 1181 +f 1182 1132 1181 +f 1133 1132 1182 +f 1183 1133 1182 +f 1134 1133 1183 +f 1184 1134 1183 +f 1135 1134 1184 +f 1185 1135 1184 +f 1136 1135 1185 +f 1186 1136 1185 +f 1137 1136 1186 +f 1187 1137 1186 +f 1138 1137 1187 +f 1188 1138 1187 +f 1139 1138 1188 +f 1189 1139 1188 +f 1140 1139 1189 +f 1190 1140 1189 +f 1141 1140 1190 +f 1191 1141 1190 +f 1142 1141 1191 +f 1192 1142 1191 +f 1143 1142 1192 +f 1193 1143 1192 +f 1144 1143 1193 +f 1194 1144 1193 +f 1145 1144 1194 +f 1195 1145 1194 +f 1146 1145 1195 +f 1196 1146 1195 +f 1147 1146 1196 +f 1197 1147 1196 +f 1148 1147 1197 +f 1198 1148 1197 +f 1149 1148 1198 +f 1199 1149 1198 +f 1150 1149 1199 +f 1200 1150 1199 +f 1152 1151 1201 +f 1202 1152 1201 +f 1153 1152 1202 +f 1203 1153 1202 +f 1154 1153 1203 +f 1204 1154 1203 +f 1155 1154 1204 +f 1205 1155 1204 +f 1156 1155 1205 +f 1206 1156 1205 +f 1157 1156 1206 +f 1207 1157 1206 +f 1158 1157 1207 +f 1208 1158 1207 +f 1159 1158 1208 +f 1209 1159 1208 +f 1160 1159 1209 +f 1210 1160 1209 +f 1161 1160 1210 +f 1211 1161 1210 +f 1162 1161 1211 +f 1212 1162 1211 +f 1163 1162 1212 +f 1213 1163 1212 +f 1164 1163 1213 +f 1214 1164 1213 +f 1165 1164 1214 +f 1215 1165 1214 +f 1166 1165 1215 +f 1216 1166 1215 +f 1167 1166 1216 +f 1217 1167 1216 +f 1168 1167 1217 +f 1218 1168 1217 +f 1169 1168 1218 +f 1219 1169 1218 +f 1170 1169 1219 +f 1220 1170 1219 +f 1171 1170 1220 +f 1221 1171 1220 +f 1172 1171 1221 +f 1222 1172 1221 +f 1173 1172 1222 +f 1223 1173 1222 +f 1174 1173 1223 +f 1224 1174 1223 +f 1175 1174 1224 +f 1225 1175 1224 +f 1176 1175 1225 +f 1226 1176 1225 +f 1177 1176 1226 +f 1227 1177 1226 +f 1178 1177 1227 +f 1228 1178 1227 +f 1179 1178 1228 +f 1229 1179 1228 +f 1180 1179 1229 +f 1230 1180 1229 +f 1181 1180 1230 +f 1231 1181 1230 +f 1182 1181 1231 +f 1232 1182 1231 +f 1183 1182 1232 +f 1233 1183 1232 +f 1184 1183 1233 +f 1234 1184 1233 +f 1185 1184 1234 +f 1235 1185 1234 +f 1186 1185 1235 +f 1236 1186 1235 +f 1187 1186 1236 +f 1237 1187 1236 +f 1188 1187 1237 +f 1238 1188 1237 +f 1189 1188 1238 +f 1239 1189 1238 +f 1190 1189 1239 +f 1240 1190 1239 +f 1191 1190 1240 +f 1241 1191 1240 +f 1192 1191 1241 +f 1242 1192 1241 +f 1193 1192 1242 +f 1243 1193 1242 +f 1194 1193 1243 +f 1244 1194 1243 +f 1195 1194 1244 +f 1245 1195 1244 +f 1196 1195 1245 +f 1246 1196 1245 +f 1197 1196 1246 +f 1247 1197 1246 +f 1198 1197 1247 +f 1248 1198 1247 +f 1199 1198 1248 +f 1249 1199 1248 +f 1200 1199 1249 +f 1250 1200 1249 +f 1202 1201 1251 +f 1252 1202 1251 +f 1203 1202 1252 +f 1253 1203 1252 +f 1204 1203 1253 +f 1254 1204 1253 +f 1205 1204 1254 +f 1255 1205 1254 +f 1206 1205 1255 +f 1256 1206 1255 +f 1207 1206 1256 +f 1257 1207 1256 +f 1208 1207 1257 +f 1258 1208 1257 +f 1209 1208 1258 +f 1259 1209 1258 +f 1210 1209 1259 +f 1260 1210 1259 +f 1211 1210 1260 +f 1261 1211 1260 +f 1212 1211 1261 +f 1262 1212 1261 +f 1213 1212 1262 +f 1263 1213 1262 +f 1214 1213 1263 +f 1264 1214 1263 +f 1215 1214 1264 +f 1265 1215 1264 +f 1216 1215 1265 +f 1266 1216 1265 +f 1217 1216 1266 +f 1267 1217 1266 +f 1218 1217 1267 +f 1268 1218 1267 +f 1219 1218 1268 +f 1269 1219 1268 +f 1220 1219 1269 +f 1270 1220 1269 +f 1221 1220 1270 +f 1271 1221 1270 +f 1222 1221 1271 +f 1272 1222 1271 +f 1223 1222 1272 +f 1273 1223 1272 +f 1224 1223 1273 +f 1274 1224 1273 +f 1225 1224 1274 +f 1275 1225 1274 +f 1226 1225 1275 +f 1276 1226 1275 +f 1227 1226 1276 +f 1277 1227 1276 +f 1228 1227 1277 +f 1278 1228 1277 +f 1229 1228 1278 +f 1279 1229 1278 +f 1230 1229 1279 +f 1280 1230 1279 +f 1231 1230 1280 +f 1281 1231 1280 +f 1232 1231 1281 +f 1282 1232 1281 +f 1233 1232 1282 +f 1283 1233 1282 +f 1234 1233 1283 +f 1284 1234 1283 +f 1235 1234 1284 +f 1285 1235 1284 +f 1236 1235 1285 +f 1286 1236 1285 +f 1237 1236 1286 +f 1287 1237 1286 +f 1238 1237 1287 +f 1288 1238 1287 +f 1239 1238 1288 +f 1289 1239 1288 +f 1240 1239 1289 +f 1290 1240 1289 +f 1241 1240 1290 +f 1291 1241 1290 +f 1242 1241 1291 +f 1292 1242 1291 +f 1243 1242 1292 +f 1293 1243 1292 +f 1244 1243 1293 +f 1294 1244 1293 +f 1245 1244 1294 +f 1295 1245 1294 +f 1246 1245 1295 +f 1296 1246 1295 +f 1247 1246 1296 +f 1297 1247 1296 +f 1248 1247 1297 +f 1298 1248 1297 +f 1249 1248 1298 +f 1299 1249 1298 +f 1250 1249 1299 +f 1300 1250 1299 +f 1252 1251 1301 +f 1302 1252 1301 +f 1253 1252 1302 +f 1303 1253 1302 +f 1254 1253 1303 +f 1304 1254 1303 +f 1255 1254 1304 +f 1305 1255 1304 +f 1256 1255 1305 +f 1306 1256 1305 +f 1257 1256 1306 +f 1307 1257 1306 +f 1258 1257 1307 +f 1308 1258 1307 +f 1259 1258 1308 +f 1309 1259 1308 +f 1260 1259 1309 +f 1310 1260 1309 +f 1261 1260 1310 +f 1311 1261 1310 +f 1262 1261 1311 +f 1312 1262 1311 +f 1263 1262 1312 +f 1313 1263 1312 +f 1264 1263 1313 +f 1314 1264 1313 +f 1265 1264 1314 +f 1315 1265 1314 +f 1266 1265 1315 +f 1316 1266 1315 +f 1267 1266 1316 +f 1317 1267 1316 +f 1268 1267 1317 +f 1318 1268 1317 +f 1269 1268 1318 +f 1319 1269 1318 +f 1270 1269 1319 +f 1320 1270 1319 +f 1271 1270 1320 +f 1321 1271 1320 +f 1272 1271 1321 +f 1322 1272 1321 +f 1273 1272 1322 +f 1323 1273 1322 +f 1274 1273 1323 +f 1324 1274 1323 +f 1275 1274 1324 +f 1325 1275 1324 +f 1276 1275 1325 +f 1326 1276 1325 +f 1277 1276 1326 +f 1327 1277 1326 +f 1278 1277 1327 +f 1328 1278 1327 +f 1279 1278 1328 +f 1329 1279 1328 +f 1280 1279 1329 +f 1330 1280 1329 +f 1281 1280 1330 +f 1331 1281 1330 +f 1282 1281 1331 +f 1332 1282 1331 +f 1283 1282 1332 +f 1333 1283 1332 +f 1284 1283 1333 +f 1334 1284 1333 +f 1285 1284 1334 +f 1335 1285 1334 +f 1286 1285 1335 +f 1336 1286 1335 +f 1287 1286 1336 +f 1337 1287 1336 +f 1288 1287 1337 +f 1338 1288 1337 +f 1289 1288 1338 +f 1339 1289 1338 +f 1290 1289 1339 +f 1340 1290 1339 +f 1291 1290 1340 +f 1341 1291 1340 +f 1292 1291 1341 +f 1342 1292 1341 +f 1293 1292 1342 +f 1343 1293 1342 +f 1294 1293 1343 +f 1344 1294 1343 +f 1295 1294 1344 +f 1345 1295 1344 +f 1296 1295 1345 +f 1346 1296 1345 +f 1297 1296 1346 +f 1347 1297 1346 +f 1298 1297 1347 +f 1348 1298 1347 +f 1299 1298 1348 +f 1349 1299 1348 +f 1300 1299 1349 +f 1350 1300 1349 +f 1302 1301 1351 +f 1352 1302 1351 +f 1303 1302 1352 +f 1353 1303 1352 +f 1304 1303 1353 +f 1354 1304 1353 +f 1305 1304 1354 +f 1355 1305 1354 +f 1306 1305 1355 +f 1356 1306 1355 +f 1307 1306 1356 +f 1357 1307 1356 +f 1308 1307 1357 +f 1358 1308 1357 +f 1309 1308 1358 +f 1359 1309 1358 +f 1310 1309 1359 +f 1360 1310 1359 +f 1311 1310 1360 +f 1361 1311 1360 +f 1312 1311 1361 +f 1362 1312 1361 +f 1313 1312 1362 +f 1363 1313 1362 +f 1314 1313 1363 +f 1364 1314 1363 +f 1315 1314 1364 +f 1365 1315 1364 +f 1316 1315 1365 +f 1366 1316 1365 +f 1317 1316 1366 +f 1367 1317 1366 +f 1318 1317 1367 +f 1368 1318 1367 +f 1319 1318 1368 +f 1369 1319 1368 +f 1320 1319 1369 +f 1370 1320 1369 +f 1321 1320 1370 +f 1371 1321 1370 +f 1322 1321 1371 +f 1372 1322 1371 +f 1323 1322 1372 +f 1373 1323 1372 +f 1324 1323 1373 +f 1374 1324 1373 +f 1325 1324 1374 +f 1375 1325 1374 +f 1326 1325 1375 +f 1376 1326 1375 +f 1327 1326 1376 +f 1377 1327 1376 +f 1328 1327 1377 +f 1378 1328 1377 +f 1329 1328 1378 +f 1379 1329 1378 +f 1330 1329 1379 +f 1380 1330 1379 +f 1331 1330 1380 +f 1381 1331 1380 +f 1332 1331 1381 +f 1382 1332 1381 +f 1333 1332 1382 +f 1383 1333 1382 +f 1334 1333 1383 +f 1384 1334 1383 +f 1335 1334 1384 +f 1385 1335 1384 +f 1336 1335 1385 +f 1386 1336 1385 +f 1337 1336 1386 +f 1387 1337 1386 +f 1338 1337 1387 +f 1388 1338 1387 +f 1339 1338 1388 +f 1389 1339 1388 +f 1340 1339 1389 +f 1390 1340 1389 +f 1341 1340 1390 +f 1391 1341 1390 +f 1342 1341 1391 +f 1392 1342 1391 +f 1343 1342 1392 +f 1393 1343 1392 +f 1344 1343 1393 +f 1394 1344 1393 +f 1345 1344 1394 +f 1395 1345 1394 +f 1346 1345 1395 +f 1396 1346 1395 +f 1347 1346 1396 +f 1397 1347 1396 +f 1348 1347 1397 +f 1398 1348 1397 +f 1349 1348 1398 +f 1399 1349 1398 +f 1350 1349 1399 +f 1400 1350 1399 +f 1352 1351 1401 +f 1402 1352 1401 +f 1353 1352 1402 +f 1403 1353 1402 +f 1354 1353 1403 +f 1404 1354 1403 +f 1355 1354 1404 +f 1405 1355 1404 +f 1356 1355 1405 +f 1406 1356 1405 +f 1357 1356 1406 +f 1407 1357 1406 +f 1358 1357 1407 +f 1408 1358 1407 +f 1359 1358 1408 +f 1409 1359 1408 +f 1360 1359 1409 +f 1410 1360 1409 +f 1361 1360 1410 +f 1411 1361 1410 +f 1362 1361 1411 +f 1412 1362 1411 +f 1363 1362 1412 +f 1413 1363 1412 +f 1364 1363 1413 +f 1414 1364 1413 +f 1365 1364 1414 +f 1415 1365 1414 +f 1366 1365 1415 +f 1416 1366 1415 +f 1367 1366 1416 +f 1417 1367 1416 +f 1368 1367 1417 +f 1418 1368 1417 +f 1369 1368 1418 +f 1419 1369 1418 +f 1370 1369 1419 +f 1420 1370 1419 +f 1371 1370 1420 +f 1421 1371 1420 +f 1372 1371 1421 +f 1422 1372 1421 +f 1373 1372 1422 +f 1423 1373 1422 +f 1374 1373 1423 +f 1424 1374 1423 +f 1375 1374 1424 +f 1425 1375 1424 +f 1376 1375 1425 +f 1426 1376 1425 +f 1377 1376 1426 +f 1427 1377 1426 +f 1378 1377 1427 +f 1428 1378 1427 +f 1379 1378 1428 +f 1429 1379 1428 +f 1380 1379 1429 +f 1430 1380 1429 +f 1381 1380 1430 +f 1431 1381 1430 +f 1382 1381 1431 +f 1432 1382 1431 +f 1383 1382 1432 +f 1433 1383 1432 +f 1384 1383 1433 +f 1434 1384 1433 +f 1385 1384 1434 +f 1435 1385 1434 +f 1386 1385 1435 +f 1436 1386 1435 +f 1387 1386 1436 +f 1437 1387 1436 +f 1388 1387 1437 +f 1438 1388 1437 +f 1389 1388 1438 +f 1439 1389 1438 +f 1390 1389 1439 +f 1440 1390 1439 +f 1391 1390 1440 +f 1441 1391 1440 +f 1392 1391 1441 +f 1442 1392 1441 +f 1393 1392 1442 +f 1443 1393 1442 +f 1394 1393 1443 +f 1444 1394 1443 +f 1395 1394 1444 +f 1445 1395 1444 +f 1396 1395 1445 +f 1446 1396 1445 +f 1397 1396 1446 +f 1447 1397 1446 +f 1398 1397 1447 +f 1448 1398 1447 +f 1399 1398 1448 +f 1449 1399 1448 +f 1400 1399 1449 +f 1450 1400 1449 +f 1402 1401 1451 +f 1452 1402 1451 +f 1403 1402 1452 +f 1453 1403 1452 +f 1404 1403 1453 +f 1454 1404 1453 +f 1405 1404 1454 +f 1455 1405 1454 +f 1406 1405 1455 +f 1456 1406 1455 +f 1407 1406 1456 +f 1457 1407 1456 +f 1408 1407 1457 +f 1458 1408 1457 +f 1409 1408 1458 +f 1459 1409 1458 +f 1410 1409 1459 +f 1460 1410 1459 +f 1411 1410 1460 +f 1461 1411 1460 +f 1412 1411 1461 +f 1462 1412 1461 +f 1413 1412 1462 +f 1463 1413 1462 +f 1414 1413 1463 +f 1464 1414 1463 +f 1415 1414 1464 +f 1465 1415 1464 +f 1416 1415 1465 +f 1466 1416 1465 +f 1417 1416 1466 +f 1467 1417 1466 +f 1418 1417 1467 +f 1468 1418 1467 +f 1419 1418 1468 +f 1469 1419 1468 +f 1420 1419 1469 +f 1470 1420 1469 +f 1421 1420 1470 +f 1471 1421 1470 +f 1422 1421 1471 +f 1472 1422 1471 +f 1423 1422 1472 +f 1473 1423 1472 +f 1424 1423 1473 +f 1474 1424 1473 +f 1425 1424 1474 +f 1475 1425 1474 +f 1426 1425 1475 +f 1476 1426 1475 +f 1427 1426 1476 +f 1477 1427 1476 +f 1428 1427 1477 +f 1478 1428 1477 +f 1429 1428 1478 +f 1479 1429 1478 +f 1430 1429 1479 +f 1480 1430 1479 +f 1431 1430 1480 +f 1481 1431 1480 +f 1432 1431 1481 +f 1482 1432 1481 +f 1433 1432 1482 +f 1483 1433 1482 +f 1434 1433 1483 +f 1484 1434 1483 +f 1435 1434 1484 +f 1485 1435 1484 +f 1436 1435 1485 +f 1486 1436 1485 +f 1437 1436 1486 +f 1487 1437 1486 +f 1438 1437 1487 +f 1488 1438 1487 +f 1439 1438 1488 +f 1489 1439 1488 +f 1440 1439 1489 +f 1490 1440 1489 +f 1441 1440 1490 +f 1491 1441 1490 +f 1442 1441 1491 +f 1492 1442 1491 +f 1443 1442 1492 +f 1493 1443 1492 +f 1444 1443 1493 +f 1494 1444 1493 +f 1445 1444 1494 +f 1495 1445 1494 +f 1446 1445 1495 +f 1496 1446 1495 +f 1447 1446 1496 +f 1497 1447 1496 +f 1448 1447 1497 +f 1498 1448 1497 +f 1449 1448 1498 +f 1499 1449 1498 +f 1450 1449 1499 +f 1500 1450 1499 +f 1452 1451 1501 +f 1502 1452 1501 +f 1453 1452 1502 +f 1503 1453 1502 +f 1454 1453 1503 +f 1504 1454 1503 +f 1455 1454 1504 +f 1505 1455 1504 +f 1456 1455 1505 +f 1506 1456 1505 +f 1457 1456 1506 +f 1507 1457 1506 +f 1458 1457 1507 +f 1508 1458 1507 +f 1459 1458 1508 +f 1509 1459 1508 +f 1460 1459 1509 +f 1510 1460 1509 +f 1461 1460 1510 +f 1511 1461 1510 +f 1462 1461 1511 +f 1512 1462 1511 +f 1463 1462 1512 +f 1513 1463 1512 +f 1464 1463 1513 +f 1514 1464 1513 +f 1465 1464 1514 +f 1515 1465 1514 +f 1466 1465 1515 +f 1516 1466 1515 +f 1467 1466 1516 +f 1517 1467 1516 +f 1468 1467 1517 +f 1518 1468 1517 +f 1469 1468 1518 +f 1519 1469 1518 +f 1470 1469 1519 +f 1520 1470 1519 +f 1471 1470 1520 +f 1521 1471 1520 +f 1472 1471 1521 +f 1522 1472 1521 +f 1473 1472 1522 +f 1523 1473 1522 +f 1474 1473 1523 +f 1524 1474 1523 +f 1475 1474 1524 +f 1525 1475 1524 +f 1476 1475 1525 +f 1526 1476 1525 +f 1477 1476 1526 +f 1527 1477 1526 +f 1478 1477 1527 +f 1528 1478 1527 +f 1479 1478 1528 +f 1529 1479 1528 +f 1480 1479 1529 +f 1530 1480 1529 +f 1481 1480 1530 +f 1531 1481 1530 +f 1482 1481 1531 +f 1532 1482 1531 +f 1483 1482 1532 +f 1533 1483 1532 +f 1484 1483 1533 +f 1534 1484 1533 +f 1485 1484 1534 +f 1535 1485 1534 +f 1486 1485 1535 +f 1536 1486 1535 +f 1487 1486 1536 +f 1537 1487 1536 +f 1488 1487 1537 +f 1538 1488 1537 +f 1489 1488 1538 +f 1539 1489 1538 +f 1490 1489 1539 +f 1540 1490 1539 +f 1491 1490 1540 +f 1541 1491 1540 +f 1492 1491 1541 +f 1542 1492 1541 +f 1493 1492 1542 +f 1543 1493 1542 +f 1494 1493 1543 +f 1544 1494 1543 +f 1495 1494 1544 +f 1545 1495 1544 +f 1496 1495 1545 +f 1546 1496 1545 +f 1497 1496 1546 +f 1547 1497 1546 +f 1498 1497 1547 +f 1548 1498 1547 +f 1499 1498 1548 +f 1549 1499 1548 +f 1500 1499 1549 +f 1550 1500 1549 +f 1502 1501 1551 +f 1552 1502 1551 +f 1503 1502 1552 +f 1553 1503 1552 +f 1504 1503 1553 +f 1554 1504 1553 +f 1505 1504 1554 +f 1555 1505 1554 +f 1506 1505 1555 +f 1556 1506 1555 +f 1507 1506 1556 +f 1557 1507 1556 +f 1508 1507 1557 +f 1558 1508 1557 +f 1509 1508 1558 +f 1559 1509 1558 +f 1510 1509 1559 +f 1560 1510 1559 +f 1511 1510 1560 +f 1561 1511 1560 +f 1512 1511 1561 +f 1562 1512 1561 +f 1513 1512 1562 +f 1563 1513 1562 +f 1514 1513 1563 +f 1564 1514 1563 +f 1515 1514 1564 +f 1565 1515 1564 +f 1516 1515 1565 +f 1566 1516 1565 +f 1517 1516 1566 +f 1567 1517 1566 +f 1518 1517 1567 +f 1568 1518 1567 +f 1519 1518 1568 +f 1569 1519 1568 +f 1520 1519 1569 +f 1570 1520 1569 +f 1521 1520 1570 +f 1571 1521 1570 +f 1522 1521 1571 +f 1572 1522 1571 +f 1523 1522 1572 +f 1573 1523 1572 +f 1524 1523 1573 +f 1574 1524 1573 +f 1525 1524 1574 +f 1575 1525 1574 +f 1526 1525 1575 +f 1576 1526 1575 +f 1527 1526 1576 +f 1577 1527 1576 +f 1528 1527 1577 +f 1578 1528 1577 +f 1529 1528 1578 +f 1579 1529 1578 +f 1530 1529 1579 +f 1580 1530 1579 +f 1531 1530 1580 +f 1581 1531 1580 +f 1532 1531 1581 +f 1582 1532 1581 +f 1533 1532 1582 +f 1583 1533 1582 +f 1534 1533 1583 +f 1584 1534 1583 +f 1535 1534 1584 +f 1585 1535 1584 +f 1536 1535 1585 +f 1586 1536 1585 +f 1537 1536 1586 +f 1587 1537 1586 +f 1538 1537 1587 +f 1588 1538 1587 +f 1539 1538 1588 +f 1589 1539 1588 +f 1540 1539 1589 +f 1590 1540 1589 +f 1541 1540 1590 +f 1591 1541 1590 +f 1542 1541 1591 +f 1592 1542 1591 +f 1543 1542 1592 +f 1593 1543 1592 +f 1544 1543 1593 +f 1594 1544 1593 +f 1545 1544 1594 +f 1595 1545 1594 +f 1546 1545 1595 +f 1596 1546 1595 +f 1547 1546 1596 +f 1597 1547 1596 +f 1548 1547 1597 +f 1598 1548 1597 +f 1549 1548 1598 +f 1599 1549 1598 +f 1550 1549 1599 +f 1600 1550 1599 +f 1552 1551 1601 +f 1602 1552 1601 +f 1553 1552 1602 +f 1603 1553 1602 +f 1554 1553 1603 +f 1604 1554 1603 +f 1555 1554 1604 +f 1605 1555 1604 +f 1556 1555 1605 +f 1606 1556 1605 +f 1557 1556 1606 +f 1607 1557 1606 +f 1558 1557 1607 +f 1608 1558 1607 +f 1559 1558 1608 +f 1609 1559 1608 +f 1560 1559 1609 +f 1610 1560 1609 +f 1561 1560 1610 +f 1611 1561 1610 +f 1562 1561 1611 +f 1612 1562 1611 +f 1563 1562 1612 +f 1613 1563 1612 +f 1564 1563 1613 +f 1614 1564 1613 +f 1565 1564 1614 +f 1615 1565 1614 +f 1566 1565 1615 +f 1616 1566 1615 +f 1567 1566 1616 +f 1617 1567 1616 +f 1568 1567 1617 +f 1618 1568 1617 +f 1569 1568 1618 +f 1619 1569 1618 +f 1570 1569 1619 +f 1620 1570 1619 +f 1571 1570 1620 +f 1621 1571 1620 +f 1572 1571 1621 +f 1622 1572 1621 +f 1573 1572 1622 +f 1623 1573 1622 +f 1574 1573 1623 +f 1624 1574 1623 +f 1575 1574 1624 +f 1625 1575 1624 +f 1576 1575 1625 +f 1626 1576 1625 +f 1577 1576 1626 +f 1627 1577 1626 +f 1578 1577 1627 +f 1628 1578 1627 +f 1579 1578 1628 +f 1629 1579 1628 +f 1580 1579 1629 +f 1630 1580 1629 +f 1581 1580 1630 +f 1631 1581 1630 +f 1582 1581 1631 +f 1632 1582 1631 +f 1583 1582 1632 +f 1633 1583 1632 +f 1584 1583 1633 +f 1634 1584 1633 +f 1585 1584 1634 +f 1635 1585 1634 +f 1586 1585 1635 +f 1636 1586 1635 +f 1587 1586 1636 +f 1637 1587 1636 +f 1588 1587 1637 +f 1638 1588 1637 +f 1589 1588 1638 +f 1639 1589 1638 +f 1590 1589 1639 +f 1640 1590 1639 +f 1591 1590 1640 +f 1641 1591 1640 +f 1592 1591 1641 +f 1642 1592 1641 +f 1593 1592 1642 +f 1643 1593 1642 +f 1594 1593 1643 +f 1644 1594 1643 +f 1595 1594 1644 +f 1645 1595 1644 +f 1596 1595 1645 +f 1646 1596 1645 +f 1597 1596 1646 +f 1647 1597 1646 +f 1598 1597 1647 +f 1648 1598 1647 +f 1599 1598 1648 +f 1649 1599 1648 +f 1600 1599 1649 +f 1650 1600 1649 +f 1602 1601 1651 +f 1652 1602 1651 +f 1603 1602 1652 +f 1653 1603 1652 +f 1604 1603 1653 +f 1654 1604 1653 +f 1605 1604 1654 +f 1655 1605 1654 +f 1606 1605 1655 +f 1656 1606 1655 +f 1607 1606 1656 +f 1657 1607 1656 +f 1608 1607 1657 +f 1658 1608 1657 +f 1609 1608 1658 +f 1659 1609 1658 +f 1610 1609 1659 +f 1660 1610 1659 +f 1611 1610 1660 +f 1661 1611 1660 +f 1612 1611 1661 +f 1662 1612 1661 +f 1613 1612 1662 +f 1663 1613 1662 +f 1614 1613 1663 +f 1664 1614 1663 +f 1615 1614 1664 +f 1665 1615 1664 +f 1616 1615 1665 +f 1666 1616 1665 +f 1617 1616 1666 +f 1667 1617 1666 +f 1618 1617 1667 +f 1668 1618 1667 +f 1619 1618 1668 +f 1669 1619 1668 +f 1620 1619 1669 +f 1670 1620 1669 +f 1621 1620 1670 +f 1671 1621 1670 +f 1622 1621 1671 +f 1672 1622 1671 +f 1623 1622 1672 +f 1673 1623 1672 +f 1624 1623 1673 +f 1674 1624 1673 +f 1625 1624 1674 +f 1675 1625 1674 +f 1626 1625 1675 +f 1676 1626 1675 +f 1627 1626 1676 +f 1677 1627 1676 +f 1628 1627 1677 +f 1678 1628 1677 +f 1629 1628 1678 +f 1679 1629 1678 +f 1630 1629 1679 +f 1680 1630 1679 +f 1631 1630 1680 +f 1681 1631 1680 +f 1632 1631 1681 +f 1682 1632 1681 +f 1633 1632 1682 +f 1683 1633 1682 +f 1634 1633 1683 +f 1684 1634 1683 +f 1635 1634 1684 +f 1685 1635 1684 +f 1636 1635 1685 +f 1686 1636 1685 +f 1637 1636 1686 +f 1687 1637 1686 +f 1638 1637 1687 +f 1688 1638 1687 +f 1639 1638 1688 +f 1689 1639 1688 +f 1640 1639 1689 +f 1690 1640 1689 +f 1641 1640 1690 +f 1691 1641 1690 +f 1642 1641 1691 +f 1692 1642 1691 +f 1643 1642 1692 +f 1693 1643 1692 +f 1644 1643 1693 +f 1694 1644 1693 +f 1645 1644 1694 +f 1695 1645 1694 +f 1646 1645 1695 +f 1696 1646 1695 +f 1647 1646 1696 +f 1697 1647 1696 +f 1648 1647 1697 +f 1698 1648 1697 +f 1649 1648 1698 +f 1699 1649 1698 +f 1650 1649 1699 +f 1700 1650 1699 +f 1652 1651 1701 +f 1702 1652 1701 +f 1653 1652 1702 +f 1703 1653 1702 +f 1654 1653 1703 +f 1704 1654 1703 +f 1655 1654 1704 +f 1705 1655 1704 +f 1656 1655 1705 +f 1706 1656 1705 +f 1657 1656 1706 +f 1707 1657 1706 +f 1658 1657 1707 +f 1708 1658 1707 +f 1659 1658 1708 +f 1709 1659 1708 +f 1660 1659 1709 +f 1710 1660 1709 +f 1661 1660 1710 +f 1711 1661 1710 +f 1662 1661 1711 +f 1712 1662 1711 +f 1663 1662 1712 +f 1713 1663 1712 +f 1664 1663 1713 +f 1714 1664 1713 +f 1665 1664 1714 +f 1715 1665 1714 +f 1666 1665 1715 +f 1716 1666 1715 +f 1667 1666 1716 +f 1717 1667 1716 +f 1668 1667 1717 +f 1718 1668 1717 +f 1669 1668 1718 +f 1719 1669 1718 +f 1670 1669 1719 +f 1720 1670 1719 +f 1671 1670 1720 +f 1721 1671 1720 +f 1672 1671 1721 +f 1722 1672 1721 +f 1673 1672 1722 +f 1723 1673 1722 +f 1674 1673 1723 +f 1724 1674 1723 +f 1675 1674 1724 +f 1725 1675 1724 +f 1676 1675 1725 +f 1726 1676 1725 +f 1677 1676 1726 +f 1727 1677 1726 +f 1678 1677 1727 +f 1728 1678 1727 +f 1679 1678 1728 +f 1729 1679 1728 +f 1680 1679 1729 +f 1730 1680 1729 +f 1681 1680 1730 +f 1731 1681 1730 +f 1682 1681 1731 +f 1732 1682 1731 +f 1683 1682 1732 +f 1733 1683 1732 +f 1684 1683 1733 +f 1734 1684 1733 +f 1685 1684 1734 +f 1735 1685 1734 +f 1686 1685 1735 +f 1736 1686 1735 +f 1687 1686 1736 +f 1737 1687 1736 +f 1688 1687 1737 +f 1738 1688 1737 +f 1689 1688 1738 +f 1739 1689 1738 +f 1690 1689 1739 +f 1740 1690 1739 +f 1691 1690 1740 +f 1741 1691 1740 +f 1692 1691 1741 +f 1742 1692 1741 +f 1693 1692 1742 +f 1743 1693 1742 +f 1694 1693 1743 +f 1744 1694 1743 +f 1695 1694 1744 +f 1745 1695 1744 +f 1696 1695 1745 +f 1746 1696 1745 +f 1697 1696 1746 +f 1747 1697 1746 +f 1698 1697 1747 +f 1748 1698 1747 +f 1699 1698 1748 +f 1749 1699 1748 +f 1700 1699 1749 +f 1750 1700 1749 +f 1702 1701 1751 +f 1752 1702 1751 +f 1703 1702 1752 +f 1753 1703 1752 +f 1704 1703 1753 +f 1754 1704 1753 +f 1705 1704 1754 +f 1755 1705 1754 +f 1706 1705 1755 +f 1756 1706 1755 +f 1707 1706 1756 +f 1757 1707 1756 +f 1708 1707 1757 +f 1758 1708 1757 +f 1709 1708 1758 +f 1759 1709 1758 +f 1710 1709 1759 +f 1760 1710 1759 +f 1711 1710 1760 +f 1761 1711 1760 +f 1712 1711 1761 +f 1762 1712 1761 +f 1713 1712 1762 +f 1763 1713 1762 +f 1714 1713 1763 +f 1764 1714 1763 +f 1715 1714 1764 +f 1765 1715 1764 +f 1716 1715 1765 +f 1766 1716 1765 +f 1717 1716 1766 +f 1767 1717 1766 +f 1718 1717 1767 +f 1768 1718 1767 +f 1719 1718 1768 +f 1769 1719 1768 +f 1720 1719 1769 +f 1770 1720 1769 +f 1721 1720 1770 +f 1771 1721 1770 +f 1722 1721 1771 +f 1772 1722 1771 +f 1723 1722 1772 +f 1773 1723 1772 +f 1724 1723 1773 +f 1774 1724 1773 +f 1725 1724 1774 +f 1775 1725 1774 +f 1726 1725 1775 +f 1776 1726 1775 +f 1727 1726 1776 +f 1777 1727 1776 +f 1728 1727 1777 +f 1778 1728 1777 +f 1729 1728 1778 +f 1779 1729 1778 +f 1730 1729 1779 +f 1780 1730 1779 +f 1731 1730 1780 +f 1781 1731 1780 +f 1732 1731 1781 +f 1782 1732 1781 +f 1733 1732 1782 +f 1783 1733 1782 +f 1734 1733 1783 +f 1784 1734 1783 +f 1735 1734 1784 +f 1785 1735 1784 +f 1736 1735 1785 +f 1786 1736 1785 +f 1737 1736 1786 +f 1787 1737 1786 +f 1738 1737 1787 +f 1788 1738 1787 +f 1739 1738 1788 +f 1789 1739 1788 +f 1740 1739 1789 +f 1790 1740 1789 +f 1741 1740 1790 +f 1791 1741 1790 +f 1742 1741 1791 +f 1792 1742 1791 +f 1743 1742 1792 +f 1793 1743 1792 +f 1744 1743 1793 +f 1794 1744 1793 +f 1745 1744 1794 +f 1795 1745 1794 +f 1746 1745 1795 +f 1796 1746 1795 +f 1747 1746 1796 +f 1797 1747 1796 +f 1748 1747 1797 +f 1798 1748 1797 +f 1749 1748 1798 +f 1799 1749 1798 +f 1750 1749 1799 +f 1800 1750 1799 +f 1752 1751 1801 +f 1802 1752 1801 +f 1753 1752 1802 +f 1803 1753 1802 +f 1754 1753 1803 +f 1804 1754 1803 +f 1755 1754 1804 +f 1805 1755 1804 +f 1756 1755 1805 +f 1806 1756 1805 +f 1757 1756 1806 +f 1807 1757 1806 +f 1758 1757 1807 +f 1808 1758 1807 +f 1759 1758 1808 +f 1809 1759 1808 +f 1760 1759 1809 +f 1810 1760 1809 +f 1761 1760 1810 +f 1811 1761 1810 +f 1762 1761 1811 +f 1812 1762 1811 +f 1763 1762 1812 +f 1813 1763 1812 +f 1764 1763 1813 +f 1814 1764 1813 +f 1765 1764 1814 +f 1815 1765 1814 +f 1766 1765 1815 +f 1816 1766 1815 +f 1767 1766 1816 +f 1817 1767 1816 +f 1768 1767 1817 +f 1818 1768 1817 +f 1769 1768 1818 +f 1819 1769 1818 +f 1770 1769 1819 +f 1820 1770 1819 +f 1771 1770 1820 +f 1821 1771 1820 +f 1772 1771 1821 +f 1822 1772 1821 +f 1773 1772 1822 +f 1823 1773 1822 +f 1774 1773 1823 +f 1824 1774 1823 +f 1775 1774 1824 +f 1825 1775 1824 +f 1776 1775 1825 +f 1826 1776 1825 +f 1777 1776 1826 +f 1827 1777 1826 +f 1778 1777 1827 +f 1828 1778 1827 +f 1779 1778 1828 +f 1829 1779 1828 +f 1780 1779 1829 +f 1830 1780 1829 +f 1781 1780 1830 +f 1831 1781 1830 +f 1782 1781 1831 +f 1832 1782 1831 +f 1783 1782 1832 +f 1833 1783 1832 +f 1784 1783 1833 +f 1834 1784 1833 +f 1785 1784 1834 +f 1835 1785 1834 +f 1786 1785 1835 +f 1836 1786 1835 +f 1787 1786 1836 +f 1837 1787 1836 +f 1788 1787 1837 +f 1838 1788 1837 +f 1789 1788 1838 +f 1839 1789 1838 +f 1790 1789 1839 +f 1840 1790 1839 +f 1791 1790 1840 +f 1841 1791 1840 +f 1792 1791 1841 +f 1842 1792 1841 +f 1793 1792 1842 +f 1843 1793 1842 +f 1794 1793 1843 +f 1844 1794 1843 +f 1795 1794 1844 +f 1845 1795 1844 +f 1796 1795 1845 +f 1846 1796 1845 +f 1797 1796 1846 +f 1847 1797 1846 +f 1798 1797 1847 +f 1848 1798 1847 +f 1799 1798 1848 +f 1849 1799 1848 +f 1800 1799 1849 +f 1850 1800 1849 +f 1802 1801 1851 +f 1852 1802 1851 +f 1803 1802 1852 +f 1853 1803 1852 +f 1804 1803 1853 +f 1854 1804 1853 +f 1805 1804 1854 +f 1855 1805 1854 +f 1806 1805 1855 +f 1856 1806 1855 +f 1807 1806 1856 +f 1857 1807 1856 +f 1808 1807 1857 +f 1858 1808 1857 +f 1809 1808 1858 +f 1859 1809 1858 +f 1810 1809 1859 +f 1860 1810 1859 +f 1811 1810 1860 +f 1861 1811 1860 +f 1812 1811 1861 +f 1862 1812 1861 +f 1813 1812 1862 +f 1863 1813 1862 +f 1814 1813 1863 +f 1864 1814 1863 +f 1815 1814 1864 +f 1865 1815 1864 +f 1816 1815 1865 +f 1866 1816 1865 +f 1817 1816 1866 +f 1867 1817 1866 +f 1818 1817 1867 +f 1868 1818 1867 +f 1819 1818 1868 +f 1869 1819 1868 +f 1820 1819 1869 +f 1870 1820 1869 +f 1821 1820 1870 +f 1871 1821 1870 +f 1822 1821 1871 +f 1872 1822 1871 +f 1823 1822 1872 +f 1873 1823 1872 +f 1824 1823 1873 +f 1874 1824 1873 +f 1825 1824 1874 +f 1875 1825 1874 +f 1826 1825 1875 +f 1876 1826 1875 +f 1827 1826 1876 +f 1877 1827 1876 +f 1828 1827 1877 +f 1878 1828 1877 +f 1829 1828 1878 +f 1879 1829 1878 +f 1830 1829 1879 +f 1880 1830 1879 +f 1831 1830 1880 +f 1881 1831 1880 +f 1832 1831 1881 +f 1882 1832 1881 +f 1833 1832 1882 +f 1883 1833 1882 +f 1834 1833 1883 +f 1884 1834 1883 +f 1835 1834 1884 +f 1885 1835 1884 +f 1836 1835 1885 +f 1886 1836 1885 +f 1837 1836 1886 +f 1887 1837 1886 +f 1838 1837 1887 +f 1888 1838 1887 +f 1839 1838 1888 +f 1889 1839 1888 +f 1840 1839 1889 +f 1890 1840 1889 +f 1841 1840 1890 +f 1891 1841 1890 +f 1842 1841 1891 +f 1892 1842 1891 +f 1843 1842 1892 +f 1893 1843 1892 +f 1844 1843 1893 +f 1894 1844 1893 +f 1845 1844 1894 +f 1895 1845 1894 +f 1846 1845 1895 +f 1896 1846 1895 +f 1847 1846 1896 +f 1897 1847 1896 +f 1848 1847 1897 +f 1898 1848 1897 +f 1849 1848 1898 +f 1899 1849 1898 +f 1850 1849 1899 +f 1900 1850 1899 +f 1852 1851 1901 +f 1902 1852 1901 +f 1853 1852 1902 +f 1903 1853 1902 +f 1854 1853 1903 +f 1904 1854 1903 +f 1855 1854 1904 +f 1905 1855 1904 +f 1856 1855 1905 +f 1906 1856 1905 +f 1857 1856 1906 +f 1907 1857 1906 +f 1858 1857 1907 +f 1908 1858 1907 +f 1859 1858 1908 +f 1909 1859 1908 +f 1860 1859 1909 +f 1910 1860 1909 +f 1861 1860 1910 +f 1911 1861 1910 +f 1862 1861 1911 +f 1912 1862 1911 +f 1863 1862 1912 +f 1913 1863 1912 +f 1864 1863 1913 +f 1914 1864 1913 +f 1865 1864 1914 +f 1915 1865 1914 +f 1866 1865 1915 +f 1916 1866 1915 +f 1867 1866 1916 +f 1917 1867 1916 +f 1868 1867 1917 +f 1918 1868 1917 +f 1869 1868 1918 +f 1919 1869 1918 +f 1870 1869 1919 +f 1920 1870 1919 +f 1871 1870 1920 +f 1921 1871 1920 +f 1872 1871 1921 +f 1922 1872 1921 +f 1873 1872 1922 +f 1923 1873 1922 +f 1874 1873 1923 +f 1924 1874 1923 +f 1875 1874 1924 +f 1925 1875 1924 +f 1876 1875 1925 +f 1926 1876 1925 +f 1877 1876 1926 +f 1927 1877 1926 +f 1878 1877 1927 +f 1928 1878 1927 +f 1879 1878 1928 +f 1929 1879 1928 +f 1880 1879 1929 +f 1930 1880 1929 +f 1881 1880 1930 +f 1931 1881 1930 +f 1882 1881 1931 +f 1932 1882 1931 +f 1883 1882 1932 +f 1933 1883 1932 +f 1884 1883 1933 +f 1934 1884 1933 +f 1885 1884 1934 +f 1935 1885 1934 +f 1886 1885 1935 +f 1936 1886 1935 +f 1887 1886 1936 +f 1937 1887 1936 +f 1888 1887 1937 +f 1938 1888 1937 +f 1889 1888 1938 +f 1939 1889 1938 +f 1890 1889 1939 +f 1940 1890 1939 +f 1891 1890 1940 +f 1941 1891 1940 +f 1892 1891 1941 +f 1942 1892 1941 +f 1893 1892 1942 +f 1943 1893 1942 +f 1894 1893 1943 +f 1944 1894 1943 +f 1895 1894 1944 +f 1945 1895 1944 +f 1896 1895 1945 +f 1946 1896 1945 +f 1897 1896 1946 +f 1947 1897 1946 +f 1898 1897 1947 +f 1948 1898 1947 +f 1899 1898 1948 +f 1949 1899 1948 +f 1900 1899 1949 +f 1950 1900 1949 +f 1902 1901 1951 +f 1952 1902 1951 +f 1903 1902 1952 +f 1953 1903 1952 +f 1904 1903 1953 +f 1954 1904 1953 +f 1905 1904 1954 +f 1955 1905 1954 +f 1906 1905 1955 +f 1956 1906 1955 +f 1907 1906 1956 +f 1957 1907 1956 +f 1908 1907 1957 +f 1958 1908 1957 +f 1909 1908 1958 +f 1959 1909 1958 +f 1910 1909 1959 +f 1960 1910 1959 +f 1911 1910 1960 +f 1961 1911 1960 +f 1912 1911 1961 +f 1962 1912 1961 +f 1913 1912 1962 +f 1963 1913 1962 +f 1914 1913 1963 +f 1964 1914 1963 +f 1915 1914 1964 +f 1965 1915 1964 +f 1916 1915 1965 +f 1966 1916 1965 +f 1917 1916 1966 +f 1967 1917 1966 +f 1918 1917 1967 +f 1968 1918 1967 +f 1919 1918 1968 +f 1969 1919 1968 +f 1920 1919 1969 +f 1970 1920 1969 +f 1921 1920 1970 +f 1971 1921 1970 +f 1922 1921 1971 +f 1972 1922 1971 +f 1923 1922 1972 +f 1973 1923 1972 +f 1924 1923 1973 +f 1974 1924 1973 +f 1925 1924 1974 +f 1975 1925 1974 +f 1926 1925 1975 +f 1976 1926 1975 +f 1927 1926 1976 +f 1977 1927 1976 +f 1928 1927 1977 +f 1978 1928 1977 +f 1929 1928 1978 +f 1979 1929 1978 +f 1930 1929 1979 +f 1980 1930 1979 +f 1931 1930 1980 +f 1981 1931 1980 +f 1932 1931 1981 +f 1982 1932 1981 +f 1933 1932 1982 +f 1983 1933 1982 +f 1934 1933 1983 +f 1984 1934 1983 +f 1935 1934 1984 +f 1985 1935 1984 +f 1936 1935 1985 +f 1986 1936 1985 +f 1937 1936 1986 +f 1987 1937 1986 +f 1938 1937 1987 +f 1988 1938 1987 +f 1939 1938 1988 +f 1989 1939 1988 +f 1940 1939 1989 +f 1990 1940 1989 +f 1941 1940 1990 +f 1991 1941 1990 +f 1942 1941 1991 +f 1992 1942 1991 +f 1943 1942 1992 +f 1993 1943 1992 +f 1944 1943 1993 +f 1994 1944 1993 +f 1945 1944 1994 +f 1995 1945 1994 +f 1946 1945 1995 +f 1996 1946 1995 +f 1947 1946 1996 +f 1997 1947 1996 +f 1948 1947 1997 +f 1998 1948 1997 +f 1949 1948 1998 +f 1999 1949 1998 +f 1950 1949 1999 +f 2000 1950 1999 +f 1952 1951 2001 +f 2002 1952 2001 +f 1953 1952 2002 +f 2003 1953 2002 +f 1954 1953 2003 +f 2004 1954 2003 +f 1955 1954 2004 +f 2005 1955 2004 +f 1956 1955 2005 +f 2006 1956 2005 +f 1957 1956 2006 +f 2007 1957 2006 +f 1958 1957 2007 +f 2008 1958 2007 +f 1959 1958 2008 +f 2009 1959 2008 +f 1960 1959 2009 +f 2010 1960 2009 +f 1961 1960 2010 +f 2011 1961 2010 +f 1962 1961 2011 +f 2012 1962 2011 +f 1963 1962 2012 +f 2013 1963 2012 +f 1964 1963 2013 +f 2014 1964 2013 +f 1965 1964 2014 +f 2015 1965 2014 +f 1966 1965 2015 +f 2016 1966 2015 +f 1967 1966 2016 +f 2017 1967 2016 +f 1968 1967 2017 +f 2018 1968 2017 +f 1969 1968 2018 +f 2019 1969 2018 +f 1970 1969 2019 +f 2020 1970 2019 +f 1971 1970 2020 +f 2021 1971 2020 +f 1972 1971 2021 +f 2022 1972 2021 +f 1973 1972 2022 +f 2023 1973 2022 +f 1974 1973 2023 +f 2024 1974 2023 +f 1975 1974 2024 +f 2025 1975 2024 +f 1976 1975 2025 +f 2026 1976 2025 +f 1977 1976 2026 +f 2027 1977 2026 +f 1978 1977 2027 +f 2028 1978 2027 +f 1979 1978 2028 +f 2029 1979 2028 +f 1980 1979 2029 +f 2030 1980 2029 +f 1981 1980 2030 +f 2031 1981 2030 +f 1982 1981 2031 +f 2032 1982 2031 +f 1983 1982 2032 +f 2033 1983 2032 +f 1984 1983 2033 +f 2034 1984 2033 +f 1985 1984 2034 +f 2035 1985 2034 +f 1986 1985 2035 +f 2036 1986 2035 +f 1987 1986 2036 +f 2037 1987 2036 +f 1988 1987 2037 +f 2038 1988 2037 +f 1989 1988 2038 +f 2039 1989 2038 +f 1990 1989 2039 +f 2040 1990 2039 +f 1991 1990 2040 +f 2041 1991 2040 +f 1992 1991 2041 +f 2042 1992 2041 +f 1993 1992 2042 +f 2043 1993 2042 +f 1994 1993 2043 +f 2044 1994 2043 +f 1995 1994 2044 +f 2045 1995 2044 +f 1996 1995 2045 +f 2046 1996 2045 +f 1997 1996 2046 +f 2047 1997 2046 +f 1998 1997 2047 +f 2048 1998 2047 +f 1999 1998 2048 +f 2049 1999 2048 +f 2000 1999 2049 +f 2050 2000 2049 +f 2002 2001 2051 +f 2052 2002 2051 +f 2003 2002 2052 +f 2053 2003 2052 +f 2004 2003 2053 +f 2054 2004 2053 +f 2005 2004 2054 +f 2055 2005 2054 +f 2006 2005 2055 +f 2056 2006 2055 +f 2007 2006 2056 +f 2057 2007 2056 +f 2008 2007 2057 +f 2058 2008 2057 +f 2009 2008 2058 +f 2059 2009 2058 +f 2010 2009 2059 +f 2060 2010 2059 +f 2011 2010 2060 +f 2061 2011 2060 +f 2012 2011 2061 +f 2062 2012 2061 +f 2013 2012 2062 +f 2063 2013 2062 +f 2014 2013 2063 +f 2064 2014 2063 +f 2015 2014 2064 +f 2065 2015 2064 +f 2016 2015 2065 +f 2066 2016 2065 +f 2017 2016 2066 +f 2067 2017 2066 +f 2018 2017 2067 +f 2068 2018 2067 +f 2019 2018 2068 +f 2069 2019 2068 +f 2020 2019 2069 +f 2070 2020 2069 +f 2021 2020 2070 +f 2071 2021 2070 +f 2022 2021 2071 +f 2072 2022 2071 +f 2023 2022 2072 +f 2073 2023 2072 +f 2024 2023 2073 +f 2074 2024 2073 +f 2025 2024 2074 +f 2075 2025 2074 +f 2026 2025 2075 +f 2076 2026 2075 +f 2027 2026 2076 +f 2077 2027 2076 +f 2028 2027 2077 +f 2078 2028 2077 +f 2029 2028 2078 +f 2079 2029 2078 +f 2030 2029 2079 +f 2080 2030 2079 +f 2031 2030 2080 +f 2081 2031 2080 +f 2032 2031 2081 +f 2082 2032 2081 +f 2033 2032 2082 +f 2083 2033 2082 +f 2034 2033 2083 +f 2084 2034 2083 +f 2035 2034 2084 +f 2085 2035 2084 +f 2036 2035 2085 +f 2086 2036 2085 +f 2037 2036 2086 +f 2087 2037 2086 +f 2038 2037 2087 +f 2088 2038 2087 +f 2039 2038 2088 +f 2089 2039 2088 +f 2040 2039 2089 +f 2090 2040 2089 +f 2041 2040 2090 +f 2091 2041 2090 +f 2042 2041 2091 +f 2092 2042 2091 +f 2043 2042 2092 +f 2093 2043 2092 +f 2044 2043 2093 +f 2094 2044 2093 +f 2045 2044 2094 +f 2095 2045 2094 +f 2046 2045 2095 +f 2096 2046 2095 +f 2047 2046 2096 +f 2097 2047 2096 +f 2048 2047 2097 +f 2098 2048 2097 +f 2049 2048 2098 +f 2099 2049 2098 +f 2050 2049 2099 +f 2100 2050 2099 +f 2052 2051 2101 +f 2102 2052 2101 +f 2053 2052 2102 +f 2103 2053 2102 +f 2054 2053 2103 +f 2104 2054 2103 +f 2055 2054 2104 +f 2105 2055 2104 +f 2056 2055 2105 +f 2106 2056 2105 +f 2057 2056 2106 +f 2107 2057 2106 +f 2058 2057 2107 +f 2108 2058 2107 +f 2059 2058 2108 +f 2109 2059 2108 +f 2060 2059 2109 +f 2110 2060 2109 +f 2061 2060 2110 +f 2111 2061 2110 +f 2062 2061 2111 +f 2112 2062 2111 +f 2063 2062 2112 +f 2113 2063 2112 +f 2064 2063 2113 +f 2114 2064 2113 +f 2065 2064 2114 +f 2115 2065 2114 +f 2066 2065 2115 +f 2116 2066 2115 +f 2067 2066 2116 +f 2117 2067 2116 +f 2068 2067 2117 +f 2118 2068 2117 +f 2069 2068 2118 +f 2119 2069 2118 +f 2070 2069 2119 +f 2120 2070 2119 +f 2071 2070 2120 +f 2121 2071 2120 +f 2072 2071 2121 +f 2122 2072 2121 +f 2073 2072 2122 +f 2123 2073 2122 +f 2074 2073 2123 +f 2124 2074 2123 +f 2075 2074 2124 +f 2125 2075 2124 +f 2076 2075 2125 +f 2126 2076 2125 +f 2077 2076 2126 +f 2127 2077 2126 +f 2078 2077 2127 +f 2128 2078 2127 +f 2079 2078 2128 +f 2129 2079 2128 +f 2080 2079 2129 +f 2130 2080 2129 +f 2081 2080 2130 +f 2131 2081 2130 +f 2082 2081 2131 +f 2132 2082 2131 +f 2083 2082 2132 +f 2133 2083 2132 +f 2084 2083 2133 +f 2134 2084 2133 +f 2085 2084 2134 +f 2135 2085 2134 +f 2086 2085 2135 +f 2136 2086 2135 +f 2087 2086 2136 +f 2137 2087 2136 +f 2088 2087 2137 +f 2138 2088 2137 +f 2089 2088 2138 +f 2139 2089 2138 +f 2090 2089 2139 +f 2140 2090 2139 +f 2091 2090 2140 +f 2141 2091 2140 +f 2092 2091 2141 +f 2142 2092 2141 +f 2093 2092 2142 +f 2143 2093 2142 +f 2094 2093 2143 +f 2144 2094 2143 +f 2095 2094 2144 +f 2145 2095 2144 +f 2096 2095 2145 +f 2146 2096 2145 +f 2097 2096 2146 +f 2147 2097 2146 +f 2098 2097 2147 +f 2148 2098 2147 +f 2099 2098 2148 +f 2149 2099 2148 +f 2100 2099 2149 +f 2150 2100 2149 +f 2102 2101 2151 +f 2152 2102 2151 +f 2103 2102 2152 +f 2153 2103 2152 +f 2104 2103 2153 +f 2154 2104 2153 +f 2105 2104 2154 +f 2155 2105 2154 +f 2106 2105 2155 +f 2156 2106 2155 +f 2107 2106 2156 +f 2157 2107 2156 +f 2108 2107 2157 +f 2158 2108 2157 +f 2109 2108 2158 +f 2159 2109 2158 +f 2110 2109 2159 +f 2160 2110 2159 +f 2111 2110 2160 +f 2161 2111 2160 +f 2112 2111 2161 +f 2162 2112 2161 +f 2113 2112 2162 +f 2163 2113 2162 +f 2114 2113 2163 +f 2164 2114 2163 +f 2115 2114 2164 +f 2165 2115 2164 +f 2116 2115 2165 +f 2166 2116 2165 +f 2117 2116 2166 +f 2167 2117 2166 +f 2118 2117 2167 +f 2168 2118 2167 +f 2119 2118 2168 +f 2169 2119 2168 +f 2120 2119 2169 +f 2170 2120 2169 +f 2121 2120 2170 +f 2171 2121 2170 +f 2122 2121 2171 +f 2172 2122 2171 +f 2123 2122 2172 +f 2173 2123 2172 +f 2124 2123 2173 +f 2174 2124 2173 +f 2125 2124 2174 +f 2175 2125 2174 +f 2126 2125 2175 +f 2176 2126 2175 +f 2127 2126 2176 +f 2177 2127 2176 +f 2128 2127 2177 +f 2178 2128 2177 +f 2129 2128 2178 +f 2179 2129 2178 +f 2130 2129 2179 +f 2180 2130 2179 +f 2131 2130 2180 +f 2181 2131 2180 +f 2132 2131 2181 +f 2182 2132 2181 +f 2133 2132 2182 +f 2183 2133 2182 +f 2134 2133 2183 +f 2184 2134 2183 +f 2135 2134 2184 +f 2185 2135 2184 +f 2136 2135 2185 +f 2186 2136 2185 +f 2137 2136 2186 +f 2187 2137 2186 +f 2138 2137 2187 +f 2188 2138 2187 +f 2139 2138 2188 +f 2189 2139 2188 +f 2140 2139 2189 +f 2190 2140 2189 +f 2141 2140 2190 +f 2191 2141 2190 +f 2142 2141 2191 +f 2192 2142 2191 +f 2143 2142 2192 +f 2193 2143 2192 +f 2144 2143 2193 +f 2194 2144 2193 +f 2145 2144 2194 +f 2195 2145 2194 +f 2146 2145 2195 +f 2196 2146 2195 +f 2147 2146 2196 +f 2197 2147 2196 +f 2148 2147 2197 +f 2198 2148 2197 +f 2149 2148 2198 +f 2199 2149 2198 +f 2150 2149 2199 +f 2200 2150 2199 +f 2152 2151 2201 +f 2202 2152 2201 +f 2153 2152 2202 +f 2203 2153 2202 +f 2154 2153 2203 +f 2204 2154 2203 +f 2155 2154 2204 +f 2205 2155 2204 +f 2156 2155 2205 +f 2206 2156 2205 +f 2157 2156 2206 +f 2207 2157 2206 +f 2158 2157 2207 +f 2208 2158 2207 +f 2159 2158 2208 +f 2209 2159 2208 +f 2160 2159 2209 +f 2210 2160 2209 +f 2161 2160 2210 +f 2211 2161 2210 +f 2162 2161 2211 +f 2212 2162 2211 +f 2163 2162 2212 +f 2213 2163 2212 +f 2164 2163 2213 +f 2214 2164 2213 +f 2165 2164 2214 +f 2215 2165 2214 +f 2166 2165 2215 +f 2216 2166 2215 +f 2167 2166 2216 +f 2217 2167 2216 +f 2168 2167 2217 +f 2218 2168 2217 +f 2169 2168 2218 +f 2219 2169 2218 +f 2170 2169 2219 +f 2220 2170 2219 +f 2171 2170 2220 +f 2221 2171 2220 +f 2172 2171 2221 +f 2222 2172 2221 +f 2173 2172 2222 +f 2223 2173 2222 +f 2174 2173 2223 +f 2224 2174 2223 +f 2175 2174 2224 +f 2225 2175 2224 +f 2176 2175 2225 +f 2226 2176 2225 +f 2177 2176 2226 +f 2227 2177 2226 +f 2178 2177 2227 +f 2228 2178 2227 +f 2179 2178 2228 +f 2229 2179 2228 +f 2180 2179 2229 +f 2230 2180 2229 +f 2181 2180 2230 +f 2231 2181 2230 +f 2182 2181 2231 +f 2232 2182 2231 +f 2183 2182 2232 +f 2233 2183 2232 +f 2184 2183 2233 +f 2234 2184 2233 +f 2185 2184 2234 +f 2235 2185 2234 +f 2186 2185 2235 +f 2236 2186 2235 +f 2187 2186 2236 +f 2237 2187 2236 +f 2188 2187 2237 +f 2238 2188 2237 +f 2189 2188 2238 +f 2239 2189 2238 +f 2190 2189 2239 +f 2240 2190 2239 +f 2191 2190 2240 +f 2241 2191 2240 +f 2192 2191 2241 +f 2242 2192 2241 +f 2193 2192 2242 +f 2243 2193 2242 +f 2194 2193 2243 +f 2244 2194 2243 +f 2195 2194 2244 +f 2245 2195 2244 +f 2196 2195 2245 +f 2246 2196 2245 +f 2197 2196 2246 +f 2247 2197 2246 +f 2198 2197 2247 +f 2248 2198 2247 +f 2199 2198 2248 +f 2249 2199 2248 +f 2200 2199 2249 +f 2250 2200 2249 +f 2202 2201 2251 +f 2252 2202 2251 +f 2203 2202 2252 +f 2253 2203 2252 +f 2204 2203 2253 +f 2254 2204 2253 +f 2205 2204 2254 +f 2255 2205 2254 +f 2206 2205 2255 +f 2256 2206 2255 +f 2207 2206 2256 +f 2257 2207 2256 +f 2208 2207 2257 +f 2258 2208 2257 +f 2209 2208 2258 +f 2259 2209 2258 +f 2210 2209 2259 +f 2260 2210 2259 +f 2211 2210 2260 +f 2261 2211 2260 +f 2212 2211 2261 +f 2262 2212 2261 +f 2213 2212 2262 +f 2263 2213 2262 +f 2214 2213 2263 +f 2264 2214 2263 +f 2215 2214 2264 +f 2265 2215 2264 +f 2216 2215 2265 +f 2266 2216 2265 +f 2217 2216 2266 +f 2267 2217 2266 +f 2218 2217 2267 +f 2268 2218 2267 +f 2219 2218 2268 +f 2269 2219 2268 +f 2220 2219 2269 +f 2270 2220 2269 +f 2221 2220 2270 +f 2271 2221 2270 +f 2222 2221 2271 +f 2272 2222 2271 +f 2223 2222 2272 +f 2273 2223 2272 +f 2224 2223 2273 +f 2274 2224 2273 +f 2225 2224 2274 +f 2275 2225 2274 +f 2226 2225 2275 +f 2276 2226 2275 +f 2227 2226 2276 +f 2277 2227 2276 +f 2228 2227 2277 +f 2278 2228 2277 +f 2229 2228 2278 +f 2279 2229 2278 +f 2230 2229 2279 +f 2280 2230 2279 +f 2231 2230 2280 +f 2281 2231 2280 +f 2232 2231 2281 +f 2282 2232 2281 +f 2233 2232 2282 +f 2283 2233 2282 +f 2234 2233 2283 +f 2284 2234 2283 +f 2235 2234 2284 +f 2285 2235 2284 +f 2236 2235 2285 +f 2286 2236 2285 +f 2237 2236 2286 +f 2287 2237 2286 +f 2238 2237 2287 +f 2288 2238 2287 +f 2239 2238 2288 +f 2289 2239 2288 +f 2240 2239 2289 +f 2290 2240 2289 +f 2241 2240 2290 +f 2291 2241 2290 +f 2242 2241 2291 +f 2292 2242 2291 +f 2243 2242 2292 +f 2293 2243 2292 +f 2244 2243 2293 +f 2294 2244 2293 +f 2245 2244 2294 +f 2295 2245 2294 +f 2246 2245 2295 +f 2296 2246 2295 +f 2247 2246 2296 +f 2297 2247 2296 +f 2248 2247 2297 +f 2298 2248 2297 +f 2249 2248 2298 +f 2299 2249 2298 +f 2250 2249 2299 +f 2300 2250 2299 +f 2252 2251 2301 +f 2302 2252 2301 +f 2253 2252 2302 +f 2303 2253 2302 +f 2254 2253 2303 +f 2304 2254 2303 +f 2255 2254 2304 +f 2305 2255 2304 +f 2256 2255 2305 +f 2306 2256 2305 +f 2257 2256 2306 +f 2307 2257 2306 +f 2258 2257 2307 +f 2308 2258 2307 +f 2259 2258 2308 +f 2309 2259 2308 +f 2260 2259 2309 +f 2310 2260 2309 +f 2261 2260 2310 +f 2311 2261 2310 +f 2262 2261 2311 +f 2312 2262 2311 +f 2263 2262 2312 +f 2313 2263 2312 +f 2264 2263 2313 +f 2314 2264 2313 +f 2265 2264 2314 +f 2315 2265 2314 +f 2266 2265 2315 +f 2316 2266 2315 +f 2267 2266 2316 +f 2317 2267 2316 +f 2268 2267 2317 +f 2318 2268 2317 +f 2269 2268 2318 +f 2319 2269 2318 +f 2270 2269 2319 +f 2320 2270 2319 +f 2271 2270 2320 +f 2321 2271 2320 +f 2272 2271 2321 +f 2322 2272 2321 +f 2273 2272 2322 +f 2323 2273 2322 +f 2274 2273 2323 +f 2324 2274 2323 +f 2275 2274 2324 +f 2325 2275 2324 +f 2276 2275 2325 +f 2326 2276 2325 +f 2277 2276 2326 +f 2327 2277 2326 +f 2278 2277 2327 +f 2328 2278 2327 +f 2279 2278 2328 +f 2329 2279 2328 +f 2280 2279 2329 +f 2330 2280 2329 +f 2281 2280 2330 +f 2331 2281 2330 +f 2282 2281 2331 +f 2332 2282 2331 +f 2283 2282 2332 +f 2333 2283 2332 +f 2284 2283 2333 +f 2334 2284 2333 +f 2285 2284 2334 +f 2335 2285 2334 +f 2286 2285 2335 +f 2336 2286 2335 +f 2287 2286 2336 +f 2337 2287 2336 +f 2288 2287 2337 +f 2338 2288 2337 +f 2289 2288 2338 +f 2339 2289 2338 +f 2290 2289 2339 +f 2340 2290 2339 +f 2291 2290 2340 +f 2341 2291 2340 +f 2292 2291 2341 +f 2342 2292 2341 +f 2293 2292 2342 +f 2343 2293 2342 +f 2294 2293 2343 +f 2344 2294 2343 +f 2295 2294 2344 +f 2345 2295 2344 +f 2296 2295 2345 +f 2346 2296 2345 +f 2297 2296 2346 +f 2347 2297 2346 +f 2298 2297 2347 +f 2348 2298 2347 +f 2299 2298 2348 +f 2349 2299 2348 +f 2300 2299 2349 +f 2350 2300 2349 +f 2302 2301 2351 +f 2352 2302 2351 +f 2303 2302 2352 +f 2353 2303 2352 +f 2304 2303 2353 +f 2354 2304 2353 +f 2305 2304 2354 +f 2355 2305 2354 +f 2306 2305 2355 +f 2356 2306 2355 +f 2307 2306 2356 +f 2357 2307 2356 +f 2308 2307 2357 +f 2358 2308 2357 +f 2309 2308 2358 +f 2359 2309 2358 +f 2310 2309 2359 +f 2360 2310 2359 +f 2311 2310 2360 +f 2361 2311 2360 +f 2312 2311 2361 +f 2362 2312 2361 +f 2313 2312 2362 +f 2363 2313 2362 +f 2314 2313 2363 +f 2364 2314 2363 +f 2315 2314 2364 +f 2365 2315 2364 +f 2316 2315 2365 +f 2366 2316 2365 +f 2317 2316 2366 +f 2367 2317 2366 +f 2318 2317 2367 +f 2368 2318 2367 +f 2319 2318 2368 +f 2369 2319 2368 +f 2320 2319 2369 +f 2370 2320 2369 +f 2321 2320 2370 +f 2371 2321 2370 +f 2322 2321 2371 +f 2372 2322 2371 +f 2323 2322 2372 +f 2373 2323 2372 +f 2324 2323 2373 +f 2374 2324 2373 +f 2325 2324 2374 +f 2375 2325 2374 +f 2326 2325 2375 +f 2376 2326 2375 +f 2327 2326 2376 +f 2377 2327 2376 +f 2328 2327 2377 +f 2378 2328 2377 +f 2329 2328 2378 +f 2379 2329 2378 +f 2330 2329 2379 +f 2380 2330 2379 +f 2331 2330 2380 +f 2381 2331 2380 +f 2332 2331 2381 +f 2382 2332 2381 +f 2333 2332 2382 +f 2383 2333 2382 +f 2334 2333 2383 +f 2384 2334 2383 +f 2335 2334 2384 +f 2385 2335 2384 +f 2336 2335 2385 +f 2386 2336 2385 +f 2337 2336 2386 +f 2387 2337 2386 +f 2338 2337 2387 +f 2388 2338 2387 +f 2339 2338 2388 +f 2389 2339 2388 +f 2340 2339 2389 +f 2390 2340 2389 +f 2341 2340 2390 +f 2391 2341 2390 +f 2342 2341 2391 +f 2392 2342 2391 +f 2343 2342 2392 +f 2393 2343 2392 +f 2344 2343 2393 +f 2394 2344 2393 +f 2345 2344 2394 +f 2395 2345 2394 +f 2346 2345 2395 +f 2396 2346 2395 +f 2347 2346 2396 +f 2397 2347 2396 +f 2348 2347 2397 +f 2398 2348 2397 +f 2349 2348 2398 +f 2399 2349 2398 +f 2350 2349 2399 +f 2400 2350 2399 +f 2352 2351 2401 +f 2402 2352 2401 +f 2353 2352 2402 +f 2403 2353 2402 +f 2354 2353 2403 +f 2404 2354 2403 +f 2355 2354 2404 +f 2405 2355 2404 +f 2356 2355 2405 +f 2406 2356 2405 +f 2357 2356 2406 +f 2407 2357 2406 +f 2358 2357 2407 +f 2408 2358 2407 +f 2359 2358 2408 +f 2409 2359 2408 +f 2360 2359 2409 +f 2410 2360 2409 +f 2361 2360 2410 +f 2411 2361 2410 +f 2362 2361 2411 +f 2412 2362 2411 +f 2363 2362 2412 +f 2413 2363 2412 +f 2364 2363 2413 +f 2414 2364 2413 +f 2365 2364 2414 +f 2415 2365 2414 +f 2366 2365 2415 +f 2416 2366 2415 +f 2367 2366 2416 +f 2417 2367 2416 +f 2368 2367 2417 +f 2418 2368 2417 +f 2369 2368 2418 +f 2419 2369 2418 +f 2370 2369 2419 +f 2420 2370 2419 +f 2371 2370 2420 +f 2421 2371 2420 +f 2372 2371 2421 +f 2422 2372 2421 +f 2373 2372 2422 +f 2423 2373 2422 +f 2374 2373 2423 +f 2424 2374 2423 +f 2375 2374 2424 +f 2425 2375 2424 +f 2376 2375 2425 +f 2426 2376 2425 +f 2377 2376 2426 +f 2427 2377 2426 +f 2378 2377 2427 +f 2428 2378 2427 +f 2379 2378 2428 +f 2429 2379 2428 +f 2380 2379 2429 +f 2430 2380 2429 +f 2381 2380 2430 +f 2431 2381 2430 +f 2382 2381 2431 +f 2432 2382 2431 +f 2383 2382 2432 +f 2433 2383 2432 +f 2384 2383 2433 +f 2434 2384 2433 +f 2385 2384 2434 +f 2435 2385 2434 +f 2386 2385 2435 +f 2436 2386 2435 +f 2387 2386 2436 +f 2437 2387 2436 +f 2388 2387 2437 +f 2438 2388 2437 +f 2389 2388 2438 +f 2439 2389 2438 +f 2390 2389 2439 +f 2440 2390 2439 +f 2391 2390 2440 +f 2441 2391 2440 +f 2392 2391 2441 +f 2442 2392 2441 +f 2393 2392 2442 +f 2443 2393 2442 +f 2394 2393 2443 +f 2444 2394 2443 +f 2395 2394 2444 +f 2445 2395 2444 +f 2396 2395 2445 +f 2446 2396 2445 +f 2397 2396 2446 +f 2447 2397 2446 +f 2398 2397 2447 +f 2448 2398 2447 +f 2399 2398 2448 +f 2449 2399 2448 +f 2400 2399 2449 +f 2450 2400 2449 +f 2402 2401 2451 +f 2452 2402 2451 +f 2403 2402 2452 +f 2453 2403 2452 +f 2404 2403 2453 +f 2454 2404 2453 +f 2405 2404 2454 +f 2455 2405 2454 +f 2406 2405 2455 +f 2456 2406 2455 +f 2407 2406 2456 +f 2457 2407 2456 +f 2408 2407 2457 +f 2458 2408 2457 +f 2409 2408 2458 +f 2459 2409 2458 +f 2410 2409 2459 +f 2460 2410 2459 +f 2411 2410 2460 +f 2461 2411 2460 +f 2412 2411 2461 +f 2462 2412 2461 +f 2413 2412 2462 +f 2463 2413 2462 +f 2414 2413 2463 +f 2464 2414 2463 +f 2415 2414 2464 +f 2465 2415 2464 +f 2416 2415 2465 +f 2466 2416 2465 +f 2417 2416 2466 +f 2467 2417 2466 +f 2418 2417 2467 +f 2468 2418 2467 +f 2419 2418 2468 +f 2469 2419 2468 +f 2420 2419 2469 +f 2470 2420 2469 +f 2421 2420 2470 +f 2471 2421 2470 +f 2422 2421 2471 +f 2472 2422 2471 +f 2423 2422 2472 +f 2473 2423 2472 +f 2424 2423 2473 +f 2474 2424 2473 +f 2425 2424 2474 +f 2475 2425 2474 +f 2426 2425 2475 +f 2476 2426 2475 +f 2427 2426 2476 +f 2477 2427 2476 +f 2428 2427 2477 +f 2478 2428 2477 +f 2429 2428 2478 +f 2479 2429 2478 +f 2430 2429 2479 +f 2480 2430 2479 +f 2431 2430 2480 +f 2481 2431 2480 +f 2432 2431 2481 +f 2482 2432 2481 +f 2433 2432 2482 +f 2483 2433 2482 +f 2434 2433 2483 +f 2484 2434 2483 +f 2435 2434 2484 +f 2485 2435 2484 +f 2436 2435 2485 +f 2486 2436 2485 +f 2437 2436 2486 +f 2487 2437 2486 +f 2438 2437 2487 +f 2488 2438 2487 +f 2439 2438 2488 +f 2489 2439 2488 +f 2440 2439 2489 +f 2490 2440 2489 +f 2441 2440 2490 +f 2491 2441 2490 +f 2442 2441 2491 +f 2492 2442 2491 +f 2443 2442 2492 +f 2493 2443 2492 +f 2444 2443 2493 +f 2494 2444 2493 +f 2445 2444 2494 +f 2495 2445 2494 +f 2446 2445 2495 +f 2496 2446 2495 +f 2447 2446 2496 +f 2497 2447 2496 +f 2448 2447 2497 +f 2498 2448 2497 +f 2449 2448 2498 +f 2499 2449 2498 +f 2450 2449 2499 +f 2500 2450 2499 +f 2501 2503 2502 +f 2504 2505 2506 +f 2501 2505 2504 +f 2505 2501 2502 +f 2502 2506 2505 +f 2506 2502 2503 +f 2503 2504 2506 +f 2504 2503 2501 +f 2507 2509 2508 +f 2510 2511 2512 +f 2507 2511 2510 +f 2511 2507 2508 +f 2508 2512 2511 +f 2512 2508 2509 +f 2509 2510 2512 +f 2510 2509 2507 +f 2513 2515 2514 +f 2516 2517 2518 +f 2513 2517 2516 +f 2517 2513 2514 +f 2514 2518 2517 +f 2518 2514 2515 +f 2515 2516 2518 +f 2516 2515 2513 +f 2519 2521 2520 +f 2522 2523 2524 +f 2519 2523 2522 +f 2523 2519 2520 +f 2520 2524 2523 +f 2524 2520 2521 +f 2521 2522 2524 +f 2522 2521 2519 +f 2525 2527 2526 +f 2528 2529 2530 +f 2525 2529 2528 +f 2529 2525 2526 +f 2526 2530 2529 +f 2530 2526 2527 +f 2527 2528 2530 +f 2528 2527 2525 +f 2531 2533 2532 +f 2534 2535 2536 +f 2531 2535 2534 +f 2535 2531 2532 +f 2532 2536 2535 +f 2536 2532 2533 +f 2533 2534 2536 +f 2534 2533 2531 +f 2537 2539 2538 +f 2540 2541 2542 +f 2537 2541 2540 +f 2541 2537 2538 +f 2538 2542 2541 +f 2542 2538 2539 +f 2539 2540 2542 +f 2540 2539 2537 +f 2543 2545 2544 +f 2546 2547 2548 +f 2543 2547 2546 +f 2547 2543 2544 +f 2544 2548 2547 +f 2548 2544 2545 +f 2545 2546 2548 +f 2546 2545 2543 +f 2549 2551 2550 +f 2552 2553 2554 +f 2549 2553 2552 +f 2553 2549 2550 +f 2550 2554 2553 +f 2554 2550 2551 +f 2551 2552 2554 +f 2552 2551 2549 +f 2555 2557 2556 +f 2558 2559 2560 +f 2555 2559 2558 +f 2559 2555 2556 +f 2556 2560 2559 +f 2560 2556 2557 +f 2557 2558 2560 +f 2558 2557 2555 +f 2561 2563 2562 +f 2564 2565 2566 +f 2561 2565 2564 +f 2565 2561 2562 +f 2562 2566 2565 +f 2566 2562 2563 +f 2563 2564 2566 +f 2564 2563 2561 +f 2567 2569 2568 +f 2570 2571 2572 +f 2567 2571 2570 +f 2571 2567 2568 +f 2568 2572 2571 +f 2572 2568 2569 +f 2569 2570 2572 +f 2570 2569 2567 +f 2573 2575 2574 +f 2576 2577 2578 +f 2573 2577 2576 +f 2577 2573 2574 +f 2574 2578 2577 +f 2578 2574 2575 +f 2575 2576 2578 +f 2576 2575 2573 +f 2579 2581 2580 +f 2582 2583 2584 +f 2579 2583 2582 +f 2583 2579 2580 +f 2580 2584 2583 +f 2584 2580 2581 +f 2581 2582 2584 +f 2582 2581 2579 +f 2585 2587 2586 +f 2588 2589 2590 +f 2585 2589 2588 +f 2589 2585 2586 +f 2586 2590 2589 +f 2590 2586 2587 +f 2587 2588 2590 +f 2588 2587 2585 +f 2591 2593 2592 +f 2594 2595 2596 +f 2591 2595 2594 +f 2595 2591 2592 +f 2592 2596 2595 +f 2596 2592 2593 +f 2593 2594 2596 +f 2594 2593 2591 +f 2597 2599 2598 +f 2600 2601 2602 +f 2597 2601 2600 +f 2601 2597 2598 +f 2598 2602 2601 +f 2602 2598 2599 +f 2599 2600 2602 +f 2600 2599 2597 +f 2603 2605 2604 +f 2606 2607 2608 +f 2603 2607 2606 +f 2607 2603 2604 +f 2604 2608 2607 +f 2608 2604 2605 +f 2605 2606 2608 +f 2606 2605 2603 +f 2609 2611 2610 +f 2612 2613 2614 +f 2609 2613 2612 +f 2613 2609 2610 +f 2610 2614 2613 +f 2614 2610 2611 +f 2611 2612 2614 +f 2612 2611 2609 +f 2615 2617 2616 +f 2618 2619 2620 +f 2615 2619 2618 +f 2619 2615 2616 +f 2616 2620 2619 +f 2620 2616 2617 +f 2617 2618 2620 +f 2618 2617 2615 +f 2621 2623 2622 +f 2624 2625 2626 +f 2621 2625 2624 +f 2625 2621 2622 +f 2622 2626 2625 +f 2626 2622 2623 +f 2623 2624 2626 +f 2624 2623 2621 +f 2627 2629 2628 +f 2630 2631 2632 +f 2627 2631 2630 +f 2631 2627 2628 +f 2628 2632 2631 +f 2632 2628 2629 +f 2629 2630 2632 +f 2630 2629 2627 +f 2633 2635 2634 +f 2636 2637 2638 +f 2633 2637 2636 +f 2637 2633 2634 +f 2634 2638 2637 +f 2638 2634 2635 +f 2635 2636 2638 +f 2636 2635 2633 +f 2639 2641 2640 +f 2642 2643 2644 +f 2639 2643 2642 +f 2643 2639 2640 +f 2640 2644 2643 +f 2644 2640 2641 +f 2641 2642 2644 +f 2642 2641 2639 +f 2645 2647 2646 +f 2648 2649 2650 +f 2645 2649 2648 +f 2649 2645 2646 +f 2646 2650 2649 +f 2650 2646 2647 +f 2647 2648 2650 +f 2648 2647 2645 +f 2651 2653 2652 +f 2654 2655 2656 +f 2651 2655 2654 +f 2655 2651 2652 +f 2652 2656 2655 +f 2656 2652 2653 +f 2653 2654 2656 +f 2654 2653 2651 +f 2657 2659 2658 +f 2660 2661 2662 +f 2657 2661 2660 +f 2661 2657 2658 +f 2658 2662 2661 +f 2662 2658 2659 +f 2659 2660 2662 +f 2660 2659 2657 +f 2663 2665 2664 +f 2666 2667 2668 +f 2663 2667 2666 +f 2667 2663 2664 +f 2664 2668 2667 +f 2668 2664 2665 +f 2665 2666 2668 +f 2666 2665 2663 +f 2669 2671 2670 +f 2672 2673 2674 +f 2669 2673 2672 +f 2673 2669 2670 +f 2670 2674 2673 +f 2674 2670 2671 +f 2671 2672 2674 +f 2672 2671 2669 +f 2675 2677 2676 +f 2678 2679 2680 +f 2675 2679 2678 +f 2679 2675 2676 +f 2676 2680 2679 +f 2680 2676 2677 +f 2677 2678 2680 +f 2678 2677 2675 +f 2681 2683 2682 +f 2684 2685 2686 +f 2681 2685 2684 +f 2685 2681 2682 +f 2682 2686 2685 +f 2686 2682 2683 +f 2683 2684 2686 +f 2684 2683 2681 +f 2687 2689 2688 +f 2690 2691 2692 +f 2687 2691 2690 +f 2691 2687 2688 +f 2688 2692 2691 +f 2692 2688 2689 +f 2689 2690 2692 +f 2690 2689 2687 +f 2693 2695 2694 +f 2696 2697 2698 +f 2693 2697 2696 +f 2697 2693 2694 +f 2694 2698 2697 +f 2698 2694 2695 +f 2695 2696 2698 +f 2696 2695 2693 +f 2699 2701 2700 +f 2702 2703 2704 +f 2699 2703 2702 +f 2703 2699 2700 +f 2700 2704 2703 +f 2704 2700 2701 +f 2701 2702 2704 +f 2702 2701 2699 +f 2705 2707 2706 +f 2708 2709 2710 +f 2705 2709 2708 +f 2709 2705 2706 +f 2706 2710 2709 +f 2710 2706 2707 +f 2707 2708 2710 +f 2708 2707 2705 +f 2711 2713 2712 +f 2714 2715 2716 +f 2711 2715 2714 +f 2715 2711 2712 +f 2712 2716 2715 +f 2716 2712 2713 +f 2713 2714 2716 +f 2714 2713 2711 +f 2717 2719 2718 +f 2720 2721 2722 +f 2717 2721 2720 +f 2721 2717 2718 +f 2718 2722 2721 +f 2722 2718 2719 +f 2719 2720 2722 +f 2720 2719 2717 +f 2723 2725 2724 +f 2726 2727 2728 +f 2723 2727 2726 +f 2727 2723 2724 +f 2724 2728 2727 +f 2728 2724 2725 +f 2725 2726 2728 +f 2726 2725 2723 +f 2729 2731 2730 +f 2732 2733 2734 +f 2729 2733 2732 +f 2733 2729 2730 +f 2730 2734 2733 +f 2734 2730 2731 +f 2731 2732 2734 +f 2732 2731 2729 +f 2735 2737 2736 +f 2738 2739 2740 +f 2735 2739 2738 +f 2739 2735 2736 +f 2736 2740 2739 +f 2740 2736 2737 +f 2737 2738 2740 +f 2738 2737 2735 +f 2741 2743 2742 +f 2744 2745 2746 +f 2741 2745 2744 +f 2745 2741 2742 +f 2742 2746 2745 +f 2746 2742 2743 +f 2743 2744 2746 +f 2744 2743 2741 +f 2747 2749 2748 +f 2750 2751 2752 +f 2747 2751 2750 +f 2751 2747 2748 +f 2748 2752 2751 +f 2752 2748 2749 +f 2749 2750 2752 +f 2750 2749 2747 +f 2753 2755 2754 +f 2756 2757 2758 +f 2753 2757 2756 +f 2757 2753 2754 +f 2754 2758 2757 +f 2758 2754 2755 +f 2755 2756 2758 +f 2756 2755 2753 +f 2759 2761 2760 +f 2762 2763 2764 +f 2759 2763 2762 +f 2763 2759 2760 +f 2760 2764 2763 +f 2764 2760 2761 +f 2761 2762 2764 +f 2762 2761 2759 +f 2765 2767 2766 +f 2768 2769 2770 +f 2765 2769 2768 +f 2769 2765 2766 +f 2766 2770 2769 +f 2770 2766 2767 +f 2767 2768 2770 +f 2768 2767 2765 +f 2771 2773 2772 +f 2774 2775 2776 +f 2771 2775 2774 +f 2775 2771 2772 +f 2772 2776 2775 +f 2776 2772 2773 +f 2773 2774 2776 +f 2774 2773 2771 +f 2777 2779 2778 +f 2780 2781 2782 +f 2777 2781 2780 +f 2781 2777 2778 +f 2778 2782 2781 +f 2782 2778 2779 +f 2779 2780 2782 +f 2780 2779 2777 +f 2783 2785 2784 +f 2786 2787 2788 +f 2783 2787 2786 +f 2787 2783 2784 +f 2784 2788 2787 +f 2788 2784 2785 +f 2785 2786 2788 +f 2786 2785 2783 +f 2789 2791 2790 +f 2792 2793 2794 +f 2789 2793 2792 +f 2793 2789 2790 +f 2790 2794 2793 +f 2794 2790 2791 +f 2791 2792 2794 +f 2792 2791 2789 +f 2795 2797 2796 +f 2798 2799 2800 +f 2795 2799 2798 +f 2799 2795 2796 +f 2796 2800 2799 +f 2800 2796 2797 +f 2797 2798 2800 +f 2798 2797 2795 diff --git a/RecastDemo/CMakeLists.txt b/RecastDemo/CMakeLists.txt new file mode 100644 index 00000000..1e8f9398 --- /dev/null +++ b/RecastDemo/CMakeLists.txt @@ -0,0 +1,87 @@ +file(GLOB SOURCES Source/*.cpp Contrib/fastlz/fastlz.c) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +if(NOT SDL2_ROOT_DIR) + if(WIN32) + set(SDL2_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Contrib/SDL") + elseif(APPLE) + set(SDL2_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Bin") + endif() +endif() + +find_package(OpenGL REQUIRED) +if(APPLE) + find_library(SDL2_LIBRARY + NAMES SDL2 + PATHS ${SDL2_ROOT_DIR} + REQUIRED) +else() + find_package(SDL2 REQUIRED) +endif() + +include_directories(SYSTEM ${OPENGL_INCLUDE_DIR}) +include_directories(SYSTEM Contrib/fastlz) +include_directories(SYSTEM Contrib) +include_directories(../DebugUtils/Include) +include_directories(../Detour/Include) +include_directories(../DetourCrowd/Include) +include_directories(../DetourTileCache/Include) +include_directories(../Recast/Include) +include_directories(Include) +include_directories(Contrib/SDL/include) +if(APPLE) + include_directories(${SDL2_LIBRARY}/Headers) +endif() + +if (WIN32) + add_executable(RecastDemo WIN32 ${SOURCES}) +elseif(APPLE) + add_executable(RecastDemo MACOSX_BUNDLE ${SOURCES}) +else() + add_executable(RecastDemo ${SOURCES}) +endif() + +if( WIN32 ) + if ( "${CMAKE_MAKE_PROGRAM}" MATCHES "MSBuild" ) + add_custom_command(TARGET RecastDemo + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${SDL2_RUNTIME_LIBRARY}" ${CMAKE_BINARY_DIR}/RecastDemo/$(ConfigurationName)/ + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/Bin/Meshes ${CMAKE_BINARY_DIR}/RecastDemo/$(ConfigurationName)/Meshes + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/Bin/TestCases ${CMAKE_BINARY_DIR}/RecastDemo/$(ConfigurationName)/TestCases + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/Bin/DroidSans.ttf ${CMAKE_BINARY_DIR}/RecastDemo/$(ConfigurationName)/ + ) + elseif ( MINGW ) + add_custom_command(TARGET RecastDemo + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${SDL2_RUNTIME_LIBRARY}" ${CMAKE_BINARY_DIR}/RecastDemo/ + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/Bin/Meshes ${CMAKE_BINARY_DIR}/RecastDemo/Meshes + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/Bin/TestCases ${CMAKE_BINARY_DIR}/RecastDemo/TestCases + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/Bin/DroidSans.ttf ${CMAKE_BINARY_DIR}/RecastDemo/ + ) + endif() +else() + file(COPY Bin/Meshes DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + file(COPY Bin/TestCases DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + file(COPY Bin/DroidSans.ttf DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +endif() + + +add_dependencies(RecastDemo DebugUtils Detour DetourCrowd DetourTileCache Recast) +if(APPLE) + target_link_libraries(RecastDemo ${OPENGL_LIBRARIES} ${SDL2_LIBRARY} DebugUtils Detour DetourCrowd DetourTileCache Recast) +else() + target_link_libraries(RecastDemo ${OPENGL_LIBRARIES} SDL2::SDL2main DebugUtils Detour DetourCrowd DetourTileCache Recast) +endif() + + +install(TARGETS RecastDemo + RUNTIME DESTINATION bin + BUNDLE DESTINATION bin) +install(DIRECTORY Bin/Meshes DESTINATION bin) +install(DIRECTORY Bin/TestCases DESTINATION bin) +install(FILES Bin/DroidSans.ttf DESTINATION bin) + +if (WIN32) + install(FILES "${SDL2_RUNTIME_LIBRARY}" DESTINATION bin) +endif() \ No newline at end of file diff --git a/RecastDemo/Contrib/fastlz/fastlz.c b/RecastDemo/Contrib/fastlz/fastlz.c index 3c9d6f6f..645d2947 100644 --- a/RecastDemo/Contrib/fastlz/fastlz.c +++ b/RecastDemo/Contrib/fastlz/fastlz.c @@ -81,6 +81,11 @@ typedef unsigned char flzuint8; typedef unsigned short flzuint16; typedef unsigned int flzuint32; +/* Disable "conversion from A to B, possible loss of data" warning when using MSVC */ +#if defined(_MSC_VER) +#pragma warning(disable: 4244) +#endif + /* prototypes */ int fastlz_compress(const void* input, int length, void* output); int fastlz_compress_level(int level, const void* input, int length, void* output); diff --git a/RecastDemo/Include/ChunkyTriMesh.h b/RecastDemo/Include/ChunkyTriMesh.h index 65849799..0870d64e 100644 --- a/RecastDemo/Include/ChunkyTriMesh.h +++ b/RecastDemo/Include/ChunkyTriMesh.h @@ -29,7 +29,7 @@ struct rcChunkyTriMeshNode struct rcChunkyTriMesh { - inline rcChunkyTriMesh() : nodes(0), nnodes(0), tris(0), ntris(0), maxTrisPerChunk(0) {}; + inline rcChunkyTriMesh() : nodes(0), nnodes(0), tris(0), ntris(0), maxTrisPerChunk(0) {} inline ~rcChunkyTriMesh() { delete [] nodes; delete [] tris; } rcChunkyTriMeshNode* nodes; diff --git a/RecastDemo/Include/CrowdTool.h b/RecastDemo/Include/CrowdTool.h index 8c170ef4..a2980660 100644 --- a/RecastDemo/Include/CrowdTool.h +++ b/RecastDemo/Include/CrowdTool.h @@ -122,7 +122,7 @@ class CrowdTool : public SampleTool TOOLMODE_CREATE, TOOLMODE_MOVE_TARGET, TOOLMODE_SELECT, - TOOLMODE_TOGGLE_POLYS, + TOOLMODE_TOGGLE_POLYS }; ToolMode m_mode; diff --git a/RecastDemo/Include/NavMeshTesterTool.h b/RecastDemo/Include/NavMeshTesterTool.h index 01f46808..080170ea 100644 --- a/RecastDemo/Include/NavMeshTesterTool.h +++ b/RecastDemo/Include/NavMeshTesterTool.h @@ -43,7 +43,7 @@ class NavMeshTesterTool : public SampleTool TOOLMODE_DISTANCE_TO_WALL, TOOLMODE_FIND_POLYS_IN_CIRCLE, TOOLMODE_FIND_POLYS_IN_SHAPE, - TOOLMODE_FIND_LOCAL_NEIGHBOURHOOD, + TOOLMODE_FIND_LOCAL_NEIGHBOURHOOD }; ToolMode m_toolMode; @@ -110,4 +110,5 @@ public: void drawAgent(const float* pos, float r, float h, float c, const unsigned int col); }; -#endif // NAVMESHTESTERTOOL_H \ No newline at end of file +#endif // NAVMESHTESTERTOOL_H + diff --git a/RecastDemo/Include/PerfTimer.h b/RecastDemo/Include/PerfTimer.h index ed62c180..ce0f296f 100644 --- a/RecastDemo/Include/PerfTimer.h +++ b/RecastDemo/Include/PerfTimer.h @@ -29,4 +29,5 @@ typedef __int64 TimeVal; TimeVal getPerfTime(); int getPerfTimeUsec(const TimeVal duration); -#endif // PERFTIMER_H \ No newline at end of file +#endif // PERFTIMER_H + diff --git a/RecastDemo/Include/Sample.h b/RecastDemo/Include/Sample.h index bc1a257b..f54ad817 100644 --- a/RecastDemo/Include/Sample.h +++ b/RecastDemo/Include/Sample.h @@ -78,12 +78,12 @@ enum SamplePartitionType { SAMPLE_PARTITION_WATERSHED, SAMPLE_PARTITION_MONOTONE, - SAMPLE_PARTITION_LAYERS, + SAMPLE_PARTITION_LAYERS }; struct SampleTool { - virtual ~SampleTool() {} + virtual ~SampleTool(); virtual int type() = 0; virtual void init(class Sample* sample) = 0; virtual void reset() = 0; @@ -97,7 +97,7 @@ struct SampleTool }; struct SampleToolState { - virtual ~SampleToolState() {} + virtual ~SampleToolState(); virtual void init(class Sample* sample) = 0; virtual void reset() = 0; virtual void handleRender() = 0; @@ -141,6 +141,9 @@ protected: SampleDebugDraw m_dd; + dtNavMesh* loadAll(const char* path); + void saveAll(const char* path, const dtNavMesh* mesh); + public: Sample(); virtual ~Sample(); diff --git a/RecastDemo/Include/Sample_Debug.h b/RecastDemo/Include/Sample_Debug.h index 7340355c..129e4bc6 100644 --- a/RecastDemo/Include/Sample_Debug.h +++ b/RecastDemo/Include/Sample_Debug.h @@ -31,7 +31,7 @@ protected: rcContourSet* m_cset; rcPolyMesh* m_pmesh; - float m_ext[3]; + float m_halfExtents[3]; float m_center[3]; float m_bmin[3], m_bmax[3]; dtPolyRef m_ref; diff --git a/RecastDemo/Include/Sample_SoloMesh.h b/RecastDemo/Include/Sample_SoloMesh.h index 70afb11a..a278529e 100644 --- a/RecastDemo/Include/Sample_SoloMesh.h +++ b/RecastDemo/Include/Sample_SoloMesh.h @@ -62,7 +62,7 @@ protected: DrawMode m_drawMode; void cleanup(); - + public: Sample_SoloMesh(); virtual ~Sample_SoloMesh(); diff --git a/RecastDemo/Include/Sample_TempObstacles.h b/RecastDemo/Include/Sample_TempObstacles.h index b9ce4a92..3ba41e14 100644 --- a/RecastDemo/Include/Sample_TempObstacles.h +++ b/RecastDemo/Include/Sample_TempObstacles.h @@ -40,7 +40,7 @@ protected: int m_cacheCompressedSize; int m_cacheRawSize; int m_cacheLayerCount; - int m_cacheBuildMemUsage; + unsigned int m_cacheBuildMemUsage; enum DrawMode { diff --git a/RecastDemo/Include/TestCase.h b/RecastDemo/Include/TestCase.h index f61cc856..8991e576 100644 --- a/RecastDemo/Include/TestCase.h +++ b/RecastDemo/Include/TestCase.h @@ -27,13 +27,17 @@ class TestCase enum TestType { TEST_PATHFIND, - TEST_RAYCAST, + TEST_RAYCAST }; struct Test { Test() : type(), + spos(), + epos(), + nspos(), + nepos(), radius(0), includeFlags(0), excludeFlags(0), @@ -107,4 +111,4 @@ private: TestCase& operator=(const TestCase&); }; -#endif // TESTCASE_H \ No newline at end of file +#endif // TESTCASE_H diff --git a/RecastDemo/Include/ValueHistory.h b/RecastDemo/Include/ValueHistory.h index 2da53902..1e83a211 100644 --- a/RecastDemo/Include/ValueHistory.h +++ b/RecastDemo/Include/ValueHistory.h @@ -47,4 +47,5 @@ void drawGraph(const GraphParams* p, const ValueHistory* graph, int idx, const char* label, const unsigned int col); -#endif // VALUEHISTORY_H \ No newline at end of file +#endif // VALUEHISTORY_H + diff --git a/RecastDemo/Include/imgui.h b/RecastDemo/Include/imgui.h index 325829b3..3d7346ef 100644 --- a/RecastDemo/Include/imgui.h +++ b/RecastDemo/Include/imgui.h @@ -22,14 +22,14 @@ enum imguiMouseButton { IMGUI_MBUT_LEFT = 0x01, - IMGUI_MBUT_RIGHT = 0x02, + IMGUI_MBUT_RIGHT = 0x02 }; enum imguiTextAlign { IMGUI_ALIGN_LEFT, IMGUI_ALIGN_CENTER, - IMGUI_ALIGN_RIGHT, + IMGUI_ALIGN_RIGHT }; inline unsigned int imguiRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a=255) @@ -68,7 +68,7 @@ enum imguiGfxCmdType IMGUI_GFXCMD_TRIANGLE, IMGUI_GFXCMD_LINE, IMGUI_GFXCMD_TEXT, - IMGUI_GFXCMD_SCISSOR, + IMGUI_GFXCMD_SCISSOR }; struct imguiGfxRect diff --git a/RecastDemo/Include/imguiRenderGL.h b/RecastDemo/Include/imguiRenderGL.h index b1483416..33cd017c 100644 --- a/RecastDemo/Include/imguiRenderGL.h +++ b/RecastDemo/Include/imguiRenderGL.h @@ -23,4 +23,5 @@ bool imguiRenderGLInit(const char* fontpath); void imguiRenderGLDestroy(); void imguiRenderGLDraw(); -#endif // IMGUI_RENDER_GL_H \ No newline at end of file +#endif // IMGUI_RENDER_GL_H + diff --git a/RecastDemo/Source/ChunkyTriMesh.cpp b/RecastDemo/Source/ChunkyTriMesh.cpp index 2991d4ff..242bf285 100644 --- a/RecastDemo/Source/ChunkyTriMesh.cpp +++ b/RecastDemo/Source/ChunkyTriMesh.cpp @@ -83,7 +83,7 @@ static void subdivide(BoundsItem* items, int nitems, int imin, int imax, int tri int inum = imax - imin; int icur = curNode; - if (curNode > maxNodes) + if (curNode >= maxNodes) return; rcChunkyTriMeshNode& node = nodes[curNode++]; diff --git a/RecastDemo/Source/ConvexVolumeTool.cpp b/RecastDemo/Source/ConvexVolumeTool.cpp index 117171d0..43be1421 100644 --- a/RecastDemo/Source/ConvexVolumeTool.cpp +++ b/RecastDemo/Source/ConvexVolumeTool.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -254,7 +253,7 @@ void ConvexVolumeTool::handleRender() { duDebugDraw& dd = m_sample->getDebugDraw(); - // Find height extents of the shape. + // Find height extent of the shape. float minh = FLT_MAX, maxh = 0; for (int i = 0; i < m_npts; ++i) minh = rcMin(minh, m_pts[i*3+1]); diff --git a/RecastDemo/Source/CrowdTool.cpp b/RecastDemo/Source/CrowdTool.cpp index c7a8f1f2..7f9f7843 100644 --- a/RecastDemo/Source/CrowdTool.cpp +++ b/RecastDemo/Source/CrowdTool.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -144,8 +143,6 @@ void CrowdToolState::init(class Sample* sample) if (m_sample != sample) { m_sample = sample; -// m_oldFlags = m_sample->getNavMeshDrawFlags(); -// m_sample->setNavMeshDrawFlags(m_oldFlags & ~DU_DRAWNAVMESH_CLOSEDLIST); } dtNavMesh* nav = m_sample->getNavMesh(); @@ -712,7 +709,7 @@ void CrowdToolState::setMoveTarget(const float* p, bool adjust) dtNavMeshQuery* navquery = m_sample->getNavMeshQuery(); dtCrowd* crowd = m_sample->getCrowd(); const dtQueryFilter* filter = crowd->getFilter(0); - const float* ext = crowd->getQueryExtents(); + const float* halfExtents = crowd->getQueryExtents(); if (adjust) { @@ -740,7 +737,7 @@ void CrowdToolState::setMoveTarget(const float* p, bool adjust) } else { - navquery->findNearestPoly(p, ext, filter, &m_targetRef, m_targetPos); + navquery->findNearestPoly(p, halfExtents, filter, &m_targetRef, m_targetPos); if (m_agentDebug.idx != -1) { @@ -1032,10 +1029,10 @@ void CrowdTool::handleClick(const float* s, const float* p, bool shift) if (nav && navquery) { dtQueryFilter filter; - const float* ext = crowd->getQueryExtents(); + const float* halfExtents = crowd->getQueryExtents(); float tgt[3]; dtPolyRef ref; - navquery->findNearestPoly(p, ext, &filter, &ref, tgt); + navquery->findNearestPoly(p, halfExtents, &filter, &ref, tgt); if (ref) { unsigned short flags = 0; diff --git a/RecastDemo/Source/Filelist.cpp b/RecastDemo/Source/Filelist.cpp index 8a8b7c9e..bd715c47 100644 --- a/RecastDemo/Source/Filelist.cpp +++ b/RecastDemo/Source/Filelist.cpp @@ -55,10 +55,11 @@ void scanDirectoryAppend(const string& path, const string& ext, vector& return; } + size_t extLen = strlen(ext.c_str()); while ((current = readdir(dp)) != 0) { - int len = strlen(current->d_name); - if (len > 4 && strncmp(current->d_name + len - 4, ext.c_str(), 4) == 0) + size_t len = strlen(current->d_name); + if (len > extLen && strncmp(current->d_name + len - extLen, ext.c_str(), extLen) == 0) { filelist.push_back(current->d_name); } @@ -74,4 +75,4 @@ void scanDirectory(const string& path, const string& ext, vector& fileli { filelist.clear(); scanDirectoryAppend(path, ext, filelist); -} \ No newline at end of file +} diff --git a/RecastDemo/Source/InputGeom.cpp b/RecastDemo/Source/InputGeom.cpp index 33672b67..ba5d6596 100644 --- a/RecastDemo/Source/InputGeom.cpp +++ b/RecastDemo/Source/InputGeom.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -92,6 +91,7 @@ static char* parseRow(char* buf, char* bufEnd, char* row, int len) case '\t': case ' ': if (start) break; + // else falls through default: start = false; row[n++] = c; @@ -423,9 +423,6 @@ static bool isectSegAABB(const float* sp, const float* sq, bool InputGeom::raycastMesh(float* src, float* dst, float& tmin) { - float dir[3]; - rcVsub(dir, dst, src); - // Prune hit ray. float btmin, btmax; if (!isectSegAABB(src, dst, m_meshBMin, m_meshBMax, btmin, btmax)) diff --git a/RecastDemo/Source/MeshLoaderObj.cpp b/RecastDemo/Source/MeshLoaderObj.cpp index 8b44127d..21959ced 100644 --- a/RecastDemo/Source/MeshLoaderObj.cpp +++ b/RecastDemo/Source/MeshLoaderObj.cpp @@ -20,7 +20,6 @@ #include #include #include -#define _USE_MATH_DEFINES #include rcMeshLoaderObj::rcMeshLoaderObj() : @@ -99,6 +98,7 @@ static char* parseRow(char* buf, char* bufEnd, char* row, int len) case '\t': case ' ': if (start) break; + // else falls through default: start = false; row[n++] = c; diff --git a/RecastDemo/Source/NavMeshPruneTool.cpp b/RecastDemo/Source/NavMeshPruneTool.cpp index 95656d5c..75b7e1fd 100644 --- a/RecastDemo/Source/NavMeshPruneTool.cpp +++ b/RecastDemo/Source/NavMeshPruneTool.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -252,10 +251,10 @@ void NavMeshPruneTool::handleClick(const float* s, const float* p, bool shift) m_flags->init(nav); } - const float ext[3] = {2,4,2}; + const float halfExtents[3] = { 2, 4, 2 }; dtQueryFilter filter; dtPolyRef ref = 0; - query->findNearestPoly(p, ext, &filter, &ref, 0); + query->findNearestPoly(p, halfExtents, &filter, &ref, 0); floodNavmesh(nav, m_flags, ref, 1); } diff --git a/RecastDemo/Source/NavMeshTesterTool.cpp b/RecastDemo/Source/NavMeshTesterTool.cpp index 031f9ae3..14217b33 100644 --- a/RecastDemo/Source/NavMeshTesterTool.cpp +++ b/RecastDemo/Source/NavMeshTesterTool.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -45,7 +44,7 @@ // Uncomment this to dump all the requests in stdout. #define DUMP_REQS -// Returns a random number [0..1) +// Returns a random number [0..1] static float frand() { // return ((float)(rand() & 0xffff)/(float)0xffff); diff --git a/RecastDemo/Source/OffMeshConnectionTool.cpp b/RecastDemo/Source/OffMeshConnectionTool.cpp index 5efda624..17d272f0 100644 --- a/RecastDemo/Source/OffMeshConnectionTool.cpp +++ b/RecastDemo/Source/OffMeshConnectionTool.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include diff --git a/RecastDemo/Source/Sample.cpp b/RecastDemo/Source/Sample.cpp index 45b4275a..7dbfb379 100644 --- a/RecastDemo/Source/Sample.cpp +++ b/RecastDemo/Source/Sample.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include "Sample.h" @@ -35,6 +34,16 @@ # define snprintf _snprintf #endif +SampleTool::~SampleTool() +{ + // Defined out of line to fix the weak v-tables warning +} + +SampleToolState::~SampleToolState() +{ + // Defined out of line to fix the weak v-tables warning +} + unsigned short sampleAreaToFlags(unsigned char area) { unsigned char areaType = (area & SAMPLE_POLYAREA_TYPE_MASK); @@ -350,3 +359,128 @@ void Sample::renderOverlayToolStates(double* proj, double* model, int* view) } } +static const int NAVMESHSET_MAGIC = 'M'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'MSET'; +static const int NAVMESHSET_VERSION = 1; + +struct NavMeshSetHeader +{ + int magic; + int version; + int numTiles; + dtNavMeshParams params; +}; + +struct NavMeshTileHeader +{ + dtTileRef tileRef; + int dataSize; +}; + +dtNavMesh* Sample::loadAll(const char* path) +{ + FILE* fp = fopen(path, "rb"); + if (!fp) return 0; + + // Read header. + NavMeshSetHeader header; + size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp); + if (readLen != 1) + { + fclose(fp); + return 0; + } + if (header.magic != NAVMESHSET_MAGIC) + { + fclose(fp); + return 0; + } + if (header.version != NAVMESHSET_VERSION) + { + fclose(fp); + return 0; + } + + dtNavMesh* mesh = dtAllocNavMesh(); + if (!mesh) + { + fclose(fp); + return 0; + } + dtStatus status = mesh->init(&header.params); + if (dtStatusFailed(status)) + { + fclose(fp); + return 0; + } + + // Read tiles. + for (int i = 0; i < header.numTiles; ++i) + { + NavMeshTileHeader tileHeader; + readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp); + if (readLen != 1) + { + fclose(fp); + return 0; + } + + if (!tileHeader.tileRef || !tileHeader.dataSize) + break; + + unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM); + if (!data) break; + memset(data, 0, tileHeader.dataSize); + readLen = fread(data, tileHeader.dataSize, 1, fp); + if (readLen != 1) + { + dtFree(data); + fclose(fp); + return 0; + } + + mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0); + } + + fclose(fp); + + return mesh; +} + +void Sample::saveAll(const char* path, const dtNavMesh* mesh) +{ + if (!mesh) return; + + FILE* fp = fopen(path, "wb"); + if (!fp) + return; + + // Store header. + NavMeshSetHeader header; + header.magic = NAVMESHSET_MAGIC; + header.version = NAVMESHSET_VERSION; + header.numTiles = 0; + for (int i = 0; i < mesh->getMaxTiles(); ++i) + { + const dtMeshTile* tile = mesh->getTile(i); + if (!tile || !tile->header || !tile->dataSize) continue; + header.numTiles++; + } + memcpy(&header.params, mesh->getParams(), sizeof(dtNavMeshParams)); + fwrite(&header, sizeof(NavMeshSetHeader), 1, fp); + + // Store tiles. + for (int i = 0; i < mesh->getMaxTiles(); ++i) + { + const dtMeshTile* tile = mesh->getTile(i); + if (!tile || !tile->header || !tile->dataSize) continue; + + NavMeshTileHeader tileHeader; + tileHeader.tileRef = mesh->getTileRef(tile); + tileHeader.dataSize = tile->dataSize; + fwrite(&tileHeader, sizeof(tileHeader), 1, fp); + + fwrite(tile->data, tile->dataSize, 1, fp); + } + + fclose(fp); +} diff --git a/RecastDemo/Source/SampleInterfaces.cpp b/RecastDemo/Source/SampleInterfaces.cpp index 44b306b0..24f6ea12 100644 --- a/RecastDemo/Source/SampleInterfaces.cpp +++ b/RecastDemo/Source/SampleInterfaces.cpp @@ -1,4 +1,3 @@ -#define _USE_MATH_DEFINES #include #include #include @@ -68,7 +67,7 @@ void BuildContext::doStartTimer(const rcTimerLabel label) void BuildContext::doStopTimer(const rcTimerLabel label) { const TimeVal endTime = getPerfTime(); - const int deltaTime = (int)(endTime - m_startTime[label]); + const TimeVal deltaTime = endTime - m_startTime[label]; if (m_accTime[label] == -1) m_accTime[label] = deltaTime; else @@ -184,7 +183,7 @@ public: } } }; -GLCheckerTexture g_tex; +static GLCheckerTexture g_tex; void DebugDrawGL::depthMask(bool state) diff --git a/RecastDemo/Source/Sample_Debug.cpp b/RecastDemo/Source/Sample_Debug.cpp index a8b8e2f5..6dc6c141 100644 --- a/RecastDemo/Source/Sample_Debug.cpp +++ b/RecastDemo/Source/Sample_Debug.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include "Sample_Debug.h" @@ -124,12 +123,12 @@ Sample_Debug::Sample_Debug() : m_navMesh->addTileAt(-14,-14, data, dataSize, true); } - const float ext[3] = {40,100,40}; + const float halfExtents[3] = {40,100,40}; const float center[3] = { -1667.9491f, 135.52649f, -1680.6149f }; dtQueryFilter filter; - m_ref = m_navMesh->findNearestPoly(center, ext, &filter, 0); + m_ref = m_navMesh->findNearestPoly(center, halfExtents, &filter, 0); - vcopy(m_ext, ext); + vcopy(m_halfExtents, halfExtents); vcopy(m_center, center);*/ @@ -204,8 +203,8 @@ void Sample_Debug::handleRender() duDebugDrawNavMeshPoly(&m_dd, *m_navMesh, m_ref, duRGBA(255,0,0,128)); /* float bmin[3], bmax[3]; - rcVsub(bmin, m_center, m_ext); - rcVadd(bmax, m_center, m_ext); + rcVsub(bmin, m_center, m_halfExtents); + rcVadd(bmax, m_center, m_halfExtents); duDebugDrawBoxWire(&dd, bmin[0],bmin[1],bmin[2], bmax[0],bmax[1],bmax[2], duRGBA(255,255,255,128), 1.0f); duDebugDrawCross(&dd, m_center[0], m_center[1], m_center[2], 1.0f, duRGBA(255,255,255,128), 2.0f);*/ diff --git a/RecastDemo/Source/Sample_SoloMesh.cpp b/RecastDemo/Source/Sample_SoloMesh.cpp index 90b65807..34b6eed7 100644 --- a/RecastDemo/Source/Sample_SoloMesh.cpp +++ b/RecastDemo/Source/Sample_SoloMesh.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -79,20 +78,38 @@ void Sample_SoloMesh::cleanup() dtFreeNavMesh(m_navMesh); m_navMesh = 0; } - + void Sample_SoloMesh::handleSettings() { Sample::handleCommonSettings(); - + if (imguiCheck("Keep Itermediate Results", m_keepInterResults)) m_keepInterResults = !m_keepInterResults; imguiSeparator(); + + imguiIndent(); + imguiIndent(); + + if (imguiButton("Save")) + { + Sample::saveAll("solo_navmesh.bin", m_navMesh); + } + + if (imguiButton("Load")) + { + dtFreeNavMesh(m_navMesh); + m_navMesh = Sample::loadAll("solo_navmesh.bin"); + m_navQuery->init(m_navMesh, 2048); + } + + imguiUnindent(); + imguiUnindent(); char msg[64]; snprintf(msg, 64, "Build Time: %.1fms", m_totalBuildTimeMs); imguiLabel(msg); - + imguiSeparator(); } diff --git a/RecastDemo/Source/Sample_TempObstacles.cpp b/RecastDemo/Source/Sample_TempObstacles.cpp index d306c731..f2ef16f0 100644 --- a/RecastDemo/Source/Sample_TempObstacles.cpp +++ b/RecastDemo/Source/Sample_TempObstacles.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -109,6 +108,8 @@ static int calcLayerBufferSize(const int gridWidth, const int gridHeight) struct FastLZCompressor : public dtTileCacheCompressor { + virtual ~FastLZCompressor(); + virtual int maxCompressedSize(const int bufferSize) { return (int)(bufferSize* 1.05f); @@ -129,6 +130,11 @@ struct FastLZCompressor : public dtTileCacheCompressor } }; +FastLZCompressor::~FastLZCompressor() +{ + // Defined out of line to fix the weak v-tables warning +} + struct LinearAllocator : public dtTileCacheAlloc { unsigned char* buffer; @@ -141,10 +147,7 @@ struct LinearAllocator : public dtTileCacheAlloc resize(cap); } - ~LinearAllocator() - { - dtFree(buffer); - } + virtual ~LinearAllocator(); void resize(const size_t cap) { @@ -176,6 +179,12 @@ struct LinearAllocator : public dtTileCacheAlloc } }; +LinearAllocator::~LinearAllocator() +{ + // Defined out of line to fix the weak v-tables warning + dtFree(buffer); +} + struct MeshProcess : public dtTileCacheMeshProcess { InputGeom* m_geom; @@ -184,6 +193,8 @@ struct MeshProcess : public dtTileCacheMeshProcess { } + virtual ~MeshProcess(); + inline void init(InputGeom* geom) { m_geom = geom; @@ -212,8 +223,10 @@ struct MeshProcess : public dtTileCacheMeshProcess } }; - - +MeshProcess::~MeshProcess() +{ + // Defined out of line to fix the weak v-tables warning +} static const int MAX_LAYERS = 32; @@ -479,7 +492,7 @@ enum DrawDetailType DRAWDETAIL_AREAS, DRAWDETAIL_REGIONS, DRAWDETAIL_CONTOURS, - DRAWDETAIL_MESH, + DRAWDETAIL_MESH }; void drawDetail(duDebugDraw* dd, dtTileCache* tc, const int tx, const int ty, int type) @@ -652,9 +665,6 @@ void drawObstacles(duDebugDraw* dd, const dtTileCache* tc) } } - - - class TempObstacleHilightTool : public SampleTool { Sample_TempObstacles* m_sample; @@ -672,9 +682,7 @@ public: m_hitPos[0] = m_hitPos[1] = m_hitPos[2] = 0; } - virtual ~TempObstacleHilightTool() - { - } + virtual ~TempObstacleHilightTool(); virtual int type() { return TOOL_TILE_HIGHLIGHT; } @@ -749,6 +757,10 @@ public: } }; +TempObstacleHilightTool::~TempObstacleHilightTool() +{ + // Defined out of line to fix the weak v-tables warning +} class TempObstacleCreateTool : public SampleTool { @@ -760,9 +772,7 @@ public: { } - virtual ~TempObstacleCreateTool() - { - } + virtual ~TempObstacleCreateTool(); virtual int type() { return TOOL_TEMP_OBSTACLE; } @@ -804,9 +814,10 @@ public: virtual void handleRenderOverlay(double* /*proj*/, double* /*model*/, int* /*view*/) { } }; - - - +TempObstacleCreateTool::~TempObstacleCreateTool() +{ + // Defined out of line to fix the weak v-tables warning +} Sample_TempObstacles::Sample_TempObstacles() : m_keepInterResults(false), @@ -1324,7 +1335,7 @@ bool Sample_TempObstacles::handleBuild() m_ctx->stopTimer(RC_TIMER_TOTAL); m_cacheBuildTimeMs = m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; - m_cacheBuildMemUsage = m_talloc->high; + m_cacheBuildMemUsage = static_cast(m_talloc->high); const dtNavMesh* nav = m_navMesh; diff --git a/RecastDemo/Source/Sample_TileMesh.cpp b/RecastDemo/Source/Sample_TileMesh.cpp index c45fc70b..a8f91f4a 100644 --- a/RecastDemo/Source/Sample_TileMesh.cpp +++ b/RecastDemo/Source/Sample_TileMesh.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include @@ -87,9 +86,7 @@ public: m_hitPos[0] = m_hitPos[1] = m_hitPos[2] = 0; } - virtual ~NavMeshTileTool() - { - } + virtual ~NavMeshTileTool(); virtual int type() { return TOOL_TILE_EDIT; } @@ -172,8 +169,10 @@ public: } }; - - +NavMeshTileTool::~NavMeshTileTool() +{ + // Defined out of line to fix the weak v-tables warning +} Sample_TileMesh::Sample_TileMesh() : m_keepInterResults(false), @@ -224,132 +223,6 @@ void Sample_TileMesh::cleanup() m_dmesh = 0; } - -static const int NAVMESHSET_MAGIC = 'M'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'MSET'; -static const int NAVMESHSET_VERSION = 1; - -struct NavMeshSetHeader -{ - int magic; - int version; - int numTiles; - dtNavMeshParams params; -}; - -struct NavMeshTileHeader -{ - dtTileRef tileRef; - int dataSize; -}; - -void Sample_TileMesh::saveAll(const char* path, const dtNavMesh* mesh) -{ - if (!mesh) return; - - FILE* fp = fopen(path, "wb"); - if (!fp) - return; - - // Store header. - NavMeshSetHeader header; - header.magic = NAVMESHSET_MAGIC; - header.version = NAVMESHSET_VERSION; - header.numTiles = 0; - for (int i = 0; i < mesh->getMaxTiles(); ++i) - { - const dtMeshTile* tile = mesh->getTile(i); - if (!tile || !tile->header || !tile->dataSize) continue; - header.numTiles++; - } - memcpy(&header.params, mesh->getParams(), sizeof(dtNavMeshParams)); - fwrite(&header, sizeof(NavMeshSetHeader), 1, fp); - - // Store tiles. - for (int i = 0; i < mesh->getMaxTiles(); ++i) - { - const dtMeshTile* tile = mesh->getTile(i); - if (!tile || !tile->header || !tile->dataSize) continue; - - NavMeshTileHeader tileHeader; - tileHeader.tileRef = mesh->getTileRef(tile); - tileHeader.dataSize = tile->dataSize; - fwrite(&tileHeader, sizeof(tileHeader), 1, fp); - - fwrite(tile->data, tile->dataSize, 1, fp); - } - - fclose(fp); -} - -dtNavMesh* Sample_TileMesh::loadAll(const char* path) -{ - FILE* fp = fopen(path, "rb"); - if (!fp) return 0; - - // Read header. - NavMeshSetHeader header; - size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp); - if (readLen != 1) - { - fclose(fp); - return 0; - } - if (header.magic != NAVMESHSET_MAGIC) - { - fclose(fp); - return 0; - } - if (header.version != NAVMESHSET_VERSION) - { - fclose(fp); - return 0; - } - - dtNavMesh* mesh = dtAllocNavMesh(); - if (!mesh) - { - fclose(fp); - return 0; - } - dtStatus status = mesh->init(&header.params); - if (dtStatusFailed(status)) - { - fclose(fp); - return 0; - } - - // Read tiles. - for (int i = 0; i < header.numTiles; ++i) - { - NavMeshTileHeader tileHeader; - readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp); - if (readLen != 1) - { - fclose(fp); - return 0; - } - - if (!tileHeader.tileRef || !tileHeader.dataSize) - break; - - unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM); - if (!data) break; - memset(data, 0, tileHeader.dataSize); - readLen = fread(data, tileHeader.dataSize, 1, fp); - if (readLen != 1) - { - fclose(fp); - return 0; - } - - mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0); - } - - fclose(fp); - - return mesh; -} - void Sample_TileMesh::handleSettings() { Sample::handleCommonSettings(); @@ -401,13 +274,13 @@ void Sample_TileMesh::handleSettings() if (imguiButton("Save")) { - saveAll("all_tiles_navmesh.bin", m_navMesh); + Sample::saveAll("all_tiles_navmesh.bin", m_navMesh); } if (imguiButton("Load")) { dtFreeNavMesh(m_navMesh); - m_navMesh = loadAll("all_tiles_navmesh.bin"); + m_navMesh = Sample::loadAll("all_tiles_navmesh.bin"); m_navQuery->init(m_navMesh, 2048); } diff --git a/RecastDemo/Source/TestCase.cpp b/RecastDemo/Source/TestCase.cpp index aecf9a89..5ce42623 100644 --- a/RecastDemo/Source/TestCase.cpp +++ b/RecastDemo/Source/TestCase.cpp @@ -76,6 +76,7 @@ static char* parseRow(char* buf, char* bufEnd, char* row, int len) case '\t': case ' ': if (start) break; + // else falls through default: start = false; row[n++] = c; @@ -153,8 +154,7 @@ bool TestCase::load(const std::string& filePath) else if (row[0] == 'p' && row[1] == 'f') { // Pathfind test. - Test* test = new Test; - memset(test, 0, sizeof(Test)); + Test* test = new Test(); test->type = TEST_PATHFIND; test->expand = false; test->next = m_tests; @@ -167,8 +167,7 @@ bool TestCase::load(const std::string& filePath) else if (row[0] == 'r' && row[1] == 'c') { // Pathfind test. - Test* test = new Test; - memset(test, 0, sizeof(Test)); + Test* test = new Test(); test->type = TEST_RAYCAST; test->expand = false; test->next = m_tests; diff --git a/RecastDemo/Source/imgui.cpp b/RecastDemo/Source/imgui.cpp index 89fcc19b..84022012 100644 --- a/RecastDemo/Source/imgui.cpp +++ b/RecastDemo/Source/imgui.cpp @@ -18,7 +18,6 @@ #include #include -#define _USE_MATH_DEFINES #include #include "imgui.h" @@ -33,7 +32,7 @@ static char g_textPool[TEXT_POOL_SIZE]; static unsigned g_textPoolSize = 0; static const char* allocText(const char* text) { - unsigned len = strlen(text)+1; + unsigned len = static_cast(strlen(text)+1); if (g_textPoolSize + len >= TEXT_POOL_SIZE) return 0; char* dst = &g_textPool[g_textPoolSize]; diff --git a/RecastDemo/Source/imguiRenderGL.cpp b/RecastDemo/Source/imguiRenderGL.cpp index 9f053759..f81b13e7 100644 --- a/RecastDemo/Source/imguiRenderGL.cpp +++ b/RecastDemo/Source/imguiRenderGL.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#define _USE_MATH_DEFINES #include #include #include "imgui.h" diff --git a/RecastDemo/Source/main.cpp b/RecastDemo/Source/main.cpp index e9f584df..6e6fe496 100644 --- a/RecastDemo/Source/main.cpp +++ b/RecastDemo/Source/main.cpp @@ -17,7 +17,6 @@ // #include -#define _USE_MATH_DEFINES #include #include "SDL.h" @@ -78,6 +77,9 @@ int main(int /*argc*/, char** /*argv*/) return -1; } + // Use OpenGL render driver. + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); + // Enable depth buffer. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); @@ -124,7 +126,6 @@ int main(int /*argc*/, char** /*argv*/) } SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); - SDL_GL_CreateContext(window); if (!imguiRenderGLInit("DroidSans.ttf")) { @@ -133,7 +134,6 @@ int main(int /*argc*/, char** /*argv*/) return -1; } - float t = 0.0f; float timeAcc = 0.0f; Uint32 prevFrameTime = SDL_GetTicks(); int mousePos[2] = {0, 0}; @@ -352,8 +352,6 @@ int main(int /*argc*/, char** /*argv*/) Uint32 time = SDL_GetTicks(); float dt = (time - prevFrameTime) / 1000.0f; prevFrameTime = time; - - t += dt; // Hit test mesh. if (processHitTest && geom && sample) diff --git a/RecastDemo/cmake/FindSDL2.cmake b/RecastDemo/cmake/FindSDL2.cmake new file mode 100644 index 00000000..f69c68a1 --- /dev/null +++ b/RecastDemo/cmake/FindSDL2.cmake @@ -0,0 +1,249 @@ +# - Find SDL2 +# Find the SDL2 headers and libraries +# +# SDL2::SDL2 - Imported target to use for building a library +# SDL2::SDL2main - Imported interface target to use if you want SDL and SDLmain. +# SDL2_FOUND - True if SDL2 was found. +# SDL2_DYNAMIC - If we found a DLL version of SDL (meaning you might want to copy a DLL from SDL2::SDL2) +# +# Original Author: +# 2015 Ryan Pavlik +# +# Copyright Sensics, Inc. 2015. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# Set up architectures (for windows) and prefixes (for mingw builds) +if(WIN32) + if(MINGW) + include(MinGWSearchPathExtras OPTIONAL) + if(MINGWSEARCH_TARGET_TRIPLE) + set(SDL2_PREFIX ${MINGWSEARCH_TARGET_TRIPLE}) + endif() + endif() + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(SDL2_LIB_PATH_SUFFIX lib/x64) + if(NOT MSVC AND NOT SDL2_PREFIX) + set(SDL2_PREFIX x86_64-w64-mingw32) + endif() + else() + set(SDL2_LIB_PATH_SUFFIX lib/x86) + if(NOT MSVC AND NOT SDL2_PREFIX) + set(SDL2_PREFIX i686-w64-mingw32) + endif() + endif() +endif() + +if(SDL2_PREFIX) + set(SDL2_ORIGPREFIXPATH ${CMAKE_PREFIX_PATH}) + if(SDL2_ROOT_DIR) + list(APPEND CMAKE_PREFIX_PATH "${SDL2_ROOT_DIR}") + endif() + if(CMAKE_PREFIX_PATH) + foreach(_prefix ${CMAKE_PREFIX_PATH}) + list(APPEND CMAKE_PREFIX_PATH "${_prefix}/${SDL2_PREFIX}") + endforeach() + endif() + if(MINGWSEARCH_PREFIXES) + list(APPEND CMAKE_PREFIX_PATH ${MINGWSEARCH_PREFIXES}) + endif() +endif() + +# Invoke pkgconfig for hints +find_package(PkgConfig QUIET) +set(SDL2_INCLUDE_HINTS) +set(SDL2_LIB_HINTS) +if(PKG_CONFIG_FOUND) + pkg_search_module(SDL2PC QUIET sdl2) + if(SDL2PC_INCLUDE_DIRS) + set(SDL2_INCLUDE_HINTS ${SDL2PC_INCLUDE_DIRS}) + endif() + if(SDL2PC_LIBRARY_DIRS) + set(SDL2_LIB_HINTS ${SDL2PC_LIBRARY_DIRS}) + endif() +endif() + +include(FindPackageHandleStandardArgs) + +find_library(SDL2_LIBRARY + NAMES + SDL2 + HINTS + ${SDL2_LIB_HINTS} + PATHS + ${SDL2_ROOT_DIR} + ENV SDL2DIR + PATH_SUFFIXES lib SDL2 ${SDL2_LIB_PATH_SUFFIX}) + +set(_sdl2_framework FALSE) +# Some special-casing if we've found/been given a framework. +# Handles whether we're given the library inside the framework or the framework itself. +if(APPLE AND "${SDL2_LIBRARY}" MATCHES "(/[^/]+)*.framework(/.*)?$") + set(_sdl2_framework TRUE) + set(SDL2_FRAMEWORK "${SDL2_LIBRARY}") + # Move up in the directory tree as required to get the framework directory. + while("${SDL2_FRAMEWORK}" MATCHES "(/[^/]+)*.framework(/.*)$" AND NOT "${SDL2_FRAMEWORK}" MATCHES "(/[^/]+)*.framework$") + get_filename_component(SDL2_FRAMEWORK "${SDL2_FRAMEWORK}" DIRECTORY) + endwhile() + if("${SDL2_FRAMEWORK}" MATCHES "(/[^/]+)*.framework$") + set(SDL2_FRAMEWORK_NAME ${CMAKE_MATCH_1}) + # If we found a framework, do a search for the header ahead of time that will be more likely to get the framework header. + find_path(SDL2_INCLUDE_DIR + NAMES + SDL_haptic.h # this file was introduced with SDL2 + HINTS + "${SDL2_FRAMEWORK}/Headers/") + else() + # For some reason we couldn't get the framework directory itself. + # Shouldn't happen, but might if something is weird. + unset(SDL2_FRAMEWORK) + endif() +endif() + +find_path(SDL2_INCLUDE_DIR + NAMES + SDL_haptic.h # this file was introduced with SDL2 + HINTS + ${SDL2_INCLUDE_HINTS} + PATHS + ${SDL2_ROOT_DIR} + ENV SDL2DIR + PATH_SUFFIXES include include/sdl2 include/SDL2 SDL2) + +if(WIN32 AND SDL2_LIBRARY) + find_file(SDL2_RUNTIME_LIBRARY + NAMES + SDL2.dll + libSDL2.dll + HINTS + ${SDL2_LIB_HINTS} + PATHS + ${SDL2_ROOT_DIR} + ENV SDL2DIR + PATH_SUFFIXES bin lib ${SDL2_LIB_PATH_SUFFIX}) +endif() + + +if(WIN32 OR ANDROID OR IOS OR (APPLE AND NOT _sdl2_framework)) + set(SDL2_EXTRA_REQUIRED SDL2_SDLMAIN_LIBRARY) + find_library(SDL2_SDLMAIN_LIBRARY + NAMES + SDL2main + PATHS + ${SDL2_ROOT_DIR} + ENV SDL2DIR + PATH_SUFFIXES lib ${SDL2_LIB_PATH_SUFFIX}) +endif() + +if(MINGW AND NOT SDL2PC_FOUND) + find_library(SDL2_MINGW_LIBRARY mingw32) + find_library(SDL2_MWINDOWS_LIBRARY mwindows) +endif() + +if(SDL2_PREFIX) + # Restore things the way they used to be. + set(CMAKE_PREFIX_PATH ${SDL2_ORIGPREFIXPATH}) +endif() + +# handle the QUIETLY and REQUIRED arguments and set QUATLIB_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SDL2 + DEFAULT_MSG + SDL2_LIBRARY + SDL2_INCLUDE_DIR + ${SDL2_EXTRA_REQUIRED}) + +if(SDL2_FOUND) + if(NOT TARGET SDL2::SDL2) + # Create SDL2::SDL2 + if(WIN32 AND SDL2_RUNTIME_LIBRARY) + set(SDL2_DYNAMIC TRUE) + add_library(SDL2::SDL2 SHARED IMPORTED) + set_target_properties(SDL2::SDL2 + PROPERTIES + IMPORTED_IMPLIB "${SDL2_LIBRARY}" + IMPORTED_LOCATION "${SDL2_RUNTIME_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}" + ) + else() + add_library(SDL2::SDL2 UNKNOWN IMPORTED) + if(SDL2_FRAMEWORK AND SDL2_FRAMEWORK_NAME) + # Handle the case that SDL2 is a framework and we were able to decompose it above. + set_target_properties(SDL2::SDL2 PROPERTIES + IMPORTED_LOCATION "${SDL2_FRAMEWORK}/${SDL2_FRAMEWORK_NAME}") + elseif(_sdl2_framework AND SDL2_LIBRARY MATCHES "(/[^/]+)*.framework$") + # Handle the case that SDL2 is a framework and SDL_LIBRARY is just the framework itself. + + # This takes the basename of the framework, without the extension, + # and sets it (as a child of the framework) as the imported location for the target. + # This is the library symlink inside of the framework. + set_target_properties(SDL2::SDL2 PROPERTIES + IMPORTED_LOCATION "${SDL2_LIBRARY}/${CMAKE_MATCH_1}") + else() + # Handle non-frameworks (including non-Mac), as well as the case that we're given the library inside of the framework + set_target_properties(SDL2::SDL2 PROPERTIES + IMPORTED_LOCATION "${SDL2_LIBRARY}") + endif() + set_target_properties(SDL2::SDL2 + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}" + ) + endif() + + if(APPLE) + # Need Cocoa here, is always a framework + find_library(SDL2_COCOA_LIBRARY Cocoa) + list(APPEND SDL2_EXTRA_REQUIRED SDL2_COCOA_LIBRARY) + if(SDL2_COCOA_LIBRARY) + set_target_properties(SDL2::SDL2 PROPERTIES + IMPORTED_LINK_INTERFACE_LIBRARIES ${SDL2_COCOA_LIBRARY}) + endif() + endif() + + + # Compute what to do with SDL2main + set(SDL2MAIN_LIBRARIES SDL2::SDL2) + add_library(SDL2::SDL2main INTERFACE IMPORTED) + if(SDL2_SDLMAIN_LIBRARY) + add_library(SDL2::SDL2main_real STATIC IMPORTED) + set_target_properties(SDL2::SDL2main_real + PROPERTIES + IMPORTED_LOCATION "${SDL2_SDLMAIN_LIBRARY}") + set(SDL2MAIN_LIBRARIES SDL2::SDL2main_real ${SDL2MAIN_LIBRARIES}) + endif() + if(MINGW) + # MinGW requires some additional libraries to appear earlier in the link line. + if(SDL2PC_LIBRARIES) + # Use pkgconfig-suggested extra libraries if available. + list(REMOVE_ITEM SDL2PC_LIBRARIES SDL2main SDL2) + set(SDL2MAIN_LIBRARIES ${SDL2PC_LIBRARIES} ${SDL2MAIN_LIBRARIES}) + else() + # fall back to extra libraries specified in pkg-config in + # an official binary distro of SDL2 for MinGW I downloaded + if(SDL2_MINGW_LIBRARY) + set(SDL2MAIN_LIBRARIES ${SDL2_MINGW_LIBRARY} ${SDL2MAIN_LIBRARIES}) + endif() + if(SDL2_MWINDOWS_LIBRARY) + set(SDL2MAIN_LIBRARIES ${SDL2_MWINDOWS_LIBRARY} ${SDL2MAIN_LIBRARIES}) + endif() + endif() + set_target_properties(SDL2::SDL2main + PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "main=SDL_main") + endif() + set_target_properties(SDL2::SDL2main + PROPERTIES + INTERFACE_LINK_LIBRARIES "${SDL2MAIN_LIBRARIES}") + endif() + mark_as_advanced(SDL2_ROOT_DIR) +endif() + +mark_as_advanced(SDL2_LIBRARY + SDL2_RUNTIME_LIBRARY + SDL2_INCLUDE_DIR + SDL2_SDLMAIN_LIBRARY + SDL2_COCOA_LIBRARY + SDL2_MINGW_LIBRARY + SDL2_MWINDOWS_LIBRARY) diff --git a/RecastDemo/premake5.lua b/RecastDemo/premake5.lua index 7f6e00dc..52bbaf93 100644 --- a/RecastDemo/premake5.lua +++ b/RecastDemo/premake5.lua @@ -6,43 +6,49 @@ local action = _ACTION or "" local todir = "Build/" .. action -solution "recastnavigation" +workspace "recastnavigation" configurations { "Debug", "Release" } + location (todir) - -- extra warnings, no exceptions or rtti - flags { - "ExtraWarnings", - "FloatFast", - "Symbols" - } + floatingpoint "Fast" exceptionhandling "Off" rtti "Off" + symbols "On" + flags { "FatalCompileWarnings" } + cppdialect "C++98" -- debug configs - configuration "Debug*" + filter "configurations:Debug" defines { "DEBUG" } targetdir ( todir .. "/lib/Debug" ) -- release configs - configuration "Release*" + filter "configurations:Release" defines { "NDEBUG" } - flags { "Optimize" } + optimize "On" targetdir ( todir .. "/lib/Release" ) - -- windows specific - configuration "windows" - defines { "WIN32", "_WINDOWS", "_CRT_SECURE_NO_WARNINGS" } + filter "system:not windows" + warnings "Extra" - -- linux specific - configuration { "linux", "gmake" } - buildoptions { - "-Wall", - "-Werror" - } + -- windows specific + filter "system:windows" + platforms { "Win32", "Win64" } + defines { "WIN32", "_WINDOWS", "_CRT_SECURE_NO_WARNINGS", "_HAS_EXCEPTIONS=0" } + -- warnings "Extra" uses /W4 which is too aggressive for us, so use W3 instead. + -- Disable: + -- * C4351: new behavior for array initialization + buildoptions { "/W3", "/wd4351" } + + filter "platforms:Win32" + architecture "x32" + + filter "platforms:Win64" + architecture "x64" project "DebugUtils" language "C++" @@ -53,7 +59,7 @@ project "DebugUtils" "../DetourTileCache/Include", "../Recast/Include" } - files { + files { "../DebugUtils/Include/*.h", "../DebugUtils/Source/*.cpp" } @@ -68,6 +74,12 @@ project "Detour" "../Detour/Include/*.h", "../Detour/Source/*.cpp" } + -- linux library cflags and libs + filter {"system:linux", "toolset:gcc"} + buildoptions { + "-Wno-error=class-memaccess", + "-Wno-error=maybe-uninitialized" + } project "DetourCrowd" language "C++" @@ -119,7 +131,7 @@ project "RecastDemo" "../DetourTileCache/Include", "../Recast/Include" } - files { + files { "../RecastDemo/Include/*.h", "../RecastDemo/Source/*.cpp", "../RecastDemo/Contrib/fastlz/*.h", @@ -127,7 +139,7 @@ project "RecastDemo" } -- project dependencies - links { + links { "DebugUtils", "Detour", "DetourCrowd", @@ -139,11 +151,12 @@ project "RecastDemo" targetdir "Bin" -- linux library cflags and libs - configuration { "linux", "gmake" } + filter "system:linux" buildoptions { "`pkg-config --cflags sdl2`", "`pkg-config --cflags gl`", - "`pkg-config --cflags glu`" + "`pkg-config --cflags glu`", + "-Wno-ignored-qualifiers", } linkoptions { "`pkg-config --libs sdl2`", @@ -151,10 +164,15 @@ project "RecastDemo" "`pkg-config --libs glu`" } + filter { "system:linux", "toolset:gcc", "files:*.c" } + buildoptions { + "-Wno-class-memaccess" + } + -- windows library cflags and libs - configuration { "windows" } + filter "system:windows" includedirs { "../RecastDemo/Contrib/SDL/include" } - libdirs { "../RecastDemo/Contrib/SDL/lib/x86" } + libdirs { "../RecastDemo/Contrib/SDL/lib/%{cfg.architecture:gsub('x86_64', 'x64')}" } debugdir "../RecastDemo/Bin/" links { "glu32", @@ -164,14 +182,13 @@ project "RecastDemo" } postbuildcommands { -- Copy the SDL2 dll to the Bin folder. - '{COPY} "%{wks.location}../../Contrib/SDL/lib/x86/SDL2.dll" "%{cfg.targetdir}"' + '{COPY} "%{path.getabsolute("Contrib/SDL/lib/" .. cfg.architecture:gsub("x86_64", "x64") .. "/SDL2.dll")}" "%{cfg.targetdir}"' } -- mac includes and libs - configuration { "macosx" } + filter "system:macosx" kind "ConsoleApp" -- xcode4 failes to run the project if using WindowedApp - includedirs { "/Library/Frameworks/SDL2.framework/Headers" } - buildoptions { "-Wunused-value -Wshadow -Wreorder -Wsign-compare -Wall" } + includedirs { "Bin/SDL2.framework/Headers" } links { "OpenGL.framework", "SDL2.framework", @@ -181,6 +198,7 @@ project "RecastDemo" project "Tests" language "C++" kind "ConsoleApp" + cppdialect "C++14" -- Catch requires newer C++ features -- Catch requires RTTI and exceptions exceptionhandling "On" @@ -195,13 +213,17 @@ project "Tests" "../Recast/Source", "../Tests/Recast", "../Tests", + "../Tests/Contrib/Catch" } - files { + files { "../Tests/*.h", "../Tests/*.hpp", "../Tests/*.cpp", "../Tests/Recast/*.h", "../Tests/Recast/*.cpp", + "../Tests/Detour/*.h", + "../Tests/Detour/*.cpp", + "../Tests/Contrib/Catch/*.cpp" } -- project dependencies @@ -217,8 +239,8 @@ project "Tests" targetdir "Bin" -- linux library cflags and libs - configuration { "linux", "gmake" } - buildoptions { + filter "system:linux" + buildoptions { "`pkg-config --cflags sdl2`", "`pkg-config --cflags gl`", "`pkg-config --cflags glu`", @@ -227,13 +249,14 @@ project "Tests" linkoptions { "`pkg-config --libs sdl2`", "`pkg-config --libs gl`", - "`pkg-config --libs glu`" + "`pkg-config --libs glu`", + "-lpthread" } -- windows library cflags and libs - configuration { "windows" } + filter "system:windows" includedirs { "../RecastDemo/Contrib/SDL/include" } - libdirs { "../RecastDemo/Contrib/SDL/lib/x86" } + libdirs { "../RecastDemo/Contrib/SDL/lib/%{cfg.architecture:gsub('x86_64', 'x64')}" } debugdir "../RecastDemo/Bin/" links { "glu32", @@ -243,12 +266,8 @@ project "Tests" } -- mac includes and libs - configuration { "macosx" } + filter "system:macosx" kind "ConsoleApp" - includedirs { "/Library/Frameworks/SDL2.framework/Headers" } - buildoptions { "-Wunused-value -Wshadow -Wreorder -Wsign-compare -Wall" } - links { - "OpenGL.framework", - "SDL2.framework", + links { "Cocoa.framework", } diff --git a/Roadmap.md b/Roadmap.md new file mode 100644 index 00000000..e86e3793 --- /dev/null +++ b/Roadmap.md @@ -0,0 +1,74 @@ +# Recast Development Roadmap + +This document describes the current development roadmap for Recast and Detour. It is initially focused on maintaining and polishing the current functionality in a way that makes it more appealing and easier to use. There are a number of large areas of new functionality that are appealing as well. + +If you're excited about contributing to Recast or want to understand what its future looks like, this roadmap is a great place to start. + +## Short Term + +### Documentation & Web Presence +- **Project website** (GitHub pages). A home for docs, info, tutorials, etc. that's easy to find and navigate. There's stuff like the wiki system in GitHub that can serve this purpose, but it's not the greatest. +- **Hosted API Reference**: We have extensive doxygen docs that we should also host on github pages. Ideally this would be implemented as a job for the CI process. +- **High-level design/overview**. Basically taking a lot of the "how does Recast work?" docs we have (and stuff on Mikko's blog) and surfacing them in a place that's easier to find. e.g. [this information](http://digestingduck.blogspot.com/2010/02/slides-from-past.html) should be integrated to the documentation. +- **FAQ** to include answers to common questions like "can I use Recast on a spherical world?" etc. The small group of questions that come up often. +- **Expand on configuration parameter docstrings**. Expand on the docstrings for the fields in `rcConfig`. +- **Projects using recast page**. A place to show off integrations of Recast into different games and engines. Having this visible will help give people confidence in the quality of Recast. + +### More explicit variable names +There's quite a few variables that are single-letter or very condensed acronyms. Stuff like `u`, `v`, `cs`, `nnei` etc. These are certainly easier to type than more descriptive names, and can allow for some nice code formatting, but the readability gains of less condensed names outweigh that. These should be updated to be more easily maintainable and less of a hurdle for newcomers. + +### Opt-in config value validation system +There's a number of documentation-defined valid value ranges, as well as some suggested value ranges for config values like those in rcConfig. Recast should have an _opt-in_ system for validating value ranges and value relationships. Some implementations [like Godot's](https://github.com/godotengine/godot/blob/c7ceb94e372216b1b033d7c2ac26d5b7545c4dac/modules/navigation/navigation_mesh_generator.cpp#L545-L568) include similar configuration value checks already. This would help alert users to potential problems arising from misconfiguration. Recast shouldn't restrict people from going outside the recommended bounds, but for the majority of users values that are out of bounds are likely a mistake and Recast should at least raise the concern when prompted. + +## Medium Term + +### STB-Style Single-Header Release Packaging +For ease of integration. This should probably be implemented as a build packaging script, similar to the release packaging process for the Catch unit testing library. + +### Ensure there's a good threading story +Currently Recast provides no specific threading support. Many aspects of navmesh generation (especialy with tile generation) can be easily multithreaded, as can parts of detour (threaded pathfinding, etc). This likely requires a good example integration to work with - it probably shouldn't be designed in isolation. + +### More Tests +This is a good place to start when making any changes to the internals or fixing bugs. It's important to have a way to validate that bugfixes or enhancements are not adversely affecting the base Recast functionality. Lots of parts of the code are fairly simple to test, but some would require some extra effort with mocking data or similar. + +### More POD structs for clarity in internals +Small structs like a simple 3-float POD vector would go a long way to help clarify a lot of the internals. Any changes here shouldn't impact performance or functionality. They should just be simple POD structs that get optimized away but make the code a bit clearer to read and work with. + +### Revisit structural organization +There some number of generally low-level geometry functions that are implemented both in Recast and in Detour. We should probably take stock of the general library layout and determine if there's enough of a benefit to slightly restructuring things to better share core functionality. There may not be enough code like this to be worth it, so it might not be a net win. Organization and re-use benefits should vastly outweighing the cost of defining a shared dependency to make this worthwhile. There's likely just minimal gains here since things are already laid out logically, but it's worth looking into. + +## Longer-Term + +### Higher-Level APIs +The current API level is excellent for providing maximum control over the entire navmesh building process. For many people, integrating Recast involves copying over all the code from the tiled mesh demo with minimal tweaks. Recast can better serve these people, and hopefully boost adoption by providing some higher-level API calls. + +### C API +This would help tremendously with adoption. There are a number of integrations of Recast to other languages right now, and a number of them are using their own C API wrappers around the existing Recast API. Providing a first-party C API would help normalize the different integrations of Recast out there, as well as improve the integration process for people with engines written in C or other languages with C interop. + +There's enough cases where we pass around object pointers in API calls that may cause this to be a bit more work than it seems. + +# Roadmap for New Recast/Detour Functionality + +### Nav Volumes & 3D Navigation +Obviously a big feature that would enable flying agents. Probably not terribly difficult to integrate due to the voxelization process, but it's probably a lot of detailed geometric processes and challenges. We'd at least need to retain unwalkable polygons longer in the process (they get thrown away pretty early) so we should ensure it doesn't have any adversarial affects to the perf or capabilities of the normal navmesh process. 3d navigation should be a separate set of APIs, as the considerations and use cases are quite different than normal navmesh navigation. + +### Climbing Markup & Navigation +This is a common problem with many modern games. There's a number of ways to solve this, so we should make sure to consult with people who are actively using Recast and building games with climbing to understand their needs. + +### Tooling +Recast Demo already has a number of sample tools to explore and test the generated navmesh data. There's a number of middleware providers that tout workflow features and tooling for generating and manipulating navmesh data. RecastDemo could be expanded (and maybe re-named) to be a more fully fleshed out standalone tool, rather than be mostly focused as an integration demo and feature showcase. There's almost certainly a ton of custom data and markup most people will want to add to the generated navmeshes, so there should be a reasonable way for people to integrate their customizations and markup with any tooling Recast provide. + +### More spatial querying abilities +There's already a number of spatial query functions, and fleshing out that set some more is likely quite easy and useful. + +### Auto-markup system +This is something recast already gives you access to add yourself, thanks to its low-level API and how each step in the process is explicit. It is worthwhile to consider what kind of help other middleware systems provide in this process. For example, are other navigation middleware solutions providing a structured way to define spatial queries that result in nav markup? Some easy examples of useful features here might be automatic jump point or cover annotations. It would be good to talk to people in the AAA FPS space to see what markup types are most useful to them. + +### Formations, Group behaviors +Extending DetourCrowd to support formations, group navigation, group behaviors and similar beyond what's it's already capable of. + +### Vehicle Navigation & Movement +A feature set centered on pathfinding and movement planning for characters with kinematic movement constraints. + +### Crowd Simulation and Flowfield movement systems +Movement systems for large crowds that leverage flowfields or other highly-scalable navigation and movement approaches. \ No newline at end of file diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt new file mode 100644 index 00000000..45d7d1f7 --- /dev/null +++ b/Tests/CMakeLists.txt @@ -0,0 +1,14 @@ +file(GLOB TESTS_SOURCES *.cpp Detour/*.cpp Recast/*.cpp Contrib/Catch/*.cpp) + +include_directories(../Detour/Include) +include_directories(../Recast/Include) +include_directories(./Contrib/Catch) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +add_executable(Tests ${TESTS_SOURCES}) + +set_property(TARGET Tests PROPERTY CXX_STANDARD 17) + +add_dependencies(Tests Recast Detour) +target_link_libraries(Tests Recast Detour) +add_test(Tests Tests) diff --git a/Tests/Contrib/Catch/catch_amalgamated.cpp b/Tests/Contrib/Catch/catch_amalgamated.cpp new file mode 100644 index 00000000..1f5527a4 --- /dev/null +++ b/Tests/Contrib/Catch/catch_amalgamated.cpp @@ -0,0 +1,10395 @@ +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +// Catch v3.1.1 +// Generated: 2022-10-17 18:47:22.400176 +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- + +#include "catch_amalgamated.hpp" + + +#ifndef CATCH_WINDOWS_H_PROXY_HPP_INCLUDED +#define CATCH_WINDOWS_H_PROXY_HPP_INCLUDED + + +#if defined(CATCH_PLATFORM_WINDOWS) + +// We might end up with the define made globally through the compiler, +// and we don't want to trigger warnings for this +#if !defined(NOMINMAX) +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +#endif + +#include + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +#endif // CATCH_WINDOWS_H_PROXY_HPP_INCLUDED + + + + +namespace Catch { + namespace Benchmark { + namespace Detail { + ChronometerConcept::~ChronometerConcept() = default; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + + + +namespace Catch { + namespace Benchmark { + namespace Detail { + BenchmarkFunction::callable::~callable() = default; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct optimized_away_error : std::exception { + const char* what() const noexcept override; + }; + + const char* optimized_away_error::what() const noexcept { + return "could not measure benchmark, maybe it was optimized away"; + } + + void throw_optimized_away_error() { + Catch::throw_exception(optimized_away_error{}); + } + + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + +// Adapted from donated nonius code. + + + +#include +#include +#include +#include + + +#if defined(CATCH_CONFIG_USE_ASYNC) +#include +#endif + +namespace { + +using Catch::Benchmark::Detail::sample; + + template + sample resample(URng& rng, unsigned int resamples, std::vector::iterator first, std::vector::iterator last, Estimator& estimator) { + auto n = static_cast(last - first); + std::uniform_int_distribution dist(0, n - 1); + + sample out; + out.reserve(resamples); + std::generate_n(std::back_inserter(out), resamples, [n, first, &estimator, &dist, &rng] { + std::vector resampled; + resampled.reserve(n); + std::generate_n(std::back_inserter(resampled), n, [first, &dist, &rng] { return first[static_cast(dist(rng))]; }); + return estimator(resampled.begin(), resampled.end()); + }); + std::sort(out.begin(), out.end()); + return out; + } + + + double erf_inv(double x) { + // Code accompanying the article "Approximating the erfinv function" in GPU Computing Gems, Volume 2 + double w, p; + + w = -log((1.0 - x) * (1.0 + x)); + + if (w < 6.250000) { + w = w - 3.125000; + p = -3.6444120640178196996e-21; + p = -1.685059138182016589e-19 + p * w; + p = 1.2858480715256400167e-18 + p * w; + p = 1.115787767802518096e-17 + p * w; + p = -1.333171662854620906e-16 + p * w; + p = 2.0972767875968561637e-17 + p * w; + p = 6.6376381343583238325e-15 + p * w; + p = -4.0545662729752068639e-14 + p * w; + p = -8.1519341976054721522e-14 + p * w; + p = 2.6335093153082322977e-12 + p * w; + p = -1.2975133253453532498e-11 + p * w; + p = -5.4154120542946279317e-11 + p * w; + p = 1.051212273321532285e-09 + p * w; + p = -4.1126339803469836976e-09 + p * w; + p = -2.9070369957882005086e-08 + p * w; + p = 4.2347877827932403518e-07 + p * w; + p = -1.3654692000834678645e-06 + p * w; + p = -1.3882523362786468719e-05 + p * w; + p = 0.0001867342080340571352 + p * w; + p = -0.00074070253416626697512 + p * w; + p = -0.0060336708714301490533 + p * w; + p = 0.24015818242558961693 + p * w; + p = 1.6536545626831027356 + p * w; + } else if (w < 16.000000) { + w = sqrt(w) - 3.250000; + p = 2.2137376921775787049e-09; + p = 9.0756561938885390979e-08 + p * w; + p = -2.7517406297064545428e-07 + p * w; + p = 1.8239629214389227755e-08 + p * w; + p = 1.5027403968909827627e-06 + p * w; + p = -4.013867526981545969e-06 + p * w; + p = 2.9234449089955446044e-06 + p * w; + p = 1.2475304481671778723e-05 + p * w; + p = -4.7318229009055733981e-05 + p * w; + p = 6.8284851459573175448e-05 + p * w; + p = 2.4031110387097893999e-05 + p * w; + p = -0.0003550375203628474796 + p * w; + p = 0.00095328937973738049703 + p * w; + p = -0.0016882755560235047313 + p * w; + p = 0.0024914420961078508066 + p * w; + p = -0.0037512085075692412107 + p * w; + p = 0.005370914553590063617 + p * w; + p = 1.0052589676941592334 + p * w; + p = 3.0838856104922207635 + p * w; + } else { + w = sqrt(w) - 5.000000; + p = -2.7109920616438573243e-11; + p = -2.5556418169965252055e-10 + p * w; + p = 1.5076572693500548083e-09 + p * w; + p = -3.7894654401267369937e-09 + p * w; + p = 7.6157012080783393804e-09 + p * w; + p = -1.4960026627149240478e-08 + p * w; + p = 2.9147953450901080826e-08 + p * w; + p = -6.7711997758452339498e-08 + p * w; + p = 2.2900482228026654717e-07 + p * w; + p = -9.9298272942317002539e-07 + p * w; + p = 4.5260625972231537039e-06 + p * w; + p = -1.9681778105531670567e-05 + p * w; + p = 7.5995277030017761139e-05 + p * w; + p = -0.00021503011930044477347 + p * w; + p = -0.00013871931833623122026 + p * w; + p = 1.0103004648645343977 + p * w; + p = 4.8499064014085844221 + p * w; + } + return p * x; + } + + double standard_deviation(std::vector::iterator first, std::vector::iterator last) { + auto m = Catch::Benchmark::Detail::mean(first, last); + double variance = std::accumulate( first, + last, + 0., + [m]( double a, double b ) { + double diff = b - m; + return a + diff * diff; + } ) / + ( last - first ); + return std::sqrt( variance ); + } + +} + +namespace Catch { + namespace Benchmark { + namespace Detail { + +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + bool directCompare( double lhs, double rhs ) { return lhs == rhs; } +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic pop +#endif + + double weighted_average_quantile(int k, int q, std::vector::iterator first, std::vector::iterator last) { + auto count = last - first; + double idx = (count - 1) * k / static_cast(q); + int j = static_cast(idx); + double g = idx - j; + std::nth_element(first, first + j, last); + auto xj = first[j]; + if ( directCompare( g, 0 ) ) { + return xj; + } + + auto xj1 = *std::min_element(first + (j + 1), last); + return xj + g * (xj1 - xj); + } + + + double erfc_inv(double x) { + return erf_inv(1.0 - x); + } + + double normal_quantile(double p) { + static const double ROOT_TWO = std::sqrt(2.0); + + double result = 0.0; + assert(p >= 0 && p <= 1); + if (p < 0 || p > 1) { + return result; + } + + result = -erfc_inv(2.0 * p); + // result *= normal distribution standard deviation (1.0) * sqrt(2) + result *= /*sd * */ ROOT_TWO; + // result += normal disttribution mean (0) + return result; + } + + + double outlier_variance(Estimate mean, Estimate stddev, int n) { + double sb = stddev.point; + double mn = mean.point / n; + double mg_min = mn / 2.; + double sg = (std::min)(mg_min / 4., sb / std::sqrt(n)); + double sg2 = sg * sg; + double sb2 = sb * sb; + + auto c_max = [n, mn, sb2, sg2](double x) -> double { + double k = mn - x; + double d = k * k; + double nd = n * d; + double k0 = -n * nd; + double k1 = sb2 - n * sg2 + nd; + double det = k1 * k1 - 4 * sg2 * k0; + return static_cast(-2. * k0 / (k1 + std::sqrt(det))); + }; + + auto var_out = [n, sb2, sg2](double c) { + double nc = n - c; + return (nc / n) * (sb2 - nc * sg2); + }; + + return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2; + } + + + bootstrap_analysis analyse_samples(double confidence_level, unsigned int n_resamples, std::vector::iterator first, std::vector::iterator last) { + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + static std::random_device entropy; + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + auto n = static_cast(last - first); // seriously, one can't use integral types without hell in C++ + + auto mean = &Detail::mean::iterator>; + auto stddev = &standard_deviation; + +#if defined(CATCH_CONFIG_USE_ASYNC) + auto Estimate = [=](double(*f)(std::vector::iterator, std::vector::iterator)) { + auto seed = entropy(); + return std::async(std::launch::async, [=] { + std::mt19937 rng(seed); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }); + }; + + auto mean_future = Estimate(mean); + auto stddev_future = Estimate(stddev); + + auto mean_estimate = mean_future.get(); + auto stddev_estimate = stddev_future.get(); +#else + auto Estimate = [=](double(*f)(std::vector::iterator, std::vector::iterator)) { + auto seed = entropy(); + std::mt19937 rng(seed); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }; + + auto mean_estimate = Estimate(mean); + auto stddev_estimate = Estimate(stddev); +#endif // CATCH_USE_ASYNC + + double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n); + + return { mean_estimate, stddev_estimate, outlier_variance }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + + +#include +#include + +namespace { + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +} + +namespace Catch { + + Approx::Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100. ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} + + Approx Approx::custom() { + return Approx( 0 ); + } + + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } + + + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); + } + + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) + || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value))); + } + + void Approx::setMargin(double newMargin) { + CATCH_ENFORCE(newMargin >= 0, + "Invalid Approx::margin: " << newMargin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = newMargin; + } + + void Approx::setEpsilon(double newEpsilon) { + CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0, + "Invalid Approx::epsilon: " << newEpsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = newEpsilon; + } + +namespace literals { + Approx operator "" _a(long double val) { + return Approx(val); + } + Approx operator "" _a(unsigned long long val) { + return Approx(val); + } +} // end namespace literals + +std::string StringMaker::convert(Catch::Approx const& value) { + return value.toString(); +} + +} // end namespace Catch + + + +namespace Catch { + + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; + } + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + // Possibly overallocating by 3 characters should be basically free + std::string expr; expr.reserve(m_info.capturedExpression.size() + 3); + if (isFalseTest(m_info.resultDisposition)) { + expr += "!("; + } + expr += m_info.capturedExpression; + if (isFalseTest(m_info.resultDisposition)) { + expr += ')'; + } + return expr; + } + + std::string AssertionResult::getExpressionInMacro() const { + std::string expr; + if( m_info.macroName.empty() ) + expr = static_cast(m_info.capturedExpression); + else { + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + } + return expr; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + StringRef AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + + + +namespace { + bool provideBazelReporterOutput() { +#if defined(CATCH_CONFIG_BAZEL_SUPPORT) + return true; +#elif defined(CATCH_PLATFORM_WINDOWS_UWP) + // UWP does not support environment variables + return false; +#else + +# if defined( _MSC_VER ) + // On Windows getenv throws a warning as there is no input validation, + // since the switch is hardcoded, this should not be an issue. +# pragma warning( push ) +# pragma warning( disable : 4996 ) +# endif + + return std::getenv( "BAZEL_TEST" ) != nullptr; + +# if defined( _MSC_VER ) +# pragma warning( pop ) +# endif +#endif + } +} + +namespace Catch { + + bool operator==( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ) { + return lhs.name == rhs.name && + lhs.outputFilename == rhs.outputFilename && + lhs.colourMode == rhs.colourMode && + lhs.customOptions == rhs.customOptions; + } + + Config::Config( ConfigData const& data ): + m_data( data ) { + // We need to trim filter specs to avoid trouble with superfluous + // whitespace (esp. important for bdd macros, as those are manually + // aligned with whitespace). + + for (auto& elem : m_data.testsOrTags) { + elem = trim(elem); + } + for (auto& elem : m_data.sectionsToRun) { + elem = trim(elem); + } + + + TestSpecParser parser(ITagAliasRegistry::get()); + if (!m_data.testsOrTags.empty()) { + m_hasTestFilters = true; + for (auto const& testOrTags : m_data.testsOrTags) { + parser.parse(testOrTags); + } + } + m_testSpec = parser.testSpec(); + + + // Insert the default reporter if user hasn't asked for a specfic one + if ( m_data.reporterSpecifications.empty() ) { + m_data.reporterSpecifications.push_back( { +#if defined( CATCH_CONFIG_DEFAULT_REPORTER ) + CATCH_CONFIG_DEFAULT_REPORTER, +#else + "console", +#endif + {}, {}, {} + } ); + } + +#if !defined(CATCH_PLATFORM_WINDOWS_UWP) + if(provideBazelReporterOutput()){ + // Register a JUnit reporter for Bazel. Bazel sets an environment + // variable with the path to XML output. If this file is written to + // during test, Bazel will not generate a default XML output. + // This allows the XML output file to contain higher level of detail + // than what is possible otherwise. +# if defined( _MSC_VER ) + // On Windows getenv throws a warning as there is no input validation, + // since the key is hardcoded, this should not be an issue. +# pragma warning( push ) +# pragma warning( disable : 4996 ) +# endif + const auto bazelOutputFilePtr = std::getenv( "XML_OUTPUT_FILE" ); +# if defined( _MSC_VER ) +# pragma warning( pop ) +# endif + if ( bazelOutputFilePtr != nullptr ) { + m_data.reporterSpecifications.push_back( + { "junit", std::string( bazelOutputFilePtr ), {}, {} } ); + } + } +#endif + + // We now fixup the reporter specs to handle default output spec, + // default colour spec, etc + bool defaultOutputUsed = false; + for ( auto const& reporterSpec : m_data.reporterSpecifications ) { + // We do the default-output check separately, while always + // using the default output below to make the code simpler + // and avoid superfluous copies. + if ( reporterSpec.outputFile().none() ) { + CATCH_ENFORCE( !defaultOutputUsed, + "Internal error: cannot use default output for " + "multiple reporters" ); + defaultOutputUsed = true; + } + + m_processedReporterSpecs.push_back( ProcessedReporterSpec{ + reporterSpec.name(), + reporterSpec.outputFile() ? *reporterSpec.outputFile() + : data.defaultOutputFilename, + reporterSpec.colourMode().valueOr( data.defaultColourMode ), + reporterSpec.customOptions() } ); + } + } + + Config::~Config() = default; + + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + bool Config::listListeners() const { return m_data.listListeners; } + + std::vector const& Config::getTestsOrTags() const { return m_data.testsOrTags; } + std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + std::vector const& Config::getReporterSpecs() const { + return m_data.reporterSpecifications; + } + + std::vector const& + Config::getProcessedReporterSpecs() const { + return m_processedReporterSpecs; + } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + bool Config::hasTestFilters() const { return m_hasTestFilters; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { + return !!( m_data.warnings & WarnAbout::NoAssertions ); + } + bool Config::warnAboutUnmatchedTestSpecs() const { + return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec ); + } + bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; } + ShowDurations Config::showDurations() const { return m_data.showDurations; } + double Config::minDuration() const { return m_data.minDuration; } + TestRunOrder Config::runOrder() const { return m_data.runOrder; } + uint32_t Config::rngSeed() const { return m_data.rngSeed; } + unsigned int Config::shardCount() const { return m_data.shardCount; } + unsigned int Config::shardIndex() const { return m_data.shardIndex; } + ColourMode Config::defaultColourMode() const { return m_data.defaultColourMode; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + bool Config::skipBenchmarks() const { return m_data.skipBenchmarks; } + bool Config::benchmarkNoAnalysis() const { return m_data.benchmarkNoAnalysis; } + unsigned int Config::benchmarkSamples() const { return m_data.benchmarkSamples; } + double Config::benchmarkConfidenceInterval() const { return m_data.benchmarkConfidenceInterval; } + unsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; } + std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); } + +} // end namespace Catch + + + + + +namespace Catch { + std::uint32_t getSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } +} + + + +#include +#include + +namespace Catch { + + //////////////////////////////////////////////////////////////////////////// + + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ): + m_info( builder.m_info ) { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: + m_info( CATCH_MOVE( old.m_info ) ) { + old.m_moved = true; + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() && !m_moved ){ + getResultCapture().popScopedMessage(m_info); + } + } + + + Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) { + auto trimmed = [&] (size_t start, size_t end) { + while (names[start] == ',' || isspace(static_cast(names[start]))) { + ++start; + } + while (names[end] == ',' || isspace(static_cast(names[end]))) { + --end; + } + return names.substr(start, end - start + 1); + }; + auto skipq = [&] (size_t start, char quote) { + for (auto i = start + 1; i < names.size() ; ++i) { + if (names[i] == quote) + return i; + if (names[i] == '\\') + ++i; + } + CATCH_INTERNAL_ERROR("CAPTURE parsing encountered unmatched quote"); + }; + + size_t start = 0; + std::stack openings; + for (size_t pos = 0; pos < names.size(); ++pos) { + char c = names[pos]; + switch (c) { + case '[': + case '{': + case '(': + // It is basically impossible to disambiguate between + // comparison and start of template args in this context +// case '<': + openings.push(c); + break; + case ']': + case '}': + case ')': +// case '>': + openings.pop(); + break; + case '"': + case '\'': + pos = skipq(pos, c); + break; + case ',': + if (start != pos && openings.empty()) { + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = static_cast(trimmed(start, pos)); + m_messages.back().message += " := "; + start = pos; + } + } + } + assert(openings.empty() && "Mismatched openings"); + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = static_cast(trimmed(start, names.size() - 1)); + m_messages.back().message += " := "; + } + Capturer::~Capturer() { + if ( !uncaught_exceptions() ){ + assert( m_captured == m_messages.size() ); + for( size_t i = 0; i < m_captured; ++i ) + m_resultCapture.popScopedMessage( m_messages[i] ); + } + } + + void Capturer::captureValue( size_t index, std::string const& value ) { + assert( index < m_messages.size() ); + m_messages[index].message += value; + m_resultCapture.pushScopedMessage( m_messages[index] ); + m_captured++; + } + +} // end namespace Catch + + + + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, + public IMutableRegistryHub, + private Detail::NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const& getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const& getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const& getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter( std::string const& name, IReporterFactoryPtr factory ) override { + m_reporterRegistry.registerReporter( name, CATCH_MOVE(factory) ); + } + void registerListener( Detail::unique_ptr factory ) override { + m_reporterRegistry.registerListener( CATCH_MOVE(factory) ); + } + void registerTest( Detail::unique_ptr&& testInfo, Detail::unique_ptr&& invoker ) override { + m_testCaseRegistry.registerTest( CATCH_MOVE(testInfo), CATCH_MOVE(invoker) ); + } + void registerTranslator( Detail::unique_ptr&& translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( CATCH_MOVE(translator) ); + } + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + void registerStartupException() noexcept override { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + m_exceptionRegistry.add(std::current_exception()); +#else + CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); +#endif + } + IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override { + return m_enumValuesRegistry; + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + Detail::EnumValuesRegistry m_enumValuesRegistry; + }; + } + + using RegistryHubSingleton = Singleton; + + IRegistryHub const& getRegistryHub() { + return RegistryHubSingleton::get(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return RegistryHubSingleton::getMutable(); + } + void cleanUp() { + cleanupSingletons(); + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + + +} // end namespace Catch + + + +#include +#include +#include +#include + +namespace Catch { + + namespace { + const int MaxExitCode = 255; + + IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config)); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\''); + + return reporter; + } + + IEventListenerPtr prepareReporters(Config const* config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty() + && config->getProcessedReporterSpecs().size() == 1) { + auto const& spec = config->getProcessedReporterSpecs()[0]; + return createReporter( + spec.name, + ReporterConfig( config, + makeStream( spec.outputFilename ), + spec.colourMode, + spec.customOptions ) ); + } + + auto multi = Detail::make_unique(config); + + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) { + multi->addListener(listener->create(config)); + } + + for ( auto const& reporterSpec : config->getProcessedReporterSpecs() ) { + multi->addReporter( createReporter( + reporterSpec.name, + ReporterConfig( config, + makeStream( reporterSpec.outputFilename ), + reporterSpec.colourMode, + reporterSpec.customOptions ) ) ); + } + + return multi; + } + + class TestGroup { + public: + explicit TestGroup(IEventListenerPtr&& reporter, Config const* config): + m_reporter(reporter.get()), + m_config{config}, + m_context{config, CATCH_MOVE(reporter)} { + + assert( m_config->testSpec().getInvalidSpecs().empty() && + "Invalid test specs should be handled before running tests" ); + + auto const& allTestCases = getAllTestCasesSorted(*m_config); + auto const& testSpec = m_config->testSpec(); + if ( !testSpec.hasFilters() ) { + for ( auto const& test : allTestCases ) { + if ( !test.getTestCaseInfo().isHidden() ) { + m_tests.emplace( &test ); + } + } + } else { + m_matches = + testSpec.matchesByFilter( allTestCases, *m_config ); + for ( auto const& match : m_matches ) { + m_tests.insert( match.tests.begin(), + match.tests.end() ); + } + } + + m_tests = createShard(m_tests, m_config->shardCount(), m_config->shardIndex()); + } + + Totals execute() { + Totals totals; + for (auto const& testCase : m_tests) { + if (!m_context.aborting()) + totals += m_context.runTest(*testCase); + else + m_reporter->skipTest(testCase->getTestCaseInfo()); + } + + for (auto const& match : m_matches) { + if (match.tests.empty()) { + m_unmatchedTestSpecs = true; + m_reporter->noMatchingTestCases( match.name ); + } + } + + return totals; + } + + bool hadUnmatchedTestSpecs() const { + return m_unmatchedTestSpecs; + } + + + private: + IEventListener* m_reporter; + Config const* m_config; + RunContext m_context; + std::set m_tests; + TestSpec::Matches m_matches; + bool m_unmatchedTestSpecs = false; + }; + + void applyFilenamesAsTags() { + for (auto const& testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) { + testInfo->addFilenameTag(); + } + } + + } // anon namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } + } + + // There cannot be exceptions at startup in no-exception mode. +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + config(); + getCurrentMutableContext().setConfig(m_config.get()); + + m_startupExceptions = true; + auto errStream = makeStream( "%stderr" ); + auto colourImpl = makeColourImpl( + ColourMode::PlatformDefault, errStream.get() ); + auto guard = colourImpl->guardColour( Colour::Red ); + errStream->stream() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + errStream->stream() << TextFlow::Column( ex.what() ).indent(2) << '\n'; + } + } + } +#endif + + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch2 v" << libraryVersion() << '\n' + << m_cli << '\n' + << "For more detailed usage please see the project docs\n\n" << std::flush; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch2 test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch2\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << '\n' << std::flush; + } + + int Session::applyCommandLine( int argc, char const * const * argv ) { + if( m_startupExceptions ) + return 1; + + auto result = m_cli.parse( Clara::Args( argc, argv ) ); + + if( !result ) { + config(); + getCurrentMutableContext().setConfig(m_config.get()); + auto errStream = makeStream( "%stderr" ); + auto colour = makeColourImpl( ColourMode::PlatformDefault, errStream.get() ); + + errStream->stream() + << colour->guardColour( Colour::Red ) + << "\nError(s) in input:\n" + << TextFlow::Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + errStream->stream() << "Run with -? for usage\n\n" << std::flush; + return MaxExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + + m_config.reset(); + return 0; + } + +#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting\n" << std::flush; + static_cast(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush; + static_cast(std::getchar()); + } + return exitCode; + } + + Clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( Clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = Detail::make_unique( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if( m_startupExceptions ) + return 1; + + if (m_configData.showHelp || m_configData.libIdentify) { + return 0; + } + + if ( m_configData.shardIndex >= m_configData.shardCount ) { + Catch::cerr() << "The shard count (" << m_configData.shardCount + << ") must be greater than the shard index (" + << m_configData.shardIndex << ")\n" + << std::flush; + return 1; + } + + CATCH_TRY { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if (m_configData.filenamesAsTags) { + applyFilenamesAsTags(); + } + + // Set up global config instance before we start calling into other functions + getCurrentMutableContext().setConfig(m_config.get()); + + // Create reporter(s) so we can route listings through them + auto reporter = prepareReporters(m_config.get()); + + auto const& invalidSpecs = m_config->testSpec().getInvalidSpecs(); + if ( !invalidSpecs.empty() ) { + for ( auto const& spec : invalidSpecs ) { + reporter->reportInvalidTestSpec( spec ); + } + return 1; + } + + + // Handle list request + if (list(*reporter, *m_config)) { + return 0; + } + + TestGroup tests { CATCH_MOVE(reporter), m_config.get() }; + auto const totals = tests.execute(); + + if ( tests.hadUnmatchedTestSpecs() + && m_config->warnAboutUnmatchedTestSpecs() ) { + return 3; + } + + if ( totals.testCases.total() == 0 + && !m_config->zeroTestsCountAsSuccess() ) { + return 2; + } + + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed + return (std::min) (MaxExitCode, static_cast(totals.assertions.failed)); + } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << '\n' << std::flush; + return MaxExitCode; + } +#endif + } + +} // end namespace Catch + + + + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + CATCH_TRY { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} + + + +#include +#include +#include + +namespace Catch { + + namespace { + using TCP_underlying_type = uint8_t; + static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type), + "The size of the TestCaseProperties is different from the assumed size"); + + TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) { + return static_cast( + static_cast(lhs) | static_cast(rhs) + ); + } + + TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) { + lhs = static_cast( + static_cast(lhs) | static_cast(rhs) + ); + return lhs; + } + + TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) { + return static_cast( + static_cast(lhs) & static_cast(rhs) + ); + } + + bool applies(TestCaseProperties tcp) { + static_assert(static_cast(TestCaseProperties::None) == 0, + "TestCaseProperties::None must be equal to 0"); + return tcp != TestCaseProperties::None; + } + + TestCaseProperties parseSpecialTag( StringRef tag ) { + if( !tag.empty() && tag[0] == '.' ) + return TestCaseProperties::IsHidden; + else if( tag == "!throws"_sr ) + return TestCaseProperties::Throws; + else if( tag == "!shouldfail"_sr ) + return TestCaseProperties::ShouldFail; + else if( tag == "!mayfail"_sr ) + return TestCaseProperties::MayFail; + else if( tag == "!nonportable"_sr ) + return TestCaseProperties::NonPortable; + else if( tag == "!benchmark"_sr ) + return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden; + else + return TestCaseProperties::None; + } + bool isReservedTag( StringRef tag ) { + return parseSpecialTag( tag ) == TestCaseProperties::None + && tag.size() > 0 + && !std::isalnum( static_cast(tag[0]) ); + } + void enforceNotReservedTag( StringRef tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alphanumeric characters are reserved\n" + << _lineInfo ); + } + + std::string makeDefaultName() { + static size_t counter = 0; + return "Anonymous test case " + std::to_string(++counter); + } + + StringRef extractFilenamePart(StringRef filename) { + size_t lastDot = filename.size(); + while (lastDot > 0 && filename[lastDot - 1] != '.') { + --lastDot; + } + --lastDot; + + size_t nameStart = lastDot; + while (nameStart > 0 && filename[nameStart - 1] != '/' && filename[nameStart - 1] != '\\') { + --nameStart; + } + + return filename.substr(nameStart, lastDot - nameStart); + } + + // Returns the upper bound on size of extra tags ([#file]+[.]) + size_t sizeOfExtraTags(StringRef filepath) { + // [.] is 3, [#] is another 3 + const size_t extras = 3 + 3; + return extractFilenamePart(filepath).size() + extras; + } + } // end unnamed namespace + + bool operator<( Tag const& lhs, Tag const& rhs ) { + Detail::CaseInsensitiveLess cmp; + return cmp( lhs.original, rhs.original ); + } + bool operator==( Tag const& lhs, Tag const& rhs ) { + Detail::CaseInsensitiveEqualTo cmp; + return cmp( lhs.original, rhs.original ); + } + + Detail::unique_ptr + makeTestCaseInfo(StringRef _className, + NameAndTags const& nameAndTags, + SourceLineInfo const& _lineInfo ) { + return Detail::make_unique(_className, nameAndTags, _lineInfo); + } + + TestCaseInfo::TestCaseInfo(StringRef _className, + NameAndTags const& _nameAndTags, + SourceLineInfo const& _lineInfo): + name( _nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name ), + className( _className ), + lineInfo( _lineInfo ) + { + StringRef originalTags = _nameAndTags.tags; + // We need to reserve enough space to store all of the tags + // (including optional hidden tag and filename tag) + auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file); + backingTags.reserve(requiredSize); + + // We cannot copy the tags directly, as we need to normalize + // some tags, so that [.foo] is copied as [.][foo]. + size_t tagStart = 0; + size_t tagEnd = 0; + bool inTag = false; + for (size_t idx = 0; idx < originalTags.size(); ++idx) { + auto c = originalTags[idx]; + if (c == '[') { + assert(!inTag); + inTag = true; + tagStart = idx; + } + if (c == ']') { + assert(inTag); + inTag = false; + tagEnd = idx; + assert(tagStart < tagEnd); + + // We need to check the tag for special meanings, copy + // it over to backing storage and actually reference the + // backing storage in the saved tags + StringRef tagStr = originalTags.substr(tagStart+1, tagEnd - tagStart - 1); + CATCH_ENFORCE(!tagStr.empty(), "Empty tags are not allowed"); + enforceNotReservedTag(tagStr, lineInfo); + properties |= parseSpecialTag(tagStr); + // When copying a tag to the backing storage, we need to + // check if it is a merged hide tag, such as [.foo], and + // if it is, we need to handle it as if it was [foo]. + if (tagStr.size() > 1 && tagStr[0] == '.') { + tagStr = tagStr.substr(1, tagStr.size() - 1); + } + // We skip over dealing with the [.] tag, as we will add + // it later unconditionally and then sort and unique all + // the tags. + internalAppendTag(tagStr); + } + (void)inTag; // Silence "set-but-unused" warning in release mode. + } + // Add [.] if relevant + if (isHidden()) { + internalAppendTag("."_sr); + } + + // Sort and prepare tags + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), + end(tags)); + } + + bool TestCaseInfo::isHidden() const { + return applies( properties & TestCaseProperties::IsHidden ); + } + bool TestCaseInfo::throws() const { + return applies( properties & TestCaseProperties::Throws ); + } + bool TestCaseInfo::okToFail() const { + return applies( properties & (TestCaseProperties::ShouldFail | TestCaseProperties::MayFail ) ); + } + bool TestCaseInfo::expectedToFail() const { + return applies( properties & (TestCaseProperties::ShouldFail) ); + } + + void TestCaseInfo::addFilenameTag() { + std::string combined("#"); + combined += extractFilenamePart(lineInfo.file); + internalAppendTag(combined); + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.original.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret += tag.original; + ret.push_back(']'); + } + + return ret; + } + + void TestCaseInfo::internalAppendTag(StringRef tagStr) { + backingTags += '['; + const auto backingStart = backingTags.size(); + backingTags += tagStr; + const auto backingEnd = backingTags.size(); + backingTags += ']'; + tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart)); + } + + bool operator<( TestCaseInfo const& lhs, TestCaseInfo const& rhs ) { + // We want to avoid redoing the string comparisons multiple times, + // so we store the result of a three-way comparison before using + // it in the actual comparison logic. + const auto cmpName = lhs.name.compare( rhs.name ); + if ( cmpName != 0 ) { + return cmpName < 0; + } + const auto cmpClassName = lhs.className.compare( rhs.className ); + if ( cmpClassName != 0 ) { + return cmpClassName < 0; + } + return lhs.tags < rhs.tags; + } + + TestCaseInfo const& TestCaseHandle::getTestCaseInfo() const { + return *m_info; + } + +} // end namespace Catch + + + +#include +#include +#include + +namespace Catch { + + TestSpec::Pattern::Pattern( std::string const& name ) + : m_name( name ) + {} + + TestSpec::Pattern::~Pattern() = default; + + std::string const& TestSpec::Pattern::name() const { + return m_name; + } + + + TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString ) + : Pattern( filterString ) + , m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( testCase.name ); + } + + + TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString ) + : Pattern( filterString ) + , m_tag( tag ) + {} + + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find( begin( testCase.tags ), + end( testCase.tags ), + Tag( m_tag ) ) != end( testCase.tags ); + } + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + bool should_use = !testCase.isHidden(); + for (auto const& pattern : m_required) { + should_use = true; + if (!pattern->matches(testCase)) { + return false; + } + } + for (auto const& pattern : m_forbidden) { + if (pattern->matches(testCase)) { + return false; + } + } + return should_use; + } + + std::string TestSpec::Filter::name() const { + std::string name; + for (auto const& p : m_required) { + name += p->name(); + } + for (auto const& p : m_forbidden) { + name += p->name(); + } + return name; + } + + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } ); + } + + TestSpec::Matches TestSpec::matchesByFilter( std::vector const& testCases, IConfig const& config ) const + { + Matches matches( m_filters.size() ); + std::transform( m_filters.begin(), m_filters.end(), matches.begin(), [&]( Filter const& filter ){ + std::vector currentMatches; + for( auto const& test : testCases ) + if( isThrowSafe( test, config ) && filter.matches( test.getTestCaseInfo() ) ) + currentMatches.emplace_back( &test ); + return FilterMatch{ filter.name(), currentMatches }; + } ); + return matches; + } + + const TestSpec::vectorStrings& TestSpec::getInvalidSpecs() const { + return m_invalidSpecs; + } + +} + + + +#include + +namespace Catch { + + namespace { + static auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + } + } // end unnamed namespace + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; + } + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return getElapsedMicroseconds()/1000000.0; + } + + +} // namespace Catch + + + + +#include +#include + +namespace Catch { + +namespace Detail { + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + int one = 1; + // If the lowest byte we read is non-zero, we can assume + // that little endian format is used. + auto value = *reinterpret_cast(&one); + return value ? Little : Big; + } + }; + + template + std::string fpToString(T value, int precision) { + if (Catch::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision(precision) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of('0'); + if (i != std::string::npos && i != d.size() - 1) { + if (d[i] == '.') + i++; + d = d.substr(0, i + 1); + } + return d; + } + } // end unnamed namespace + + std::string convertIntoString(StringRef string, bool escape_invisibles) { + std::string ret; + // This is enough for the "don't escape invisibles" case, and a good + // lower bound on the "escape invisibles" case. + ret.reserve(string.size() + 2); + + if (!escape_invisibles) { + ret += '"'; + ret += string; + ret += '"'; + return ret; + } + + ret += '"'; + for (char c : string) { + switch (c) { + case '\r': + ret.append("\\r"); + break; + case '\n': + ret.append("\\n"); + break; + case '\t': + ret.append("\\t"); + break; + case '\f': + ret.append("\\f"); + break; + default: + ret.push_back(c); + break; + } + } + ret += '"'; + + return ret; + } + + std::string convertIntoString(StringRef string) { + return convertIntoString(string, getCurrentContext().getConfig()->showInvisibles()); + } + + std::string rawMemoryToString( const void *object, std::size_t size ) { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + rss << std::setw(2) << static_cast(bytes[i]); + return rss.str(); + } +} // end Detail namespace + + + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker::convert(const std::string& str) { + return Detail::convertIntoString( str ); +} + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::string_view str) { + return Detail::convertIntoString( StringRef( str.data(), str.size() ) ); +} +#endif + +std::string StringMaker::convert(char const* str) { + if (str) { + return Detail::convertIntoString( str ); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(char* str) { + if (str) { + return Detail::convertIntoString( str ); + } else { + return{ "{null string}" }; + } +} + +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker::convert(std::wstring_view str) { + return StringMaker::convert(std::wstring(str)); +} +# endif + +std::string StringMaker::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +#endif + +#if defined(CATCH_CONFIG_CPP17_BYTE) +#include +std::string StringMaker::convert(std::byte value) { + return ::Catch::Detail::stringify(std::to_integer(value)); +} +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + +std::string StringMaker::convert(int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(signed char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker::convert(char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} +std::string StringMaker::convert(unsigned char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} + +int StringMaker::precision = 5; + +std::string StringMaker::convert(float value) { + return Detail::fpToString(value, precision) + 'f'; +} + +int StringMaker::precision = 10; + +std::string StringMaker::convert(double value) { + return Detail::fpToString(value, precision); +} + +} // end namespace Catch + + + +namespace Catch { + + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::uint64_t Counts::total() const { + return passed + failed + failedButOk; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + +} + + +#include + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const& libraryVersion() { + static Version version( 3, 1, 1, "", 0 ); + return version; + } + +} + + + + +namespace Catch { + + const char* GeneratorException::what() const noexcept { + return m_msg; + } + +} // end namespace Catch + + + + +namespace Catch { + + IGeneratorTracker::~IGeneratorTracker() = default; + +namespace Generators { + +namespace Detail { + + [[noreturn]] + void throw_generator_exception(char const* msg) { + Catch::throw_exception(GeneratorException{ msg }); + } +} // end namespace Detail + + GeneratorUntypedBase::~GeneratorUntypedBase() = default; + + auto acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( generatorName, lineInfo ); + } + +} // namespace Generators +} // namespace Catch + + + + + +std::uint32_t Catch::Generators::Detail::getSeed() { return sharedRng()(); } + + + + +namespace Catch { + IResultCapture::~IResultCapture() = default; +} + + + + +namespace Catch { + IConfig::~IConfig() = default; +} + + + + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} + + + +#include + +namespace Catch { + namespace Generators { + + bool GeneratorUntypedBase::countedNext() { + auto ret = next(); + if ( ret ) { + m_stringReprCache.clear(); + ++m_currentElementIndex; + } + return ret; + } + + StringRef GeneratorUntypedBase::currentElementAsString() const { + if ( m_stringReprCache.empty() ) { + m_stringReprCache = stringifyImpl(); + } + return m_stringReprCache; + } + + } // namespace Generators +} // namespace Catch + + + + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} + + + +#include +#include +#include + +namespace Catch { + + ReporterConfig::ReporterConfig( + IConfig const* _fullConfig, + Detail::unique_ptr _stream, + ColourMode colourMode, + std::map customOptions ): + m_stream( CATCH_MOVE(_stream) ), + m_fullConfig( _fullConfig ), + m_colourMode( colourMode ), + m_customOptions( CATCH_MOVE( customOptions ) ) {} + + Detail::unique_ptr ReporterConfig::takeStream() && { + assert( m_stream ); + return CATCH_MOVE( m_stream ); + } + IConfig const * ReporterConfig::fullConfig() const { return m_fullConfig; } + ColourMode ReporterConfig::colourMode() const { return m_colourMode; } + + std::map const& + ReporterConfig::customOptions() const { + return m_customOptions; + } + + ReporterConfig::~ReporterConfig() = default; + + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; + + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + + SectionStats::SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( &_testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + + + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + IEventListener::~IEventListener() = default; + +} // end namespace Catch + + + + +namespace Catch { + IReporterFactory::~IReporterFactory() = default; + EventListenerFactory::~EventListenerFactory() = default; +} + + + + +namespace Catch { + IReporterRegistry::~IReporterRegistry() = default; +} + + + + +namespace Catch { + ITestInvoker::~ITestInvoker() = default; + ITestCaseRegistry::~ITestCaseRegistry() = default; +} + + + +namespace Catch { + + AssertionHandler::AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + {} + + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); + } + + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + setCompleted(); + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if (m_reaction.shouldThrow) { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + throw Catch::TestFailureException(); +#else + CATCH_ERROR( "Test failure requires aborting test!" ); +#endif + } + } + void AssertionHandler::setCompleted() { + m_completed = true; + } + + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); + } + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); + } + +} // namespace Catch + + + + +#include + +namespace Catch { + namespace Detail { + + bool CaseInsensitiveLess::operator()( StringRef lhs, + StringRef rhs ) const { + return std::lexicographical_compare( + lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + []( char l, char r ) { return toLower( l ) < toLower( r ); } ); + } + + bool + CaseInsensitiveEqualTo::operator()( StringRef lhs, + StringRef rhs ) const { + return std::equal( + lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + []( char l, char r ) { return toLower( l ) == toLower( r ); } ); + } + + } // namespace Detail +} // namespace Catch + + + + +#include +#include + +namespace { + bool isOptPrefix( char c ) { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + std::string normaliseOpt( std::string const& optName ) { +#ifdef CATCH_PLATFORM_WINDOWS + if ( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; + } + +} // namespace + +namespace Catch { + namespace Clara { + namespace Detail { + + void TokenStream::loadBuffer() { + m_tokenBuffer.clear(); + + // Skip any empty strings + while ( it != itEnd && it->empty() ) { + ++it; + } + + if ( it != itEnd ) { + auto const& next = *it; + if ( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if ( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( + { TokenType::Option, + next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( + { TokenType::Argument, + next.substr( delimiterPos + 1 ) } ); + } else { + if ( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for ( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( + { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( + { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( + { TokenType::Argument, next } ); + } + } + } + + TokenStream::TokenStream( Args const& args ): + TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream::TokenStream( Iterator it_, Iterator itEnd_ ): + it( it_ ), itEnd( itEnd_ ) { + loadBuffer(); + } + + TokenStream& TokenStream::operator++() { + if ( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if ( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } + + ParserResult convertInto( std::string const& source, + std::string& target ) { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + + ParserResult convertInto( std::string const& source, + bool& target ) { + std::string srcLC = toLower( source ); + + if ( srcLC == "y" || srcLC == "1" || srcLC == "true" || + srcLC == "yes" || srcLC == "on" ) { + target = true; + } else if ( srcLC == "n" || srcLC == "0" || srcLC == "false" || + srcLC == "no" || srcLC == "off" ) { + target = false; + } else { + return ParserResult::runtimeError( + "Expected a boolean value but did not recognise: '" + + source + '\'' ); + } + return ParserResult::ok( ParseResultType::Matched ); + } + + size_t ParserBase::cardinality() const { return 1; } + + InternalParseResult ParserBase::parse( Args const& args ) const { + return parse( args.exeName(), TokenStream( args ) ); + } + + ParseState::ParseState( ParseResultType type, + TokenStream const& remainingTokens ): + m_type( type ), m_remainingTokens( remainingTokens ) {} + + ParserResult BoundFlagRef::setFlag( bool flag ) { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); + } + + ResultBase::~ResultBase() = default; + + bool BoundRef::isContainer() const { return false; } + + bool BoundRef::isFlag() const { return false; } + + bool BoundFlagRefBase::isFlag() const { return true; } + +} // namespace Detail + + Detail::InternalParseResult Arg::parse(std::string const&, + Detail::TokenStream const& tokens) const { + auto validationResult = validate(); + if (!validationResult) + return Detail::InternalParseResult(validationResult); + + auto remainingTokens = tokens; + auto const& token = *remainingTokens; + if (token.type != Detail::TokenType::Argument) + return Detail::InternalParseResult::ok(Detail::ParseState( + ParseResultType::NoMatch, remainingTokens)); + + assert(!m_ref->isFlag()); + auto valueRef = + static_cast(m_ref.get()); + + auto result = valueRef->setValue(remainingTokens->token); + if (!result) + return Detail::InternalParseResult(result); + else + return Detail::InternalParseResult::ok(Detail::ParseState( + ParseResultType::Matched, ++remainingTokens)); + } + + Opt::Opt(bool& ref) : + ParserRefImpl(std::make_shared(ref)) {} + + std::vector Opt::getHelpColumns() const { + std::ostringstream oss; + bool first = true; + for (auto const& opt : m_optNames) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if (!m_hint.empty()) + oss << " <" << m_hint << '>'; + return { { oss.str(), m_description } }; + } + + bool Opt::isMatch(std::string const& optToken) const { + auto normalisedToken = normaliseOpt(optToken); + for (auto const& name : m_optNames) { + if (normaliseOpt(name) == normalisedToken) + return true; + } + return false; + } + + Detail::InternalParseResult Opt::parse(std::string const&, + Detail::TokenStream const& tokens) const { + auto validationResult = validate(); + if (!validationResult) + return Detail::InternalParseResult(validationResult); + + auto remainingTokens = tokens; + if (remainingTokens && + remainingTokens->type == Detail::TokenType::Option) { + auto const& token = *remainingTokens; + if (isMatch(token.token)) { + if (m_ref->isFlag()) { + auto flagRef = + static_cast( + m_ref.get()); + auto result = flagRef->setFlag(true); + if (!result) + return Detail::InternalParseResult(result); + if (result.value() == + ParseResultType::ShortCircuitAll) + return Detail::InternalParseResult::ok(Detail::ParseState( + result.value(), remainingTokens)); + } else { + auto valueRef = + static_cast( + m_ref.get()); + ++remainingTokens; + if (!remainingTokens) + return Detail::InternalParseResult::runtimeError( + "Expected argument following " + + token.token); + auto const& argToken = *remainingTokens; + if (argToken.type != Detail::TokenType::Argument) + return Detail::InternalParseResult::runtimeError( + "Expected argument following " + + token.token); + const auto result = valueRef->setValue(argToken.token); + if (!result) + return Detail::InternalParseResult(result); + if (result.value() == + ParseResultType::ShortCircuitAll) + return Detail::InternalParseResult::ok(Detail::ParseState( + result.value(), remainingTokens)); + } + return Detail::InternalParseResult::ok(Detail::ParseState( + ParseResultType::Matched, ++remainingTokens)); + } + } + return Detail::InternalParseResult::ok( + Detail::ParseState(ParseResultType::NoMatch, remainingTokens)); + } + + Detail::Result Opt::validate() const { + if (m_optNames.empty()) + return Detail::Result::logicError("No options supplied to Opt"); + for (auto const& name : m_optNames) { + if (name.empty()) + return Detail::Result::logicError( + "Option name cannot be empty"); +#ifdef CATCH_PLATFORM_WINDOWS + if (name[0] != '-' && name[0] != '/') + return Detail::Result::logicError( + "Option name must begin with '-' or '/'"); +#else + if (name[0] != '-') + return Detail::Result::logicError( + "Option name must begin with '-'"); +#endif + } + return ParserRefImpl::validate(); + } + + ExeName::ExeName() : + m_name(std::make_shared("")) {} + + ExeName::ExeName(std::string& ref) : ExeName() { + m_ref = std::make_shared>(ref); + } + + Detail::InternalParseResult + ExeName::parse(std::string const&, + Detail::TokenStream const& tokens) const { + return Detail::InternalParseResult::ok( + Detail::ParseState(ParseResultType::NoMatch, tokens)); + } + + ParserResult ExeName::set(std::string const& newName) { + auto lastSlash = newName.find_last_of("\\/"); + auto filename = (lastSlash == std::string::npos) + ? newName + : newName.substr(lastSlash + 1); + + *m_name = filename; + if (m_ref) + return m_ref->setValue(filename); + else + return ParserResult::ok(ParseResultType::Matched); + } + + + + + Parser& Parser::operator|=( Parser const& other ) { + m_options.insert( m_options.end(), + other.m_options.begin(), + other.m_options.end() ); + m_args.insert( + m_args.end(), other.m_args.begin(), other.m_args.end() ); + return *this; + } + + std::vector Parser::getHelpColumns() const { + std::vector cols; + for ( auto const& o : m_options ) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); + } + return cols; + } + + void Parser::writeToStream( std::ostream& os ) const { + if ( !m_exeName.name().empty() ) { + os << "usage:\n" + << " " << m_exeName.name() << ' '; + bool required = true, first = true; + for ( auto const& arg : m_args ) { + if ( first ) + first = false; + else + os << ' '; + if ( arg.isOptional() && required ) { + os << '['; + required = false; + } + os << '<' << arg.hint() << '>'; + if ( arg.cardinality() == 0 ) + os << " ... "; + } + if ( !required ) + os << ']'; + if ( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:\n"; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for ( auto const& cols : rows ) + optWidth = ( std::max )( optWidth, cols.left.size() + 2 ); + + optWidth = ( std::min )( optWidth, consoleWidth / 2 ); + + for ( auto const& cols : rows ) { + auto row = TextFlow::Column( cols.left ) + .width( optWidth ) + .indent( 2 ) + + TextFlow::Spacer( 4 ) + + TextFlow::Column( cols.right ) + .width( consoleWidth - 7 - optWidth ); + os << row << '\n'; + } + } + + Detail::Result Parser::validate() const { + for ( auto const& opt : m_options ) { + auto result = opt.validate(); + if ( !result ) + return result; + } + for ( auto const& arg : m_args ) { + auto result = arg.validate(); + if ( !result ) + return result; + } + return Detail::Result::ok(); + } + + Detail::InternalParseResult + Parser::parse( std::string const& exeName, + Detail::TokenStream const& tokens ) const { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + std::vector parseInfos; + parseInfos.reserve( m_options.size() + m_args.size() ); + for ( auto const& opt : m_options ) { + parseInfos.push_back( { &opt, 0 } ); + } + for ( auto const& arg : m_args ) { + parseInfos.push_back( { &arg, 0 } ); + } + + m_exeName.set( exeName ); + + auto result = Detail::InternalParseResult::ok( + Detail::ParseState( ParseResultType::NoMatch, tokens ) ); + while ( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for ( auto& parseInfo : parseInfos ) { + if ( parseInfo.parser->cardinality() == 0 || + parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse( + exeName, result.value().remainingTokens() ); + if ( !result ) + return result; + if ( result.value().type() != + ParseResultType::NoMatch ) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if ( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if ( !tokenParsed ) + return Detail::InternalParseResult::runtimeError( + "Unrecognised token: " + + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; + } + + Args::Args(int argc, char const* const* argv) : + m_exeName(argv[0]), m_args(argv + 1, argv + argc) {} + + Args::Args(std::initializer_list args) : + m_exeName(*args.begin()), + m_args(args.begin() + 1, args.end()) {} + + + Help::Help( bool& showHelpFlag ): + Opt( [&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + } ) { + static_cast ( *this )( + "display usage information" )["-?"]["-h"]["--help"] + .optional(); + } + + } // namespace Clara +} // namespace Catch + + + + +#include +#include + +namespace Catch { + + Clara::Parser makeCommandLineParser( ConfigData& config ) { + + using namespace Clara; + + auto const setWarning = [&]( std::string const& warning ) { + if ( warning == "NoAssertions" ) { + config.warnings = static_cast(config.warnings | WarnAbout::NoAssertions); + return ParserResult::ok( ParseResultType::Matched ); + } else if ( warning == "UnmatchedTestSpec" ) { + config.warnings = static_cast(config.warnings | WarnAbout::UnmatchedTestSpec); + return ParserResult::ok( ParseResultType::Matched ); + } + + return ParserResult ::runtimeError( + "Unrecognised warning option: '" + warning + '\'' ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + '\'' ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line ); + config.testsOrTags.emplace_back( "," ); + } + } + //Remove comma in the end + if(!config.testsOrTags.empty()) + config.testsOrTags.erase( config.testsOrTags.end()-1 ); + + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = TestRunOrder::Declared; + else if( startsWith( "lexical", order ) ) + config.runOrder = TestRunOrder::LexicographicallySorted; + else if( startsWith( "random", order ) ) + config.runOrder = TestRunOrder::Randomized; + else + return ParserResult::runtimeError( "Unrecognised ordering: '" + order + '\'' ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = generateRandomSeed(GenerateFrom::Time); + return ParserResult::ok(ParseResultType::Matched); + } else if (seed == "random-device") { + config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice); + return ParserResult::ok(ParseResultType::Matched); + } + + CATCH_TRY { + std::size_t parsedTo = 0; + unsigned long parsedSeed = std::stoul(seed, &parsedTo, 0); + if (parsedTo != seed.size()) { + return ParserResult::runtimeError("Could not parse '" + seed + "' as seed"); + } + + // TODO: Ideally we could parse unsigned int directly, + // but the stdlib doesn't provide helper for that + // type. After this is refactored to use fixed size + // type, we should check the parsed value is in range + // of the underlying type. + config.rngSeed = static_cast(parsedSeed); + return ParserResult::ok(ParseResultType::Matched); + } CATCH_CATCH_ANON(std::exception const&) { + return ParserResult::runtimeError("Could not parse '" + seed + "' as seed"); + } + }; + auto const setDefaultColourMode = [&]( std::string const& colourMode ) { + Optional maybeMode = Catch::Detail::stringToColourMode(toLower( colourMode )); + if ( !maybeMode ) { + return ParserResult::runtimeError( + "colour mode must be one of: default, ansi, win32, " + "or none. '" + + colourMode + "' is not recognised" ); + } + auto mode = *maybeMode; + if ( !isColourImplAvailable( mode ) ) { + return ParserResult::runtimeError( + "colour mode '" + colourMode + + "' is not supported in this binary" ); + } + config.defaultColourMode = mode; + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if (keypressLc == "never") + config.waitForKeypress = WaitForKeypress::Never; + else if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + '\'' ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setReporter = [&]( std::string const& userReporterSpec ) { + if ( userReporterSpec.empty() ) { + return ParserResult::runtimeError( "Received empty reporter spec." ); + } + + Optional parsed = + parseReporterSpec( userReporterSpec ); + if ( !parsed ) { + return ParserResult::runtimeError( + "Could not parse reporter spec '" + userReporterSpec + + "'" ); + } + + auto const& reporterSpec = *parsed; + + IReporterRegistry::FactoryMap const& factories = + getRegistryHub().getReporterRegistry().getFactories(); + auto result = factories.find( reporterSpec.name() ); + + if ( result == factories.end() ) { + return ParserResult::runtimeError( + "Unrecognized reporter, '" + reporterSpec.name() + + "'. Check available with --list-reporters" ); + } + + + const bool hadOutputFile = reporterSpec.outputFile().some(); + config.reporterSpecifications.push_back( CATCH_MOVE( *parsed ) ); + // It would be enough to check this only once at the very end, but + // there is not a place where we could call this check, so do it + // every time it could fail. For valid inputs, this is still called + // at most once. + if (!hadOutputFile) { + int n_reporters_without_file = 0; + for (auto const& spec : config.reporterSpecifications) { + if (spec.outputFile().none()) { + n_reporters_without_file++; + } + } + if (n_reporters_without_file > 1) { + return ParserResult::runtimeError( "Only one reporter may have unspecified output file." ); + } + } + + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setShardCount = [&]( std::string const& shardCount ) { + CATCH_TRY{ + std::size_t parsedTo = 0; + int64_t parsedCount = std::stoll(shardCount, &parsedTo, 0); + if (parsedTo != shardCount.size()) { + return ParserResult::runtimeError("Could not parse '" + shardCount + "' as shard count"); + } + if (parsedCount <= 0) { + return ParserResult::runtimeError("Shard count must be a positive number"); + } + + config.shardCount = static_cast(parsedCount); + return ParserResult::ok(ParseResultType::Matched); + } CATCH_CATCH_ANON(std::exception const&) { + return ParserResult::runtimeError("Could not parse '" + shardCount + "' as shard count"); + } + }; + + auto const setShardIndex = [&](std::string const& shardIndex) { + CATCH_TRY{ + std::size_t parsedTo = 0; + int64_t parsedIndex = std::stoll(shardIndex, &parsedTo, 0); + if (parsedTo != shardIndex.size()) { + return ParserResult::runtimeError("Could not parse '" + shardIndex + "' as shard index"); + } + if (parsedIndex < 0) { + return ParserResult::runtimeError("Shard index must be a non-negative number"); + } + + config.shardIndex = static_cast(parsedIndex); + return ParserResult::ok(ParseResultType::Matched); + } CATCH_CATCH_ANON(std::exception const&) { + return ParserResult::runtimeError("Could not parse '" + shardIndex + "' as shard index"); + } + }; + + + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.defaultOutputFilename, "filename" ) + ["-o"]["--out"] + ( "default output filename" ) + | Opt( accept_many, setReporter, "name[::key=value]*" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( accept_many, setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( config.minDuration, "seconds" ) + ["-D"]["--min-duration"] + ( "show test durations for tests taking at least the given number of seconds" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTests ) + ["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all available reporters" ) + | Opt( config.listListeners ) + ["--list-listeners"] + ( "list all listeners" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|'random-device'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setDefaultColourMode, "ansi|win32|none|default" ) + ["--colour-mode"] + ( "what color mode should be used as default" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "never|start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.skipBenchmarks) + ["--skip-benchmarks"] + ( "disable running benchmarks") + | Opt( config.benchmarkSamples, "samples" ) + ["--benchmark-samples"] + ( "number of samples to collect (default: 100)" ) + | Opt( config.benchmarkResamples, "resamples" ) + ["--benchmark-resamples"] + ( "number of resamples for the bootstrap (default: 100000)" ) + | Opt( config.benchmarkConfidenceInterval, "confidence interval" ) + ["--benchmark-confidence-interval"] + ( "confidence interval for the bootstrap (between 0 and 1, default: 0.95)" ) + | Opt( config.benchmarkNoAnalysis ) + ["--benchmark-no-analysis"] + ( "perform only measurements; do not perform any analysis" ) + | Opt( config.benchmarkWarmupTime, "benchmarkWarmupTime" ) + ["--benchmark-warmup-time"] + ( "amount of time in milliseconds spent on warming up each test (default: 100)" ) + | Opt( setShardCount, "shard count" ) + ["--shard-count"] + ( "split the tests to execute into this many groups" ) + | Opt( setShardIndex, "shard index" ) + ["--shard-index"] + ( "index of the group of tests to execute (see --shard-count)" ) | + Opt( config.allowZeroTests ) + ["--allow-running-no-tests"] + ( "Treat 'No tests run' as a success" ) + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); + + return cli; + } + +} // end namespace Catch + + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + + + +#include +#include +#include + +namespace Catch { + + ColourImpl::~ColourImpl() = default; + + ColourImpl::ColourGuard ColourImpl::guardColour( Colour::Code colourCode ) { + return ColourGuard(colourCode, this ); + } + + void ColourImpl::ColourGuard::engageImpl( std::ostream& stream ) { + assert( &stream == &m_colourImpl->m_stream->stream() && + "Engaging colour guard for different stream than used by the " + "parent colour implementation" ); + static_cast( stream ); + + m_engaged = true; + m_colourImpl->use( m_code ); + } + + ColourImpl::ColourGuard::ColourGuard( Colour::Code code, + ColourImpl const* colour ): + m_colourImpl( colour ), m_code( code ) { + } + ColourImpl::ColourGuard::ColourGuard( ColourGuard&& rhs ) noexcept: + m_colourImpl( rhs.m_colourImpl ), + m_code( rhs.m_code ), + m_engaged( rhs.m_engaged ) { + rhs.m_engaged = false; + } + ColourImpl::ColourGuard& + ColourImpl::ColourGuard::operator=( ColourGuard&& rhs ) noexcept { + using std::swap; + swap( m_colourImpl, rhs.m_colourImpl ); + swap( m_code, rhs.m_code ); + swap( m_engaged, rhs.m_engaged ); + + return *this; + } + ColourImpl::ColourGuard::~ColourGuard() { + if ( m_engaged ) { + m_colourImpl->use( Colour::None ); + } + } + + ColourImpl::ColourGuard& + ColourImpl::ColourGuard::engage( std::ostream& stream ) & { + engageImpl( stream ); + return *this; + } + + ColourImpl::ColourGuard&& + ColourImpl::ColourGuard::engage( std::ostream& stream ) && { + engageImpl( stream ); + return CATCH_MOVE(*this); + } + + namespace { + //! A do-nothing implementation of colour, used as fallback for unknown + //! platforms, and when the user asks to deactivate all colours. + class NoColourImpl : public ColourImpl { + public: + NoColourImpl( IStream* stream ): ColourImpl( stream ) {} + + private: + void use( Colour::Code ) const override {} + }; + } // namespace + + +} // namespace Catch + + +#if defined ( CATCH_CONFIG_COLOUR_WIN32 ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public ColourImpl { + public: + Win32ColourImpl(IStream* stream): + ColourImpl(stream) { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), + &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + static bool useImplementationForStream(IStream const& stream) { + // Win32 text colour APIs can only be used on console streams + // We cannot check that the output hasn't been redirected, + // so we just check that the original stream is console stream. + return stream.isConsole(); + } + + private: + void use( Colour::Code _colourCode ) const override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); + } + } + + void setTextAttribute( WORD _textAttribute ) const { + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), + _textAttribute | + originalBackgroundAttributes ); + } + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + +} // end anon namespace +} // end namespace Catch + +#endif // Windows/ ANSI/ None + + +#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) +# define CATCH_INTERNAL_HAS_ISATTY +# include +#endif + +namespace Catch { +namespace { + + class ANSIColourImpl : public ColourImpl { + public: + ANSIColourImpl( IStream* stream ): ColourImpl( stream ) {} + + static bool useImplementationForStream(IStream const& stream) { + // This is kinda messy due to trying to support a bunch of + // different platforms at once. + // The basic idea is that if we are asked to do autodetection (as + // opposed to being told to use posixy colours outright), then we + // only want to use the colours if we are writing to console. + // However, console might be redirected, so we make an attempt at + // checking for that on platforms where we know how to do that. + bool useColour = stream.isConsole(); +#if defined( CATCH_INTERNAL_HAS_ISATTY ) && \ + !( defined( __DJGPP__ ) && defined( __STRICT_ANSI__ ) ) + ErrnoGuard _; // for isatty + useColour = useColour && isatty( STDOUT_FILENO ); +# endif +# if defined( CATCH_PLATFORM_MAC ) || defined( CATCH_PLATFORM_IPHONE ) + useColour = useColour && !isDebuggerActive(); +# endif + + return useColour; + } + + private: + void use( Colour::Code _colourCode ) const override { + auto setColour = [&out = + m_stream->stream()]( char const* escapeCode ) { + // The escape sequence must be flushed to console, otherwise + // if stdin and stderr are intermixed, we'd get accidentally + // coloured output. + out << '\033' << escapeCode << std::flush; + }; + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); + } + } + }; + +} // end anon namespace +} // end namespace Catch + +namespace Catch { + + Detail::unique_ptr makeColourImpl( ColourMode implSelection, + IStream* stream ) { +#if defined( CATCH_CONFIG_COLOUR_WIN32 ) + if ( implSelection == ColourMode::Win32 ) { + return Detail::make_unique( stream ); + } +#endif + if ( implSelection == ColourMode::ANSI ) { + return Detail::make_unique( stream ); + } + if ( implSelection == ColourMode::None ) { + return Detail::make_unique( stream ); + } + + if ( implSelection == ColourMode::PlatformDefault) { +#if defined( CATCH_CONFIG_COLOUR_WIN32 ) + if ( Win32ColourImpl::useImplementationForStream( *stream ) ) { + return Detail::make_unique( stream ); + } +#endif + if ( ANSIColourImpl::useImplementationForStream( *stream ) ) { + return Detail::make_unique( stream ); + } + return Detail::make_unique( stream ); + } + + CATCH_ERROR( "Could not create colour impl for selection " << static_cast(implSelection) ); + } + + bool isColourImplAvailable( ColourMode colourSelection ) { + switch ( colourSelection ) { +#if defined( CATCH_CONFIG_COLOUR_WIN32 ) + case ColourMode::Win32: +#endif + case ColourMode::ANSI: + case ColourMode::None: + case ColourMode::PlatformDefault: + return true; + default: + return false; + } + } + + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + + + + +namespace Catch { + + class Context : public IMutableContext, private Detail::NonCopyable { + + public: // IContext + IResultCapture* getResultCapture() override { + return m_resultCapture; + } + + IConfig const* getConfig() const override { + return m_config; + } + + ~Context() override; + + public: // IMutableContext + void setResultCapture( IResultCapture* resultCapture ) override { + m_resultCapture = resultCapture; + } + void setConfig( IConfig const* config ) override { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IConfig const* m_config = nullptr; + IResultCapture* m_resultCapture = nullptr; + }; + + IMutableContext *IMutableContext::currentContext = nullptr; + + void IMutableContext::createContext() + { + currentContext = new Context(); + } + + void cleanUpContext() { + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; + } + IContext::~IContext() = default; + IMutableContext::~IMutableContext() = default; + Context::~Context() = default; + + + SimplePcg32& sharedRng() { + static SimplePcg32 s_rng; + return s_rng; + } + +} + + + + + +#include + +#if defined(CATCH_CONFIG_ANDROID_LOGWRITE) +#include + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + __android_log_write( ANDROID_LOG_DEBUG, "Catch", text.c_str() ); + } + } + +#elif defined(CATCH_PLATFORM_WINDOWS) + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } + +#else + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } + +#endif // Platform + + + +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) + +# include +# include +# include +# include +# include + +#ifdef __apple_build_version__ + // These headers will only compile with AppleClang (XCode) + // For other compilers (Clang, GCC, ... ) we need to exclude them +# include +#endif + + namespace Catch { + #ifdef __apple_build_version__ + // The following function is taken directly from the following technical note: + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + int mib[4]; + struct kinfo_proc info; + std::size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n\n" << std::flush; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + #else + bool isDebuggerActive() { + // We need to find another way to determine this for non-appleclang compilers on macOS + return false; + } + #endif + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include + #include + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform + + + + +namespace Catch { + + ITransientExpression::~ITransientExpression() = default; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << ' ' << op << ' ' << rhs; + else + os << lhs << '\n' << op << '\n' << rhs; + } +} + + + +#include + + +namespace Catch { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) + [[noreturn]] + void throw_exception(std::exception const& e) { + Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); + } +#endif + + [[noreturn]] + void throw_logic_error(std::string const& msg) { + throw_exception(std::logic_error(msg)); + } + + [[noreturn]] + void throw_domain_error(std::string const& msg) { + throw_exception(std::domain_error(msg)); + } + + [[noreturn]] + void throw_runtime_error(std::string const& msg) { + throw_exception(std::runtime_error(msg)); + } + + + +} // namespace Catch; + + + +#include + +namespace Catch { + + IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() = default; + + namespace Detail { + + namespace { + // Extracts the actual name part of an enum instance + // In other words, it returns the Blue part of Bikeshed::Colour::Blue + StringRef extractInstanceName(StringRef enumInstance) { + // Find last occurrence of ":" + size_t name_start = enumInstance.size(); + while (name_start > 0 && enumInstance[name_start - 1] != ':') { + --name_start; + } + return enumInstance.substr(name_start, enumInstance.size() - name_start); + } + } + + std::vector parseEnums( StringRef enums ) { + auto enumValues = splitStringRef( enums, ',' ); + std::vector parsed; + parsed.reserve( enumValues.size() ); + for( auto const& enumValue : enumValues ) { + parsed.push_back(trim(extractInstanceName(enumValue))); + } + return parsed; + } + + EnumInfo::~EnumInfo() {} + + StringRef EnumInfo::lookup( int value ) const { + for( auto const& valueToName : m_values ) { + if( valueToName.first == value ) + return valueToName.second; + } + return "{** unexpected enum value **}"_sr; + } + + Catch::Detail::unique_ptr makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector const& values ) { + auto enumInfo = Catch::Detail::make_unique(); + enumInfo->m_name = enumName; + enumInfo->m_values.reserve( values.size() ); + + const auto valueNames = Catch::Detail::parseEnums( allValueNames ); + assert( valueNames.size() == values.size() ); + std::size_t i = 0; + for( auto value : values ) + enumInfo->m_values.emplace_back(value, valueNames[i++]); + + return enumInfo; + } + + EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector const& values ) { + m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values)); + return *m_enumInfos.back(); + } + + } // Detail +} // Catch + + + + + +#include + +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} + + + +namespace Catch { + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { + } + + void ExceptionTranslatorRegistry::registerTranslator( Detail::unique_ptr&& translator ) { + m_translators.push_back( CATCH_MOVE( translator ) ); + } + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + std::string ExceptionTranslatorRegistry::translateActiveException() const { + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these do + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if ( std::current_exception() == nullptr ) { + return "Non C++ exception. Possibly a CLR exception."; + } + + // First we try user-registered translators. If none of them can + // handle the exception, it will be rethrown handled by our defaults. + try { + return tryTranslators(); + } + // To avoid having to handle TFE explicitly everywhere, we just + // rethrow it so that it goes back up the caller. + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception const& ex ) { + return ex.what(); + } + catch( std::string const& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if (m_translators.empty()) { + std::rethrow_exception(std::current_exception()); + } else { + return m_translators[0]->translate(m_translators.begin() + 1, m_translators.end()); + } + } + +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + std::string ExceptionTranslatorRegistry::translateActiveException() const { + CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + CATCH_INTERNAL_ERROR("Attempted to use exception translators under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } +#endif + + +} + + + +/** \file + * This file provides platform specific implementations of FatalConditionHandler + * + * This means that there is a lot of conditional compilation, and platform + * specific code. Currently, Catch2 supports a dummy handler (if no + * handler is desired), and 2 platform specific handlers: + * * Windows' SEH + * * POSIX signals + * + * Consequently, various pieces of code below are compiled if either of + * the platform specific handlers is enabled, or if none of them are + * enabled. It is assumed that both cannot be enabled at the same time, + * and doing so should cause a compilation error. + * + * If another platform specific handler is added, the compile guards + * below will need to be updated taking these assumptions into account. + */ + + + +#include + +#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + // If neither SEH nor signal handling is required, the handler impls + // do not have to do anything, and can be empty. + void FatalConditionHandler::engage_platform() {} + void FatalConditionHandler::disengage_platform() noexcept {} + FatalConditionHandler::FatalConditionHandler() = default; + FatalConditionHandler::~FatalConditionHandler() = default; + +} // end namespace Catch + +#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) +#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" +#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace { + //! Signals fatal error message to the run context + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); + } + + //! Minimal size Catch2 needs for its own fatal error handling. + //! Picked empirically, so it might not be sufficient on all + //! platforms, and for all configurations. + constexpr std::size_t minStackSizeForErrors = 32 * 1024; +} // end unnamed namespace + +#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const& def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; + + + // For MSVC, we reserve part of the stack memory for handling + // memory overflow structured exception. + FatalConditionHandler::FatalConditionHandler() { + ULONG guaranteeSize = static_cast(minStackSizeForErrors); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; + } + } + + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + FatalConditionHandler::~FatalConditionHandler() = default; + + + void FatalConditionHandler::engage_platform() { + // Register as a the top level exception filter. + previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter); + } + + void FatalConditionHandler::disengage_platform() noexcept { + if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) { + Catch::cerr() + << "Unexpected SEH unhandled exception filter on disengage." + << " The filter was restored, but might be rolled back unexpectedly."; + } + previousTopLevelExceptionFilter = nullptr; + } + +} // end namespace Catch + +#endif // CATCH_CONFIG_WINDOWS_SEH + +#if defined( CATCH_CONFIG_POSIX_SIGNALS ) + +#include + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + + static char* altStackMem = nullptr; + static std::size_t altStackSize = 0; + static stack_t oldSigStack{}; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; + + static void restorePreviousSignalHandlers() noexcept { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + } + + static void handleSignal( int sig ) { + char const * name = ""; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } + } + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal( name ); + raise( sig ); + } + + FatalConditionHandler::FatalConditionHandler() { + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = std::max(static_cast(SIGSTKSZ), minStackSizeForErrors); + } + altStackMem = new char[altStackSize](); + } + + FatalConditionHandler::~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = nullptr; + } + + void FatalConditionHandler::engage_platform() { + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = altStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + + void FatalConditionHandler::disengage_platform() noexcept { + restorePreviousSignalHandlers(); + } + +} // end namespace Catch + +#endif // CATCH_CONFIG_POSIX_SIGNALS + + + + +#include + +namespace Catch { + namespace Detail { + + uint32_t convertToBits(float f) { + static_assert(sizeof(float) == sizeof(uint32_t), "Important ULP matcher assumption violated"); + uint32_t i; + std::memcpy(&i, &f, sizeof(f)); + return i; + } + + uint64_t convertToBits(double d) { + static_assert(sizeof(double) == sizeof(uint64_t), "Important ULP matcher assumption violated"); + uint64_t i; + std::memcpy(&i, &d, sizeof(d)); + return i; + } + + } // end namespace Detail +} // end namespace Catch + + + + + +#include +#include +#include +#include + +namespace Catch { + + Catch::IStream::~IStream() = default; + +namespace Detail { + namespace { + template + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept override { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const& str ) { + if ( !str.empty() ) { + writeToDebugConsole( str ); + } + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream : public IStream { + std::ofstream m_ofs; + public: + FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << '\'' ); + m_ofs << std::unitbuf; + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() override { return m_os; } + bool isConsole() const override { return true; } + }; + + class CerrStream : public IStream { + std::ostream m_os; + + public: + // Store the streambuf from cerr up-front because + // cout may get redirected when running tests + CerrStream(): m_os( Catch::cerr().rdbuf() ) {} + ~CerrStream() override = default; + + public: // IStream + std::ostream& stream() override { return m_os; } + bool isConsole() const override { return true; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + Detail::unique_ptr> m_streamBuf; + std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( Detail::make_unique>() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() override { return m_os; } + }; + + } // unnamed namespace +} // namespace Detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( std::string const& filename ) -> Detail::unique_ptr { + if ( filename.empty() || filename == "-" ) { + return Detail::make_unique(); + } + if( filename[0] == '%' ) { + if ( filename == "%debug" ) { + return Detail::make_unique(); + } else if ( filename == "%stderr" ) { + return Detail::make_unique(); + } else if ( filename == "%stdout" ) { + return Detail::make_unique(); + } else { + CATCH_ERROR( "Unrecognised stream: '" << filename << '\'' ); + } + } + return Detail::make_unique( filename ); + } + +} + + + + +namespace Catch { + + auto operator << (std::ostream& os, LazyExpression const& lazyExpr) -> std::ostream& { + if (lazyExpr.m_isNegated) + os << '!'; + + if (lazyExpr) { + if (lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression()) + os << '(' << *lazyExpr.m_transientExpression << ')'; + else + os << *lazyExpr.m_transientExpression; + } else { + os << "{** error - unchecked empty expression requested **}"; + } + return os; + } + +} // namespace Catch + + + + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} + +#else // ^^ Windows crt debug heap enabled // Windows crt debug heap disabled vv + + Catch::LeakDetector::LeakDetector() {} + +#endif // CATCH_CONFIG_WINDOWS_CRTDBG + +Catch::LeakDetector::~LeakDetector() { + Catch::cleanUp(); +} + + + + + +namespace Catch { + namespace { + + void listTests(IEventListener& reporter, IConfig const& config) { + auto const& testSpec = config.testSpec(); + auto matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); + reporter.listTests(matchedTestCases); + } + + void listTags(IEventListener& reporter, IConfig const& config) { + auto const& testSpec = config.testSpec(); + std::vector matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); + + std::map tagCounts; + for (auto const& testCase : matchedTestCases) { + for (auto const& tagName : testCase.getTestCaseInfo().tags) { + auto it = tagCounts.find(tagName.original); + if (it == tagCounts.end()) + it = tagCounts.insert(std::make_pair(tagName.original, TagInfo())).first; + it->second.add(tagName.original); + } + } + + std::vector infos; infos.reserve(tagCounts.size()); + for (auto& tagc : tagCounts) { + infos.push_back(CATCH_MOVE(tagc.second)); + } + + reporter.listTags(infos); + } + + void listReporters(IEventListener& reporter) { + std::vector descriptions; + + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + descriptions.reserve(factories.size()); + for (auto const& fac : factories) { + descriptions.push_back({ fac.first, fac.second->getDescription() }); + } + + reporter.listReporters(descriptions); + } + + void listListeners(IEventListener& reporter) { + std::vector descriptions; + + auto const& factories = + getRegistryHub().getReporterRegistry().getListeners(); + descriptions.reserve( factories.size() ); + for ( auto const& fac : factories ) { + descriptions.push_back( { fac->getName(), fac->getDescription() } ); + } + + reporter.listListeners( descriptions ); + } + + } // end anonymous namespace + + void TagInfo::add( StringRef spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + // 2 per tag for brackets '[' and ']' + size_t size = spellings.size() * 2; + for (auto const& spelling : spellings) { + size += spelling.size(); + } + + std::string out; out.reserve(size); + for (auto const& spelling : spellings) { + out += '['; + out += spelling; + out += ']'; + } + return out; + } + + bool list( IEventListener& reporter, Config const& config ) { + bool listed = false; + if (config.listTests()) { + listed = true; + listTests(reporter, config); + } + if (config.listTags()) { + listed = true; + listTags(reporter, config); + } + if (config.listReporters()) { + listed = true; + listReporters(reporter); + } + if ( config.listListeners() ) { + listed = true; + listListeners( reporter ); + } + return listed; + } + +} // end namespace Catch + + + +namespace Catch { + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + static LeakDetector leakDetector; + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +} + +// Allow users of amalgamated .cpp file to remove our main and provide their own. +#if !defined(CATCH_AMALGAMATED_CUSTOM_MAIN) + +#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int __cdecl wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + // We want to force the linker not to discard the global variable + // and its constructor, as it (optionally) registers leak detector + (void)&Catch::leakDetector; + + return Catch::Session().run( argc, argv ); +} + +#endif // !defined(CATCH_AMALGAMATED_CUSTOM_MAIN + + + + +namespace Catch { + + MessageInfo::MessageInfo( StringRef _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + +} // end namespace Catch + + + +#include +#include +#include + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #include //_dup and _dup2 + #define dup _dup + #define dup2 _dup2 + #define fileno _fileno + #else + #include // dup and dup2 + #endif +#endif + + +namespace Catch { + + RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) + : m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) + { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + + RedirectedStream::~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); + } + + RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + + RedirectedStdErr::RedirectedStdErr() + : m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) + {} + auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + + RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr) + : m_redirectedCout(redirectedCout), + m_redirectedCerr(redirectedCerr) + {} + + RedirectedStreams::~RedirectedStreams() { + m_redirectedCout += m_redirectedStdOut.str(); + m_redirectedCerr += m_redirectedStdErr.str(); + } + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +#if defined(_MSC_VER) + TempFile::TempFile() { + if (tmpnam_s(m_buffer)) { + CATCH_RUNTIME_ERROR("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w+")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + CATCH_RUNTIME_ERROR("Could not translate errno to a string"); + } + CATCH_RUNTIME_ERROR("Could not open the temp file: '" << m_buffer << "' because: " << buffer); + } + } +#else + TempFile::TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + CATCH_RUNTIME_ERROR("Could not create a temp file."); + } + } + +#endif + + TempFile::~TempFile() { + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif + } + + + FILE* TempFile::getFile() { + return m_file; + } + + std::string TempFile::getContents() { + std::stringstream sstr; + char buffer[100] = {}; + std::rewind(m_file); + while (std::fgets(buffer, sizeof(buffer), m_file)) { + sstr << buffer; + } + return sstr.str(); + } + + OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : + m_originalStdout(dup(1)), + m_originalStderr(dup(2)), + m_stdoutDest(stdout_dest), + m_stderrDest(stderr_dest) { + dup2(fileno(m_stdoutFile.getFile()), 1); + dup2(fileno(m_stderrFile.getFile()), 2); + } + + OutputRedirect::~OutputRedirect() { + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); + + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); + } + +#endif // CATCH_CONFIG_NEW_CAPTURE + +} // namespace Catch + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #undef dup + #undef dup2 + #undef fileno + #endif +#endif + + + + +#include + +namespace Catch { + +#if !defined(CATCH_CONFIG_POLYFILL_ISNAN) + bool isnan(float f) { + return std::isnan(f); + } + bool isnan(double d) { + return std::isnan(d); + } +#else + // For now we only use this for embarcadero + bool isnan(float f) { + return std::_isnan(f); + } + bool isnan(double d) { + return std::_isnan(d); + } +#endif + +} // end namespace Catch + + + +namespace Catch { + +namespace { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4146) // we negate uint32 during the rotate +#endif + // Safe rotr implementation thanks to John Regehr + uint32_t rotate_right(uint32_t val, uint32_t count) { + const uint32_t mask = 31; + count &= mask; + return (val >> count) | (val << (-count & mask)); + } + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +} + + + SimplePcg32::SimplePcg32(result_type seed_) { + seed(seed_); + } + + + void SimplePcg32::seed(result_type seed_) { + m_state = 0; + (*this)(); + m_state += seed_; + (*this)(); + } + + void SimplePcg32::discard(uint64_t skip) { + // We could implement this to run in O(log n) steps, but this + // should suffice for our use case. + for (uint64_t s = 0; s < skip; ++s) { + static_cast((*this)()); + } + } + + SimplePcg32::result_type SimplePcg32::operator()() { + // prepare the output value + const uint32_t xorshifted = static_cast(((m_state >> 18u) ^ m_state) >> 27u); + const auto output = rotate_right(xorshifted, m_state >> 59u); + + // advance state + m_state = m_state * 6364136223846793005ULL + s_inc; + + return output; + } + + bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state == rhs.m_state; + } + + bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state != rhs.m_state; + } +} + + + + + +#include +#include + +namespace Catch { + + std::uint32_t generateRandomSeed( GenerateFrom from ) { + switch ( from ) { + case GenerateFrom::Time: + return static_cast( std::time( nullptr ) ); + + case GenerateFrom::Default: + case GenerateFrom::RandomDevice: + // In theory, a platform could have random_device that returns just + // 16 bits. That is still some randomness, so we don't care too much + return static_cast( std::random_device{}() ); + + default: + CATCH_ERROR("Unknown generation method"); + } + } + +} // end namespace Catch + + + + +namespace Catch { + + ReporterRegistry::ReporterRegistry() { + // Because it is impossible to move out of initializer list, + // we have to add the elements manually + m_factories["Automake"] = Detail::make_unique>(); + m_factories["compact"] = Detail::make_unique>(); + m_factories["console"] = Detail::make_unique>(); + m_factories["JUnit"] = Detail::make_unique>(); + m_factories["SonarQube"] = Detail::make_unique>(); + m_factories["TAP"] = Detail::make_unique>(); + m_factories["TeamCity"] = Detail::make_unique>(); + m_factories["XML"] = Detail::make_unique>(); + } + + ReporterRegistry::~ReporterRegistry() = default; + + + IEventListenerPtr ReporterRegistry::create( std::string const& name, ReporterConfig&& config ) const { + auto it = m_factories.find( name ); + if( it == m_factories.end() ) + return nullptr; + return it->second->create( CATCH_MOVE(config) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr factory ) { + CATCH_ENFORCE( name.find( "::" ) == name.npos, + "'::' is not allowed in reporter name: '" + name + '\'' ); + auto ret = m_factories.emplace(name, CATCH_MOVE(factory)); + CATCH_ENFORCE( ret.second, "reporter using '" + name + "' as name was already registered" ); + } + void ReporterRegistry::registerListener( + Detail::unique_ptr factory ) { + m_listeners.push_back( CATCH_MOVE(factory) ); + } + + IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { + return m_factories; + } + IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { + return m_listeners; + } + +} + + + + + +#include + +namespace Catch { + + namespace { + struct kvPair { + StringRef key, value; + }; + + kvPair splitKVPair(StringRef kvString) { + auto splitPos = static_cast( std::distance( + kvString.begin(), + std::find( kvString.begin(), kvString.end(), '=' ) ) ); + + return { kvString.substr( 0, splitPos ), + kvString.substr( splitPos + 1, kvString.size() ) }; + } + } + + namespace Detail { + std::vector splitReporterSpec( StringRef reporterSpec ) { + static constexpr auto separator = "::"; + static constexpr size_t separatorSize = 2; + + size_t separatorPos = 0; + auto findNextSeparator = [&reporterSpec]( size_t startPos ) { + static_assert( + separatorSize == 2, + "The code below currently assumes 2 char separator" ); + + auto currentPos = startPos; + do { + while ( currentPos < reporterSpec.size() && + reporterSpec[currentPos] != separator[0] ) { + ++currentPos; + } + if ( currentPos + 1 < reporterSpec.size() && + reporterSpec[currentPos + 1] == separator[1] ) { + return currentPos; + } + ++currentPos; + } while ( currentPos < reporterSpec.size() ); + + return static_cast( -1 ); + }; + + std::vector parts; + + while ( separatorPos < reporterSpec.size() ) { + const auto nextSeparator = findNextSeparator( separatorPos ); + parts.push_back( static_cast( reporterSpec.substr( + separatorPos, nextSeparator - separatorPos ) ) ); + + if ( nextSeparator == static_cast( -1 ) ) { + break; + } + separatorPos = nextSeparator + separatorSize; + } + + // Handle a separator at the end. + // This is not a valid spec, but we want to do validation in a + // centralized place + if ( separatorPos == reporterSpec.size() ) { + parts.emplace_back(); + } + + return parts; + } + + Optional stringToColourMode( StringRef colourMode ) { + if ( colourMode == "default" ) { + return ColourMode::PlatformDefault; + } else if ( colourMode == "ansi" ) { + return ColourMode::ANSI; + } else if ( colourMode == "win32" ) { + return ColourMode::Win32; + } else if ( colourMode == "none" ) { + return ColourMode::None; + } else { + return {}; + } + } + } // namespace Detail + + + bool operator==( ReporterSpec const& lhs, ReporterSpec const& rhs ) { + return lhs.m_name == rhs.m_name && + lhs.m_outputFileName == rhs.m_outputFileName && + lhs.m_colourMode == rhs.m_colourMode && + lhs.m_customOptions == rhs.m_customOptions; + } + + Optional parseReporterSpec( StringRef reporterSpec ) { + auto parts = Detail::splitReporterSpec( reporterSpec ); + + assert( parts.size() > 0 && "Split should never return empty vector" ); + + std::map kvPairs; + Optional outputFileName; + Optional colourMode; + + // First part is always reporter name, so we skip it + for ( size_t i = 1; i < parts.size(); ++i ) { + auto kv = splitKVPair( parts[i] ); + auto key = kv.key, value = kv.value; + + if ( key.empty() || value.empty() ) { + return {}; + } else if ( key[0] == 'X' ) { + // This is a reporter-specific option, we don't check these + // apart from basic sanity checks + if ( key.size() == 1 ) { + return {}; + } + + auto ret = kvPairs.emplace( std::string(kv.key), std::string(kv.value) ); + if ( !ret.second ) { + // Duplicated key. We might want to handle this differently, + // e.g. by overwriting the existing value? + return {}; + } + } else if ( key == "out" ) { + // Duplicated key + if ( outputFileName ) { + return {}; + } + outputFileName = static_cast( value ); + } else if ( key == "colour-mode" ) { + // Duplicated key + if ( colourMode ) { + return {}; + } + colourMode = Detail::stringToColourMode( value ); + // Parsing failed + if ( !colourMode ) { + return {}; + } + } else { + // Unrecognized option + return {}; + } + } + + return ReporterSpec{ CATCH_MOVE( parts[0] ), + CATCH_MOVE( outputFileName ), + CATCH_MOVE( colourMode ), + CATCH_MOVE( kvPairs ) }; + } + +ReporterSpec::ReporterSpec( + std::string name, + Optional outputFileName, + Optional colourMode, + std::map customOptions ): + m_name( CATCH_MOVE( name ) ), + m_outputFileName( CATCH_MOVE( outputFileName ) ), + m_colourMode( CATCH_MOVE( colourMode ) ), + m_customOptions( CATCH_MOVE( customOptions ) ) {} + +} // namespace Catch + + + +namespace Catch { + + bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + + + +#include +#include +#include + +namespace Catch { + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector> m_streams; + std::vector m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( Detail::make_unique() ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + }; + + ReusableStringStream::ReusableStringStream() + : m_index( Singleton::getMutable().add() ), + m_oss( Singleton::getMutable().m_streams[m_index].get() ) + {} + + ReusableStringStream::~ReusableStringStream() { + static_cast( m_oss )->str(""); + m_oss->clear(); + Singleton::getMutable().release( m_index ); + } + + std::string ReusableStringStream::str() const { + return static_cast( m_oss )->str(); + } + + void ReusableStringStream::str( std::string const& str ) { + static_cast( m_oss )->str( str ); + } + + +} + + + + +#include +#include + +namespace Catch { + + namespace Generators { + struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { + GeneratorBasePtr m_generator; + + GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + {} + ~GeneratorTracker() override; + + static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { + GeneratorTracker* tracker; + + ITracker& currentTracker = ctx.currentTracker(); + // Under specific circumstances, the generator we want + // to acquire is also the current tracker. If this is + // the case, we have to avoid looking through current + // tracker's children, and instead return the current + // tracker. + // A case where this check is important is e.g. + // for (int i = 0; i < 5; ++i) { + // int n = GENERATE(1, 2); + // } + // + // without it, the code above creates 5 nested generators. + if ( currentTracker.nameAndLocation() == nameAndLocation ) { + auto thisTracker = + currentTracker.parent()->findChild( nameAndLocation ); + assert( thisTracker ); + assert( thisTracker->isGeneratorTracker() ); + tracker = static_cast( thisTracker ); + } else if ( ITracker* childTracker = + currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isGeneratorTracker() ); + tracker = static_cast( childTracker ); + } else { + auto newTracker = + Catch::Detail::make_unique( + nameAndLocation, ctx, ¤tTracker ); + tracker = newTracker.get(); + currentTracker.addChild( CATCH_MOVE(newTracker) ); + } + + if( !tracker->isComplete() ) { + tracker->open(); + } + + return *tracker; + } + + // TrackerBase interface + bool isGeneratorTracker() const override { return true; } + auto hasGenerator() const -> bool override { + return !!m_generator; + } + void close() override { + TrackerBase::close(); + // If a generator has a child (it is followed by a section) + // and none of its children have started, then we must wait + // until later to start consuming its values. + // This catches cases where `GENERATE` is placed between two + // `SECTION`s. + // **The check for m_children.empty cannot be removed**. + // doing so would break `GENERATE` _not_ followed by `SECTION`s. + const bool should_wait_for_child = [&]() { + // No children -> nobody to wait for + if ( m_children.empty() ) { + return false; + } + // If at least one child started executing, don't wait + if ( std::find_if( + m_children.begin(), + m_children.end(), + []( TestCaseTracking::ITrackerPtr const& tracker ) { + return tracker->hasStarted(); + } ) != m_children.end() ) { + return false; + } + + // No children have started. We need to check if they _can_ + // start, and thus we should wait for them, or they cannot + // start (due to filters), and we shouldn't wait for them + ITracker* parent = m_parent; + // This is safe: there is always at least one section + // tracker in a test case tracking tree + while ( !parent->isSectionTracker() ) { + parent = parent->parent(); + } + assert( parent && + "Missing root (test case) level section" ); + + auto const& parentSection = + static_cast( *parent ); + auto const& filters = parentSection.getFilters(); + // No filters -> no restrictions on running sections + if ( filters.empty() ) { + return true; + } + + for ( auto const& child : m_children ) { + if ( child->isSectionTracker() && + std::find( + filters.begin(), + filters.end(), + static_cast( *child ) + .trimmedName() ) != filters.end() ) { + return true; + } + } + return false; + }(); + + // This check is a bit tricky, because m_generator->next() + // has a side-effect, where it consumes generator's current + // value, but we do not want to invoke the side-effect if + // this generator is still waiting for any child to start. + if ( should_wait_for_child || + ( m_runState == CompletedSuccessfully && + m_generator->countedNext() ) ) { + m_children.clear(); + m_runState = Executing; + } + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const& override { + return m_generator; + } + void setGenerator( GeneratorBasePtr&& generator ) override { + m_generator = CATCH_MOVE( generator ); + } + }; + GeneratorTracker::~GeneratorTracker() = default; + } + + RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(CATCH_MOVE(reporter)), + m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + { + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + Totals RunContext::runTest(TestCaseHandle const& testCase) { + const Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + auto const& testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + + // We intentionally only seed the internal RNG once per test case, + // before it is first invoked. The reason for that is a complex + // interplay of generator/section implementation details and the + // Random*Generator types. + // + // The issue boils down to us needing to seed the Random*Generators + // with different seed each, so that they return different sequences + // of random numbers. We do this by giving them a number from the + // shared RNG instance as their seed. + // + // However, this runs into an issue if the reseeding happens each + // time the test case is entered (as opposed to first time only), + // because multiple generators could get the same seed, e.g. in + // ```cpp + // TEST_CASE() { + // auto i = GENERATE(take(10, random(0, 100)); + // SECTION("A") { + // auto j = GENERATE(take(10, random(0, 100)); + // } + // SECTION("B") { + // auto k = GENERATE(take(10, random(0, 100)); + // } + // } + // ``` + // `i` and `j` would properly return values from different sequences, + // but `i` and `k` would return the same sequence, because their seed + // would be the same. + // (The reason their seeds would be the same is that the generator + // for k would be initialized when the test case is entered the second + // time, after the shared RNG instance was reset to the same value + // it had when the generator for i was initialized.) + seedRng( *m_config ); + + uint64_t testRuns = 0; + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + + m_reporter->testCasePartialStarting(testInfo, testRuns); + + const auto beforeRunTotals = m_totals; + std::string oneRunCout, oneRunCerr; + runCurrentTest(oneRunCout, oneRunCerr); + redirectedCout += oneRunCout; + redirectedCerr += oneRunCerr; + + const auto singleRunTotals = m_totals.delta(beforeRunTotals); + auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, oneRunCout, oneRunCerr, aborting()); + + m_reporter->testCasePartialEnded(statsForOneRun, testRuns); + ++testRuns; + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + + void RunContext::assertionEnded(AssertionResult const & result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } else if (!result.succeeded()) { + m_lastAssertionPassed = false; + if (result.isOk()) { + } + else if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else { + m_lastAssertionPassed = true; + } + + m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)); + + if (result.getResultType() != ResultWas::Warning) + m_messageScopes.clear(); + + // Reset working state + resetAssertionInfo(); + m_lastResult = result; + } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + } + + bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { + ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; + } + auto RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + using namespace Generators; + GeneratorTracker& tracker = GeneratorTracker::acquire(m_trackerContext, + TestCaseTracking::NameAndLocation( static_cast(generatorName), lineInfo ) ); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; + } + + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo const & endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); + m_messageScopes.clear(); + } + + void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); + } + + void RunContext::benchmarkPreparing( StringRef name ) { + m_reporter->benchmarkPreparing(name); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) { + m_reporter->benchmarkEnded( stats ); + } + void RunContext::benchmarkFailed( StringRef error ) { + m_reporter->benchmarkFailed( error ); + } + + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); + } + + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); + } + + void RunContext::emplaceUnscopedMessage( MessageBuilder const& builder ) { + m_messageScopes.emplace_back( builder ); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition( StringRef message ) { + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); + tempResult.message = static_cast(message); + AssertionResult result(m_lastAssertionInfo, tempResult); + + assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return m_lastAssertionPassed; + } + + void RunContext::assertionPassed() { + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); + m_messageScopes.clear(); + } + + bool RunContext::aborting() const { + return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); + } + + void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + + Timer timer; + CATCH_TRY { + if (m_reporter->getPreferences().shouldRedirectStdOut) { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) + RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr); + + timer.start(); + invokeActiveTestCase(); +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif + } else { + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } CATCH_CATCH_ANON (TestFailureException&) { + // This just means the test was aborted due to failure + } CATCH_CATCH_ALL { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if( m_shouldReportUnexpected ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + } + } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + m_messageScopes.clear(); + + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + // We need to engage a handler for signals/structured exceptions + // before running the tests themselves, or the binary can crash + // without failed test being reported. + FatalConditionHandlerGuard _(&m_fatalConditionhandler); + // We keep having issue where some compilers warn about an unused + // variable, even though the type has non-trivial constructor and + // destructor. This is annoying and ugly, but it makes them stfu. + (void)_; + + m_activeTestCase->invoke(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { + + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); + + AssertionResult assertionResult{ info, data }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( assertionResult ); + } + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef message, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = static_cast(message); + AssertionResult assertionResult{ m_lastAssertionInfo, data }; + assertionEnded( assertionResult ); + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + populateReaction( reaction ); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } + + void seedRng(IConfig const& config) { + sharedRng().seed(config.rngSeed()); + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + +} + + + +namespace Catch { + + Section::Section( SectionInfo&& info ): + m_info( CATCH_MOVE( info ) ), + m_sectionIncluded( + getResultCapture().sectionStarted( m_info, m_assertions ) ) { + // Non-"included" sections will not use the timing information + // anyway, so don't bother with the potential syscall. + if (m_sectionIncluded) { + m_timer.start(); + } + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() }; + if( uncaught_exceptions() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + + +} // end namespace Catch + + + +#include + +namespace Catch { + + namespace { + static auto getSingletons() -> std::vector*& { + static std::vector* g_singletons = nullptr; + if( !g_singletons ) + g_singletons = new std::vector(); + return g_singletons; + } + } + + ISingleton::~ISingleton() = default; + + void addSingleton(ISingleton* singleton ) { + getSingletons()->push_back( singleton ); + } + void cleanupSingletons() { + auto& singletons = getSingletons(); + for( auto singleton : *singletons ) + delete singleton; + delete singletons; + singletons = nullptr; + } + +} // namespace Catch + + + +#include +#include + +namespace Catch { + + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + +} // end namespace Catch + + + + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + CATCH_TRY { + m_exceptions.push_back(exception); + } CATCH_CATCH_ALL { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } +#endif + +} // end namespace Catch + + + + + +#include + +namespace Catch { + +// If you #define this you must implement these functions +#if !defined( CATCH_CONFIG_NOSTDOUT ) + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } +#endif + +} // namespace Catch + + + +#include +#include +#include +#include +#include + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( StringRef s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), []( char c ) { + return toLower( c ); + } ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + char toLower(char c) { + return static_cast(std::tolower(static_cast(c))); + } + + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + StringRef trim(StringRef ref) { + const auto is_ws = [](char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + }; + size_t real_begin = 0; + while (real_begin < ref.size() && is_ws(ref[real_begin])) { ++real_begin; } + size_t real_end = ref.size(); + while (real_end > real_begin && is_ws(ref[real_end - 1])) { --real_end; } + + return ref.substr(real_begin, real_end - real_begin); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + std::vector splitStringRef( StringRef str, char delimiter ) { + std::vector subStrings; + std::size_t start = 0; + for(std::size_t pos = 0; pos < str.size(); ++pos ) { + if( str[pos] == delimiter ) { + if( pos - start > 1 ) + subStrings.push_back( str.substr( start, pos-start ) ); + start = pos+1; + } + } + if( start < str.size() ) + subStrings.push_back( str.substr( start, str.size()-start ) ); + return subStrings; + } + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + +} + + + +#include +#include +#include +#include + +namespace Catch { + StringRef::StringRef( char const* rawChars ) noexcept + : StringRef( rawChars, std::strlen(rawChars) ) + {} + + auto StringRef::operator == ( StringRef other ) const noexcept -> bool { + return m_size == other.m_size + && (std::memcmp( m_start, other.m_start, m_size ) == 0); + } + + bool StringRef::operator<(StringRef rhs) const noexcept { + if (m_size < rhs.m_size) { + return strncmp(m_start, rhs.m_start, m_size) <= 0; + } + return strncmp(m_start, rhs.m_start, rhs.m_size) < 0; + } + + int StringRef::compare( StringRef rhs ) const { + auto cmpResult = + strncmp( m_start, rhs.m_start, std::min( m_size, rhs.m_size ) ); + + // This means that strncmp found a difference before the strings + // ended, and we can return it directly + if ( cmpResult != 0 ) { + return cmpResult; + } + + // If strings are equal up to length, then their comparison results on + // their size + if ( m_size < rhs.m_size ) { + return -1; + } else if ( m_size > rhs.m_size ) { + return 1; + } else { + return 0; + } + } + + auto operator << ( std::ostream& os, StringRef str ) -> std::ostream& { + return os.write(str.data(), static_cast(str.size())); + } + + std::string operator+(StringRef lhs, StringRef rhs) { + std::string ret; + ret.reserve(lhs.size() + rhs.size()); + ret += lhs; + ret += rhs; + return ret; + } + + auto operator+=( std::string& lhs, StringRef rhs ) -> std::string& { + lhs.append(rhs.data(), rhs.size()); + return lhs; + } + +} // namespace Catch + + + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); + } + + ITagAliasRegistry::~ITagAliasRegistry() = default; + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch + + + + +namespace Catch { + TestCaseInfoHasher::TestCaseInfoHasher( hash_t seed ): m_seed( seed ) {} + + uint32_t TestCaseInfoHasher::operator()( TestCaseInfo const& t ) const { + // FNV-1a hash algorithm that is designed for uniqueness: + const hash_t prime = 1099511628211u; + hash_t hash = 14695981039346656037u; + for ( const char c : t.name ) { + hash ^= c; + hash *= prime; + } + for ( const char c : t.className ) { + hash ^= c; + hash *= prime; + } + for ( const Tag& tag : t.tags ) { + for ( const char c : tag.original ) { + hash ^= c; + hash *= prime; + } + } + hash ^= m_seed; + hash *= prime; + const uint32_t low{ static_cast( hash ) }; + const uint32_t high{ static_cast( hash >> 32 ) }; + return low * high; + } +} // namespace Catch + + + + +#include +#include + +namespace Catch { + + std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + switch (config.runOrder()) { + case TestRunOrder::Declared: + return unsortedTestCases; + + case TestRunOrder::LexicographicallySorted: { + std::vector sorted = unsortedTestCases; + std::sort( + sorted.begin(), + sorted.end(), + []( TestCaseHandle const& lhs, TestCaseHandle const& rhs ) { + return lhs.getTestCaseInfo() < rhs.getTestCaseInfo(); + } + ); + return sorted; + } + case TestRunOrder::Randomized: { + seedRng(config); + using TestWithHash = std::pair; + + TestCaseInfoHasher h{ config.rngSeed() }; + std::vector indexed_tests; + indexed_tests.reserve(unsortedTestCases.size()); + + for (auto const& handle : unsortedTestCases) { + indexed_tests.emplace_back(h(handle.getTestCaseInfo()), handle); + } + + std::sort( indexed_tests.begin(), + indexed_tests.end(), + []( TestWithHash const& lhs, TestWithHash const& rhs ) { + if ( lhs.first == rhs.first ) { + return lhs.second.getTestCaseInfo() < + rhs.second.getTestCaseInfo(); + } + return lhs.first < rhs.first; + } ); + + std::vector randomized; + randomized.reserve(indexed_tests.size()); + + for (auto const& indexed : indexed_tests) { + randomized.push_back(indexed.second); + } + + return randomized; + } + } + + CATCH_INTERNAL_ERROR("Unknown test order value!"); + } + + bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config ) { + return !testCase.getTestCaseInfo().throws() || config.allowThrows(); + } + + bool matchTest( TestCaseHandle const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase.getTestCaseInfo() ) && isThrowSafe( testCase, config ); + } + + void + enforceNoDuplicateTestCases( std::vector const& tests ) { + auto testInfoCmp = []( TestCaseInfo const* lhs, + TestCaseInfo const* rhs ) { + return *lhs < *rhs; + }; + std::set seenTests(testInfoCmp); + for ( auto const& test : tests ) { + const auto infoPtr = &test.getTestCaseInfo(); + const auto prev = seenTests.insert( infoPtr ); + CATCH_ENFORCE( + prev.second, + "error: test case \"" << infoPtr->name << "\", with tags \"" + << infoPtr->tagsAsString() << "\" already defined.\n" + << "\tFirst seen at " << ( *prev.first )->lineInfo << "\n" + << "\tRedefined at " << infoPtr->lineInfo ); + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for (auto const& testCase : testCases) { + if ((!testSpec.hasFilters() && !testCase.getTestCaseInfo().isHidden()) || + (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) { + filtered.push_back(testCase); + } + } + return createShard(filtered, config.shardCount(), config.shardIndex()); + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + void TestRegistry::registerTest(Detail::unique_ptr testInfo, Detail::unique_ptr testInvoker) { + m_handles.emplace_back(testInfo.get(), testInvoker.get()); + m_viewed_test_infos.push_back(testInfo.get()); + m_owned_test_infos.push_back(CATCH_MOVE(testInfo)); + m_invokers.push_back(CATCH_MOVE(testInvoker)); + } + + std::vector const& TestRegistry::getAllInfos() const { + return m_viewed_test_infos; + } + + std::vector const& TestRegistry::getAllTests() const { + return m_handles; + } + std::vector const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_handles ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_handles ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + + + /////////////////////////////////////////////////////////////////////////// + void TestInvokerAsFunction::invoke() const { + m_testAsFunction(); + } + +} // end namespace Catch + + + + +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { +namespace TestCaseTracking { + + NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + + + ITracker::~ITracker() = default; + + void ITracker::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void ITracker::addChild( ITrackerPtr&& child ) { + m_children.push_back( CATCH_MOVE(child) ); + } + + ITracker* ITracker::findChild( NameAndLocation const& nameAndLocation ) { + auto it = std::find_if( + m_children.begin(), + m_children.end(), + [&nameAndLocation]( ITrackerPtr const& tracker ) { + return tracker->nameAndLocation().location == + nameAndLocation.location && + tracker->nameAndLocation().name == nameAndLocation.name; + } ); + return ( it != m_children.end() ) ? it->get() : nullptr; + } + + bool ITracker::isSectionTracker() const { return false; } + bool ITracker::isGeneratorTracker() const { return false; } + + bool ITracker::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + + bool ITracker::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + + bool ITracker::hasStarted() const { return m_runState != NotStarted; } + + void ITracker::openChild() { + if (m_runState != ExecutingChildren) { + m_runState = ExecutingChildren; + if (m_parent) { + m_parent->openChild(); + } + } + } + + ITracker& TrackerContext::startRun() { + using namespace std::string_literals; + m_rootTracker = Catch::Detail::make_unique( + NameAndLocation( "{root}"s, CATCH_INTERNAL_LINEINFO ), + *this, + nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::endRun() { + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; + } + + void TrackerContext::startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& TrackerContext::currentTracker() { + return *m_currentTracker; + } + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + + + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ): + ITracker(nameAndLocation, parent), + m_ctx( ctx ) + {} + + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + void TrackerBase::close() { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const& t){ return t->isComplete(); }) ) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); + } + + SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_trimmed_name(trim(nameAndLocation.name)) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = parent->parent(); + + SectionTracker& parentSection = static_cast( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + + bool SectionTracker::isComplete() const { + bool complete = true; + + if (m_filters.empty() + || m_filters[0].empty() + || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) { + complete = TrackerBase::isComplete(); + } + return complete; + } + + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + SectionTracker* section; + + ITracker& currentTracker = ctx.currentTracker(); + if ( ITracker* childTracker = + currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = static_cast( childTracker ); + } else { + auto newSection = Catch::Detail::make_unique( + nameAndLocation, ctx, ¤tTracker ); + section = newSection.get(); + currentTracker.addChild( CATCH_MOVE( newSection ) ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void SectionTracker::tryOpen() { + if( !isComplete() ) + open(); + } + + void SectionTracker::addInitialFilters( std::vector const& filters ) { + if( !filters.empty() ) { + m_filters.reserve( m_filters.size() + filters.size() + 2 ); + m_filters.emplace_back(StringRef{}); // Root - should never be consulted + m_filters.emplace_back(StringRef{}); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void SectionTracker::addNextFilters( std::vector const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() ); + } + + std::vector const& SectionTracker::getFilters() const { + return m_filters; + } + + StringRef SectionTracker::trimmedName() const { + return m_trimmed_name; + } + +} // namespace TestCaseTracking + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + + + +#include +#include + +namespace Catch { + + namespace { + StringRef extractClassName( StringRef classOrMethodName ) { + if ( !startsWith( classOrMethodName, '&' ) ) { + return classOrMethodName; + } + + // Remove the leading '&' to avoid having to special case it later + const auto methodName = + classOrMethodName.substr( 1, classOrMethodName.size() ); + + auto reverseStart = std::make_reverse_iterator( methodName.end() ); + auto reverseEnd = std::make_reverse_iterator( methodName.begin() ); + + // We make a simplifying assumption that ":" is only present + // in the input as part of "::" from C++ typenames (this is + // relatively safe assumption because the input is generated + // as stringification of type through preprocessor). + auto lastColons = std::find( reverseStart, reverseEnd, ':' ) + 1; + auto secondLastColons = + std::find( lastColons + 1, reverseEnd, ':' ); + + auto const startIdx = reverseEnd - secondLastColons; + auto const classNameSize = secondLastColons - lastColons - 1; + + return methodName.substr( + static_cast( startIdx ), + static_cast( classNameSize ) ); + } + } // namespace + + Detail::unique_ptr makeTestInvoker( void(*testAsFunction)() ) { + return Detail::make_unique( testAsFunction ); + } + + AutoReg::AutoReg( Detail::unique_ptr invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept { + CATCH_TRY { + getMutableRegistryHub() + .registerTest( + makeTestCaseInfo( + extractClassName( classOrMethod ), + nameAndTags, + lineInfo), + CATCH_MOVE(invoker) + ); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } +} + + + + + +namespace Catch { + + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + m_substring.reserve(m_arg.size()); + m_patternName.reserve(m_arg.size()); + m_realPatternPos = 0; + + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + //if visitChar fails + if( !visitChar( m_arg[m_pos] ) ){ + m_testSpec.m_invalidSpecs.push_back(arg); + break; + } + endMode(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return CATCH_MOVE(m_testSpec); + } + bool TestSpecParser::visitChar( char c ) { + if( (m_mode != EscapedName) && (c == '\\') ) { + escape(); + addCharToPattern(c); + return true; + }else if((m_mode != EscapedName) && (c == ',') ) { + return separate(); + } + + switch( m_mode ) { + case None: + if( processNoneChar( c ) ) + return true; + break; + case Name: + processNameChar( c ); + break; + case EscapedName: + endMode(); + addCharToPattern(c); + return true; + default: + case Tag: + case QuotedName: + if( processOtherChar( c ) ) + return true; + break; + } + + m_substring += c; + if( !isControlChar( c ) ) { + m_patternName += c; + m_realPatternPos++; + } + return true; + } + // Two of the processing methods return true to signal the caller to return + // without adding the given character to the current pattern strings + bool TestSpecParser::processNoneChar( char c ) { + switch( c ) { + case ' ': + return true; + case '~': + m_exclusion = true; + return false; + case '[': + startNewMode( Tag ); + return false; + case '"': + startNewMode( QuotedName ); + return false; + default: + startNewMode( Name ); + return false; + } + } + void TestSpecParser::processNameChar( char c ) { + if( c == '[' ) { + if( m_substring == "exclude:" ) + m_exclusion = true; + else + endMode(); + startNewMode( Tag ); + } + } + bool TestSpecParser::processOtherChar( char c ) { + if( !isControlChar( c ) ) + return false; + m_substring += c; + endMode(); + return true; + } + void TestSpecParser::startNewMode( Mode mode ) { + m_mode = mode; + } + void TestSpecParser::endMode() { + switch( m_mode ) { + case Name: + case QuotedName: + return addNamePattern(); + case Tag: + return addTagPattern(); + case EscapedName: + revertBackToLastMode(); + return; + case None: + default: + return startNewMode( None ); + } + } + void TestSpecParser::escape() { + saveLastMode(); + m_mode = EscapedName; + m_escapeChars.push_back(m_realPatternPos); + } + bool TestSpecParser::isControlChar( char c ) const { + switch( m_mode ) { + default: + return false; + case None: + return c == '~'; + case Name: + return c == '['; + case EscapedName: + return true; + case QuotedName: + return c == '"'; + case Tag: + return c == '[' || c == ']'; + } + } + + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_required.empty() || !m_currentFilter.m_forbidden.empty() ) { + m_testSpec.m_filters.push_back( CATCH_MOVE(m_currentFilter) ); + m_currentFilter = TestSpec::Filter(); + } + } + + void TestSpecParser::saveLastMode() { + lastMode = m_mode; + } + + void TestSpecParser::revertBackToLastMode() { + m_mode = lastMode; + } + + bool TestSpecParser::separate() { + if( (m_mode==QuotedName) || (m_mode==Tag) ){ + //invalid argument, signal failure to previous scope. + m_mode = None; + m_pos = m_arg.size(); + m_substring.clear(); + m_patternName.clear(); + m_realPatternPos = 0; + return false; + } + endMode(); + addFilter(); + return true; //success + } + + std::string TestSpecParser::preprocessPattern() { + std::string token = m_patternName; + for (std::size_t i = 0; i < m_escapeChars.size(); ++i) + token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1); + m_escapeChars.clear(); + if (startsWith(token, "exclude:")) { + m_exclusion = true; + token = token.substr(8); + } + + m_patternName.clear(); + m_realPatternPos = 0; + + return token; + } + + void TestSpecParser::addNamePattern() { + auto token = preprocessPattern(); + + if (!token.empty()) { + if (m_exclusion) { + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(token, m_substring)); + } else { + m_currentFilter.m_required.emplace_back(Detail::make_unique(token, m_substring)); + } + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } + + void TestSpecParser::addTagPattern() { + auto token = preprocessPattern(); + + if (!token.empty()) { + // If the tag pattern is the "hide and tag" shorthand (e.g. [.foo]) + // we have to create a separate hide tag and shorten the real one + if (token.size() > 1 && token[0] == '.') { + token.erase(token.begin()); + if (m_exclusion) { + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(".", m_substring)); + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(token, m_substring)); + } else { + m_currentFilter.m_required.emplace_back(Detail::make_unique(".", m_substring)); + m_currentFilter.m_required.emplace_back(Detail::make_unique(token, m_substring)); + } + } + if (m_exclusion) { + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(token, m_substring)); + } else { + m_currentFilter.m_required.emplace_back(Detail::make_unique(token, m_substring)); + } + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } + + TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + + + +#include +#include +#include + +namespace { + bool isWhitespace( char c ) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + } + + bool isBreakableBefore( char c ) { + static const char chars[] = "[({<|"; + return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; + } + + bool isBreakableAfter( char c ) { + static const char chars[] = "])}>.,:;*+-=&/\\"; + return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; + } + + bool isBoundary( std::string const& line, size_t at ) { + assert( at > 0 ); + assert( at <= line.size() ); + + return at == line.size() || + ( isWhitespace( line[at] ) && !isWhitespace( line[at - 1] ) ) || + isBreakableBefore( line[at] ) || + isBreakableAfter( line[at - 1] ); + } + +} // namespace + +namespace Catch { + namespace TextFlow { + + void Column::const_iterator::calcLength() { + m_addHyphen = false; + m_parsedTo = m_lineStart; + + std::string const& current_line = m_column.m_string; + if ( current_line[m_lineStart] == '\n' ) { + ++m_parsedTo; + } + + const auto maxLineLength = m_column.m_width - indentSize(); + const auto maxParseTo = std::min(current_line.size(), m_lineStart + maxLineLength); + while ( m_parsedTo < maxParseTo && + current_line[m_parsedTo] != '\n' ) { + ++m_parsedTo; + } + + // If we encountered a newline before the column is filled, + // then we linebreak at the newline and consider this line + // finished. + if ( m_parsedTo < m_lineStart + maxLineLength ) { + m_lineLength = m_parsedTo - m_lineStart; + } else { + // Look for a natural linebreak boundary in the column + // (We look from the end, so that the first found boundary is + // the right one) + size_t newLineLength = maxLineLength; + while ( newLineLength > 0 && !isBoundary( current_line, m_lineStart + newLineLength ) ) { + --newLineLength; + } + while ( newLineLength > 0 && + isWhitespace( current_line[m_lineStart + newLineLength - 1] ) ) { + --newLineLength; + } + + // If we found one, then that is where we linebreak + if ( newLineLength > 0 ) { + m_lineLength = newLineLength; + } else { + // Otherwise we have to split text with a hyphen + m_addHyphen = true; + m_lineLength = maxLineLength - 1; + } + } + } + + size_t Column::const_iterator::indentSize() const { + auto initial = + m_lineStart == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + std::string + Column::const_iterator::addIndentAndSuffix( size_t position, + size_t length ) const { + std::string ret; + const auto desired_indent = indentSize(); + ret.reserve( desired_indent + length + m_addHyphen ); + ret.append( desired_indent, ' ' ); + ret.append( m_column.m_string, position, length ); + if ( m_addHyphen ) { + ret.push_back( '-' ); + } + + return ret; + } + + Column::const_iterator::const_iterator( Column const& column ): m_column( column ) { + assert( m_column.m_width > m_column.m_indent ); + assert( m_column.m_initialIndent == std::string::npos || + m_column.m_width > m_column.m_initialIndent ); + calcLength(); + if ( m_lineLength == 0 ) { + m_lineStart = m_column.m_string.size(); + } + } + + std::string Column::const_iterator::operator*() const { + assert( m_lineStart <= m_parsedTo ); + return addIndentAndSuffix( m_lineStart, m_lineLength ); + } + + Column::const_iterator& Column::const_iterator::operator++() { + m_lineStart += m_lineLength; + std::string const& current_line = m_column.m_string; + if ( m_lineStart < current_line.size() && current_line[m_lineStart] == '\n' ) { + m_lineStart += 1; + } else { + while ( m_lineStart < current_line.size() && + isWhitespace( current_line[m_lineStart] ) ) { + ++m_lineStart; + } + } + + if ( m_lineStart != current_line.size() ) { + calcLength(); + } + return *this; + } + + Column::const_iterator Column::const_iterator::operator++( int ) { + const_iterator prev( *this ); + operator++(); + return prev; + } + + std::ostream& operator<<( std::ostream& os, Column const& col ) { + bool first = true; + for ( auto line : col ) { + if ( first ) { + first = false; + } else { + os << '\n'; + } + os << line; + } + return os; + } + + Column Spacer( size_t spaceWidth ) { + Column ret{ "" }; + ret.width( spaceWidth ); + return ret; + } + + Columns::iterator::iterator( Columns const& columns, EndTag ): + m_columns( columns.m_columns ), m_activeIterators( 0 ) { + + m_iterators.reserve( m_columns.size() ); + for ( auto const& col : m_columns ) { + m_iterators.push_back( col.end() ); + } + } + + Columns::iterator::iterator( Columns const& columns ): + m_columns( columns.m_columns ), + m_activeIterators( m_columns.size() ) { + + m_iterators.reserve( m_columns.size() ); + for ( auto const& col : m_columns ) { + m_iterators.push_back( col.begin() ); + } + } + + std::string Columns::iterator::operator*() const { + std::string row, padding; + + for ( size_t i = 0; i < m_columns.size(); ++i ) { + const auto width = m_columns[i].width(); + if ( m_iterators[i] != m_columns[i].end() ) { + std::string col = *m_iterators[i]; + row += padding; + row += col; + + padding.clear(); + if ( col.size() < width ) { + padding.append( width - col.size(), ' ' ); + } + } else { + padding.append( width, ' ' ); + } + } + return row; + } + + Columns::iterator& Columns::iterator::operator++() { + for ( size_t i = 0; i < m_columns.size(); ++i ) { + if ( m_iterators[i] != m_columns[i].end() ) { + ++m_iterators[i]; + } + } + return *this; + } + + Columns::iterator Columns::iterator::operator++( int ) { + iterator prev( *this ); + operator++(); + return prev; + } + + std::ostream& operator<<( std::ostream& os, Columns const& cols ) { + bool first = true; + for ( auto line : cols ) { + if ( first ) { + first = false; + } else { + os << '\n'; + } + os << line; + } + return os; + } + + Columns Column::operator+( Column const& other ) { + Columns cols; + cols += *this; + cols += other; + return cols; + } + + Columns& Columns::operator+=( Column const& col ) { + m_columns.push_back( col ); + return *this; + } + + Columns Columns::operator+( Column const& col ) { + Columns combined = *this; + combined += col; + return combined; + } + + } // namespace TextFlow +} // namespace Catch + + + + +#include + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + return false; +#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch + + + +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( normaliseString( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == normaliseString( str ); + case WildcardAtStart: + return endsWith( normaliseString( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( normaliseString( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( normaliseString( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } + } + + std::string WildcardPattern::normaliseString( std::string const& str ) const { + return trim( m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str ); + } +} + + +// Note: swapping these two includes around causes MSVC to error out +// while in /permissive- mode. No, I don't know why. +// Tested on VS 2019, 18.{3, 4}.x + +#include +#include + +namespace Catch { + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + os.flags(f); + } + + bool shouldNewline(XmlFormatting fmt) { + return !!(static_cast>(fmt & XmlFormatting::Newline)); + } + + bool shouldIndent(XmlFormatting fmt) { + return !!(static_cast>(fmt & XmlFormatting::Indent)); + } + +} // anonymous namespace + + XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs) { + return static_cast( + static_cast>(lhs) | + static_cast>(rhs) + ); + } + + XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs) { + return static_cast( + static_cast>(lhs) & + static_cast>(rhs) + ); + } + + + XmlEncode::XmlEncode( StringRef str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + unsigned char c = static_cast(m_str[idx]); + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + unsigned char nc = static_cast(m_str[idx + n]); + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt ) + : m_writer( writer ), + m_fmt(fmt) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ), + m_fmt(other.m_fmt) + { + other.m_writer = nullptr; + other.m_fmt = XmlFormatting::None; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + m_fmt = other.m_fmt; + other.m_fmt = XmlFormatting::None; + return *this; + } + + + XmlWriter::ScopedElement::~ScopedElement() { + if (m_writer) { + m_writer->endElement(m_fmt); + } + } + + XmlWriter::ScopedElement& + XmlWriter::ScopedElement::writeText( StringRef text, XmlFormatting fmt ) { + m_writer->writeText( text, fmt ); + return *this; + } + + XmlWriter::ScopedElement& + XmlWriter::ScopedElement::writeAttribute( StringRef name, + StringRef attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while (!m_tags.empty()) { + endElement(); + } + newlineIfNecessary(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name, XmlFormatting fmt ) { + ensureTagClosed(); + newlineIfNecessary(); + if (shouldIndent(fmt)) { + m_os << m_indent; + m_indent += " "; + } + m_os << '<' << name; + m_tags.push_back( name ); + m_tagIsOpen = true; + applyFormatting(fmt); + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) { + ScopedElement scoped( this, fmt ); + startElement( name, fmt ); + return scoped; + } + + XmlWriter& XmlWriter::endElement(XmlFormatting fmt) { + m_indent = m_indent.substr(0, m_indent.size() - 2); + + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } else { + newlineIfNecessary(); + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << "'; + } + m_os << std::flush; + applyFormatting(fmt); + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( StringRef name, + StringRef attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( StringRef name, bool attribute ) { + writeAttribute(name, (attribute ? "true"_sr : "false"_sr)); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( StringRef name, + char const* attribute ) { + writeAttribute( name, StringRef( attribute ) ); + return *this; + } + + XmlWriter& XmlWriter::writeText( StringRef text, XmlFormatting fmt ) { + CATCH_ENFORCE(!m_tags.empty(), "Cannot write text as top level element"); + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if (tagWasOpen && shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << XmlEncode( text, XmlEncode::ForTextNodes ); + applyFormatting(fmt); + } + return *this; + } + + XmlWriter& XmlWriter::writeComment( StringRef text, XmlFormatting fmt ) { + ensureTagClosed(); + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << ""; + applyFormatting(fmt); + return *this; + } + + void XmlWriter::writeStylesheetRef( StringRef url ) { + m_os << R"()" << '\n'; + } + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << '>' << std::flush; + newlineIfNecessary(); + m_tagIsOpen = false; + } + } + + void XmlWriter::applyFormatting(XmlFormatting fmt) { + m_needsNewline = shouldNewline(fmt); + } + + void XmlWriter::writeDeclaration() { + m_os << R"()" << '\n'; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << '\n' << std::flush; + m_needsNewline = false; + } + } +} + + + + + +namespace Catch { +namespace Matchers { + + std::string MatcherUntypedBase::toString() const { + if (m_cachedToString.empty()) { + m_cachedToString = describe(); + } + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + +} // namespace Matchers +} // namespace Catch + + + + +namespace Catch { +namespace Matchers { + + std::string IsEmptyMatcher::describe() const { + return "is empty"; + } + + std::string HasSizeMatcher::describe() const { + ReusableStringStream sstr; + sstr << "has size == " << m_target_size; + return sstr.str(); + } + + IsEmptyMatcher IsEmpty() { + return {}; + } + + HasSizeMatcher SizeIs(std::size_t sz) { + return HasSizeMatcher{ sz }; + } + +} // end namespace Matchers +} // end namespace Catch + + + +namespace Catch { +namespace Matchers { + +bool ExceptionMessageMatcher::match(std::exception const& ex) const { + return ex.what() == m_message; +} + +std::string ExceptionMessageMatcher::describe() const { + return "exception message matches \"" + m_message + '"'; +} + +ExceptionMessageMatcher Message(std::string const& message) { + return ExceptionMessageMatcher(message); +} + +} // namespace Matchers +} // namespace Catch + + + +#include +#include +#include +#include +#include +#include +#include + + +namespace Catch { +namespace { + + template + bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (Catch::isnan(lhs) || Catch::isnan(rhs)) { + return false; + } + + // This should also handle positive and negative zeros, infinities + const auto ulpDist = ulpDistance(lhs, rhs); + + return ulpDist <= maxUlpDiff; + } + +#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) + + float nextafter(float x, float y) { + return ::nextafterf(x, y); + } + + double nextafter(double x, double y) { + return ::nextafter(x, y); + } + +#endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^ + +template +FP step(FP start, FP direction, uint64_t steps) { + for (uint64_t i = 0; i < steps; ++i) { +#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) + start = Catch::nextafter(start, direction); +#else + start = std::nextafter(start, direction); +#endif + } + return start; +} + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +template +void write(std::ostream& out, FloatingPoint num) { + out << std::scientific + << std::setprecision(std::numeric_limits::max_digits10 - 1) + << num; +} + +} // end anonymous namespace + +namespace Matchers { +namespace Detail { + + enum class FloatingPointKind : uint8_t { + Float, + Double + }; + +} // end namespace Detail + + + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + + WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, Detail::FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + CATCH_ENFORCE(m_type == Detail::FloatingPointKind::Double + || m_ulps < (std::numeric_limits::max)(), + "Provided ULP is impossibly large for a float comparison."); + CATCH_ENFORCE( std::numeric_limits::is_iec559, + "WithinUlp matcher only supports platforms with " + "IEEE-754 compatible floating point representation" ); + } + +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case Detail::FloatingPointKind::Float: + return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); + case Detail::FloatingPointKind::Double: + return almostEqualUlps(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR( "Unknown Detail::FloatingPointKind value" ); + } + } + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + std::string WithinUlpsMatcher::describe() const { + std::stringstream ret; + + ret << "is within " << m_ulps << " ULPs of "; + + if (m_type == Detail::FloatingPointKind::Float) { + write(ret, static_cast(m_target)); + ret << 'f'; + } else { + write(ret, m_target); + } + + ret << " (["; + if (m_type == Detail::FloatingPointKind::Double) { + write( ret, + step( m_target, + -std::numeric_limits::infinity(), + m_ulps ) ); + ret << ", "; + write( ret, + step( m_target, + std::numeric_limits::infinity(), + m_ulps ) ); + } else { + // We have to cast INFINITY to float because of MinGW, see #1782 + write( ret, + step( static_cast( m_target ), + -std::numeric_limits::infinity(), + m_ulps ) ); + ret << ", "; + write( ret, + step( static_cast( m_target ), + std::numeric_limits::infinity(), + m_ulps ) ); + } + ret << "])"; + + return ret.str(); + } + + WithinRelMatcher::WithinRelMatcher(double target, double epsilon): + m_target(target), + m_epsilon(epsilon){ + CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense."); + CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense."); + } + + bool WithinRelMatcher::match(double const& matchee) const { + const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target)); + return marginComparison(matchee, m_target, + std::isinf(relMargin)? 0 : relMargin); + } + + std::string WithinRelMatcher::describe() const { + Catch::ReusableStringStream sstr; + sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other"; + return sstr.str(); + } + + +WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) { + return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Double); +} + +WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) { + return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Float); +} + +WithinAbsMatcher WithinAbs(double target, double margin) { + return WithinAbsMatcher(target, margin); +} + +WithinRelMatcher WithinRel(double target, double eps) { + return WithinRelMatcher(target, eps); +} + +WithinRelMatcher WithinRel(double target) { + return WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); +} + +WithinRelMatcher WithinRel(float target, float eps) { + return WithinRelMatcher(target, eps); +} + +WithinRelMatcher WithinRel(float target) { + return WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); +} + + +} // namespace Matchers +} // namespace Catch + + + + +std::string Catch::Matchers::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} + + + +namespace Catch { + namespace Matchers { + std::string AllTrueMatcher::describe() const { return "contains only true"; } + + AllTrueMatcher AllTrue() { return AllTrueMatcher{}; } + + std::string NoneTrueMatcher::describe() const { return "contains no true"; } + + NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; } + + std::string AnyTrueMatcher::describe() const { return "contains at least one true"; } + + AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; } + } // namespace Matchers +} // namespace Catch + + + +#include + +namespace Catch { +namespace Matchers { + + CasedString::CasedString( std::string const& str, CaseSensitive caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + StringRef CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::Yes + ? StringRef() + : " (case insensitive)"_sr; + } + + + StringMatcherBase::StringMatcherBase( StringRef operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += '"'; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + StringEqualsMatcher::StringEqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals"_sr, comparator ) {} + + bool StringEqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + + StringContainsMatcher::StringContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains"_sr, comparator ) {} + + bool StringContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with"_sr, comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with"_sr, comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive caseSensitivity): m_regex(CATCH_MOVE(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Yes)? " case sensitively" : " case insensitively"); + } + + + StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity ) { + return StringEqualsMatcher( CasedString( str, caseSensitivity) ); + } + StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity ) { + return StringContainsMatcher( CasedString( str, caseSensitivity) ); + } + EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity ) { + return EndsWithMatcher( CasedString( str, caseSensitivity) ); + } + StartsWithMatcher StartsWith( std::string const& str, CaseSensitive caseSensitivity ) { + return StartsWithMatcher( CasedString( str, caseSensitivity) ); + } + + RegexMatcher Matches(std::string const& regex, CaseSensitive caseSensitivity) { + return RegexMatcher(regex, caseSensitivity); + } + +} // namespace Matchers +} // namespace Catch + + + +namespace Catch { +namespace Matchers { + MatcherGenericBase::~MatcherGenericBase() = default; + + namespace Detail { + + std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end) { + std::string description; + std::size_t combined_size = 4; + for ( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { + combined_size += desc->size(); + } + combined_size += static_cast(descriptions_end - descriptions_begin - 1) * combine.size(); + + description.reserve(combined_size); + + description += "( "; + bool first = true; + for( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { + if( first ) + first = false; + else + description += combine; + description += *desc; + } + description += " )"; + return description; + } + + } // namespace Detail +} // namespace Matchers +} // namespace Catch + + + + +namespace Catch { + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr expr( CATCH_MOVE(exceptionMessage), matcher, matcherString ); + handler.handleExpr( expr ); + } + +} // namespace Catch + + + +#include + +namespace Catch { + + AutomakeReporter::~AutomakeReporter() {} + + void AutomakeReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + // Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR. + m_stream << ":test-result: "; + if (_testCaseStats.totals.assertions.allPassed()) { + m_stream << "PASS"; + } else if (_testCaseStats.totals.assertions.allOk()) { + m_stream << "XFAIL"; + } else { + m_stream << "FAIL"; + } + m_stream << ' ' << _testCaseStats.testInfo->name << '\n'; + StreamingReporterBase::testCaseEnded(_testCaseStats); + } + + void AutomakeReporter::skipTest(TestCaseInfo const& testInfo) { + m_stream << ":test-result: SKIP " << testInfo.name << '\n'; + } + +} // end namespace Catch + + + + + + +namespace Catch { + ReporterBase::ReporterBase( ReporterConfig&& config ): + IEventListener( config.fullConfig() ), + m_wrapped_stream( CATCH_MOVE(config).takeStream() ), + m_stream( m_wrapped_stream->stream() ), + m_colour( makeColourImpl( config.colourMode(), m_wrapped_stream.get() ) ), + m_customOptions( config.customOptions() ) + {} + + ReporterBase::~ReporterBase() = default; + + void ReporterBase::listReporters( + std::vector const& descriptions ) { + defaultListReporters(m_stream, descriptions, m_config->verbosity()); + } + + void ReporterBase::listListeners( + std::vector const& descriptions ) { + defaultListListeners( m_stream, descriptions ); + } + + void ReporterBase::listTests(std::vector const& tests) { + defaultListTests(m_stream, + m_colour.get(), + tests, + m_config->hasTestFilters(), + m_config->verbosity()); + } + + void ReporterBase::listTags(std::vector const& tags) { + defaultListTags( m_stream, tags, m_config->hasTestFilters() ); + } + +} // namespace Catch + + + + +#include + +namespace { + + constexpr Catch::StringRef bothOrAll( std::uint64_t count ) { + switch (count) { + case 1: + return Catch::StringRef{}; + case 2: + return "both "_catch_sr; + default: + return "all "_catch_sr; + } + } + +} // anon namespace + + +namespace Catch { +namespace { + + // Colour::LightGrey + static constexpr Colour::Code compactDimColour = Colour::FileName; + +#ifdef CATCH_PLATFORM_MAC + static constexpr Catch::StringRef compactFailedString = "FAILED"_sr; + static constexpr Catch::StringRef compactPassedString = "PASSED"_sr; +#else + static constexpr Catch::StringRef compactFailedString = "failed"_sr; + static constexpr Catch::StringRef compactPassedString = "passed"_sr; +#endif + +// Colour, message variants: +// - white: No tests ran. +// - red: Failed [both/all] N test cases, failed [both/all] M assertions. +// - white: Passed [both/all] N test cases (no assertions). +// - red: Failed N tests cases, failed M assertions. +// - green: Passed [both/all] N tests cases with M assertions. +void printTotals(std::ostream& out, const Totals& totals, ColourImpl* colourImpl) { + if (totals.testCases.total() == 0) { + out << "No tests ran."; + } else if (totals.testCases.failed == totals.testCases.total()) { + auto guard = colourImpl->guardColour( Colour::ResultError ).engage( out ); + const StringRef qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll(totals.assertions.failed) : StringRef{}; + out << + "Failed " << bothOrAll(totals.testCases.failed) + << pluralise(totals.testCases.failed, "test case"_sr) << ", " + "failed " << qualify_assertions_failed << + pluralise(totals.assertions.failed, "assertion"_sr) << '.'; + } else if (totals.assertions.total() == 0) { + out << + "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case"_sr) + << " (no assertions)."; + } else if (totals.assertions.failed) { + out << colourImpl->guardColour( Colour::ResultError ) << + "Failed " << pluralise(totals.testCases.failed, "test case"_sr) << ", " + "failed " << pluralise(totals.assertions.failed, "assertion"_sr) << '.'; + } else { + out << colourImpl->guardColour( Colour::ResultSuccess ) << + "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case"_sr) << + " with " << pluralise(totals.assertions.passed, "assertion"_sr) << '.'; + } +} + +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages, ColourImpl* colourImpl_) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) + , colourImpl(colourImpl_) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, compactPassedString); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, compactFailedString + " - but was ok"_sr); + else + printResultType(Colour::Error, compactFailedString); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, compactFailedString); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, compactFailedString); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, compactFailedString); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, compactFailedString); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + +private: + void printSourceInfo() const { + stream << colourImpl->guardColour( Colour::FileName ) + << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, StringRef passOrFail) const { + if (!passOrFail.empty()) { + stream << colourImpl->guardColour(colour) << ' ' << passOrFail; + stream << ':'; + } + } + + void printIssue(char const* issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + stream << colourImpl->guardColour(compactDimColour) << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << colourImpl->guardColour(compactDimColour) << " for: "; + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = compactDimColour) { + if (itMessage == messages.end()) + return; + + const auto itEnd = messages.cend(); + const auto N = static_cast(std::distance(itMessage, itEnd)); + + stream << colourImpl->guardColour( colour ) << " with " + << pluralise( N, "message"_sr ) << ':'; + + while (itMessage != itEnd) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + printMessage(); + if (itMessage != itEnd) { + stream << colourImpl->guardColour(compactDimColour) << " and"; + } + continue; + } + ++itMessage; + } + } + +private: + std::ostream& stream; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + ColourImpl* colourImpl; +}; + +} // anon namespace + + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + void CompactReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; + } + + void CompactReporter::testRunStarting( TestRunInfo const& ) { + if ( m_config->testSpec().hasFilters() ) { + m_stream << m_colour->guardColour( Colour::BrightYellow ) + << "Filters: " + << serializeFilters( m_config->getTestsOrTags() ) + << '\n'; + } + m_stream << "RNG seed: " << getSeed() << '\n'; + } + + void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return; + printInfoMessages = false; + } + + AssertionPrinter printer( m_stream, _assertionStats, printInfoMessages, m_colour.get() ); + printer.print(); + + m_stream << '\n' << std::flush; + } + + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { + double dur = _sectionStats.durationInSeconds; + if ( shouldShowDuration( *m_config, dur ) ) { + m_stream << getFormattedDuration( dur ) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush; + } + } + + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( m_stream, _testRunStats.totals, m_colour.get() ); + m_stream << "\n\n" << std::flush; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + CompactReporter::~CompactReporter() {} + +} // end namespace Catch + + + + +#include + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled and default is missing) is enabled +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +// For simplicity, benchmarking-only helpers are always enabled +# pragma clang diagnostic ignored "-Wunused-function" +#endif + + + +namespace Catch { + +namespace { + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, ColourImpl* colourImpl_, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + colourImpl(colourImpl_), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"_sr; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"_sr; + } else { + colour = Colour::Error; + passOrFail = "FAILED"_sr; + } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"_sr; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"_sr; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"_sr; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"_sr; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"_sr; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + +private: + void printResultType() const { + if (!passOrFail.empty()) { + stream << colourImpl->guardColour(colour) << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << colourImpl->guardColour( Colour::OriginalExpression ) + << " " << result.getExpressionInMacro() << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + stream << colourImpl->guardColour( Colour::ReconstructedExpression ) + << TextFlow::Column( result.getExpandedExpression() ) + .indent( 2 ) + << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << TextFlow::Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + stream << colourImpl->guardColour( Colour::FileName ) + << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + StringRef passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + ColourImpl* colourImpl; + bool printInfoMessages; +}; + +std::size_t makeRatio( std::uint64_t number, std::uint64_t total ) { + const auto ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : static_cast(ratio); +} + +std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if (i > j && i > k) + return i; + else if (j > k) + return j; + else + return k; +} + +enum class Justification { Left, Right }; + +struct ColumnInfo { + std::string name; + std::size_t width; + Justification justification; +}; +struct ColumnBreak {}; +struct RowBreak {}; + +class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + double m_inNanoseconds; + Unit m_units; + +public: + explicit Duration(double inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); + default: + return m_inNanoseconds; + } + } + StringRef unitsAsString() const { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"_sr; + case Unit::Microseconds: + return "us"_sr; + case Unit::Milliseconds: + return "ms"_sr; + case Unit::Seconds: + return "s"_sr; + case Unit::Minutes: + return "m"_sr; + default: + return "** internal error **"_sr; + } + + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << ' ' << duration.unitsAsString(); + } +}; +} // end anon namespace + +class TablePrinter { + std::ostream& m_os; + std::vector m_columnInfos; + ReusableStringStream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter( std::ostream& os, std::vector columnInfos ) + : m_os( os ), + m_columnInfos( CATCH_MOVE( columnInfos ) ) {} + + auto columnInfos() const -> std::vector const& { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + + TextFlow::Columns headerCols; + auto spacer = TextFlow::Spacer(2); + for (auto const& info : m_columnInfos) { + assert(info.width > 2); + headerCols += TextFlow::Column(info.name).width(info.width - 2); + headerCols += spacer; + } + m_os << headerCols << '\n'; + + m_os << lineOfChars('-') << '\n'; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << '\n' << std::flush; + m_isOpen = false; + } + } + + template + friend TablePrinter& operator << (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + const auto strSize = colStr.size(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << '\n'; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 1 < colInfo.width) + ? std::string(colInfo.width - (strSize + 1), ' ') + : std::string(); + if (colInfo.justification == Justification::Left) + tp.m_os << colStr << padding << ' '; + else + tp.m_os << padding << colStr << ' '; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << '\n'; + tp.m_currentColumn = -1; + } + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig&& config): + StreamingReporterBase( CATCH_MOVE( config ) ), + m_tablePrinter(Detail::make_unique(m_stream, + [&config]() -> std::vector { + if (config.fullConfig()->benchmarkNoAnalysis()) + { + return{ + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left }, + { " samples", 14, Justification::Right }, + { " iterations", 14, Justification::Right }, + { " mean", 14, Justification::Right } + }; + } + else + { + return{ + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left }, + { "samples mean std dev", 14, Justification::Right }, + { "iterations low mean low std dev", 14, Justification::Right }, + { "estimated high mean high std dev", 14, Justification::Right } + }; + } + }())) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; +} + +void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) { + m_stream << "Invalid Filter: " << arg << '\n'; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} + +void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return; + + lazyPrint(); + + ConsoleAssertionPrinter printer(m_stream, _assertionStats, m_colour.get(), includeResults); + printer.print(); + m_stream << '\n' << std::flush; +} + +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_tablePrinter->close(); + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + auto guard = + m_colour->guardColour( Colour::ResultError ).engage( m_stream ); + if (m_sectionStack.size() > 1) + m_stream << "\nNo assertions in section"; + else + m_stream << "\nNo assertions in test case"; + m_stream << " '" << _sectionStats.sectionInfo.name << "'\n\n" << std::flush; + } + double dur = _sectionStats.durationInSeconds; + if (shouldShowDuration(*m_config, dur)) { + m_stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkPreparing( StringRef name ) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = TextFlow::Column( static_cast( name ) ) + .width( m_tablePrinter->columnInfos()[0].width - 2 ); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + (*m_tablePrinter) << info.samples << ColumnBreak() + << info.iterations << ColumnBreak(); + if (!m_config->benchmarkNoAnalysis()) + (*m_tablePrinter) << Duration(info.estimatedDuration) << ColumnBreak(); +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) { + if (m_config->benchmarkNoAnalysis()) + { + (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak(); + } + else + { + (*m_tablePrinter) << ColumnBreak() + << Duration(stats.mean.point.count()) << ColumnBreak() + << Duration(stats.mean.lower_bound.count()) << ColumnBreak() + << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak() + << Duration(stats.standardDeviation.point.count()) << ColumnBreak() + << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak() + << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak(); + } +} + +void ConsoleReporter::benchmarkFailed( StringRef error ) { + auto guard = m_colour->guardColour( Colour::Red ).engage( m_stream ); + (*m_tablePrinter) + << "Benchmark failed (" << error << ')' + << ColumnBreak() << RowBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + m_stream << '\n' << std::flush; + StreamingReporterBase::testRunEnded(_testRunStats); +} +void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) { + StreamingReporterBase::testRunStarting(_testInfo); + if ( m_config->testSpec().hasFilters() ) { + m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: " + << serializeFilters( m_config->getTestsOrTags() ) << '\n'; + } + m_stream << "Randomness seeded to: " << getSeed() << '\n'; +} + +void ConsoleReporter::lazyPrint() { + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + + if ( !m_testRunInfoPrinted ) { + lazyPrintRunInfo(); + } + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + m_stream << '\n' + << lineOfChars( '~' ) << '\n' + << m_colour->guardColour( Colour::SecondaryText ) + << currentTestRunInfo.name << " is a Catch2 v" << libraryVersion() + << " host application.\n" + << "Run with -? for options\n\n"; + + m_testRunInfoPrinted = true; +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + + m_stream << lineOfChars( '-' ) << '\n' + << m_colour->guardColour( Colour::FileName ) << lineInfo << '\n' + << lineOfChars( '.' ) << "\n\n" + << std::flush; +} + +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + m_stream << lineOfChars('.') << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + m_stream << lineOfChars('-') << '\n'; + { + auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); + printHeaderString(_name); + } +} + +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + // We want to get a bit fancy with line breaking here, so that subsequent + // lines start after ":" if one is present, e.g. + // ``` + // blablabla: Fancy + // linebreaking + // ``` + // but we also want to avoid problems with overly long indentation causing + // the text to take up too many lines, e.g. + // ``` + // blablabla: F + // a + // n + // c + // y + // . + // . + // . + // ``` + // So we limit the prefix indentation check to first quarter of the possible + // width + std::size_t idx = _string.find( ": " ); + if ( idx != std::string::npos && idx < CATCH_CONFIG_CONSOLE_WIDTH / 4 ) { + idx += 2; + } else { + idx = 0; + } + m_stream << TextFlow::Column( _string ) + .indent( indent + idx ) + .initialIndent( indent ) + << '\n'; +} + +struct SummaryColumn { + + SummaryColumn( std::string _label, Colour::Code _colour ) + : label( CATCH_MOVE( _label ) ), + colour( _colour ) {} + SummaryColumn addRow( std::uint64_t count ) { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto& oldRow : rows) { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + +}; + +void ConsoleReporter::printTotals( Totals const& totals ) { + if (totals.testCases.total() == 0) { + m_stream << m_colour->guardColour( Colour::Warning ) + << "No tests ran\n"; + } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + m_stream << m_colour->guardColour( Colour::ResultSuccess ) + << "All tests passed"; + m_stream << " (" + << pluralise(totals.assertions.passed, "assertion"_sr) << " in " + << pluralise(totals.testCases.passed, "test case"_sr) << ')' + << '\n'; + } else { + + std::vector columns; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totals.assertions.total())); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + + printSummaryRow("test cases"_sr, columns, 0); + printSummaryRow("assertions"_sr, columns, 1); + } +} +void ConsoleReporter::printSummaryRow(StringRef label, std::vector const& cols, std::size_t row) { + for (auto col : cols) { + std::string const& value = col.rows[row]; + if (col.label.empty()) { + m_stream << label << ": "; + if ( value != "0" ) { + m_stream << value; + } else { + m_stream << m_colour->guardColour( Colour::Warning ) + << "- none -"; + } + } else if (value != "0") { + m_stream << m_colour->guardColour( Colour::LightGrey ) << " | " + << m_colour->guardColour( col.colour ) << value << ' ' + << col.label; + } + } + m_stream << '\n'; +} + +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)--; + + m_stream << m_colour->guardColour( Colour::Error ) + << std::string( failedRatio, '=' ) + << m_colour->guardColour( Colour::ResultExpectedFailure ) + << std::string( failedButOkRatio, '=' ); + if ( totals.testCases.allPassed() ) { + m_stream << m_colour->guardColour( Colour::ResultSuccess ) + << std::string( passedRatio, '=' ); + } else { + m_stream << m_colour->guardColour( Colour::Success ) + << std::string( passedRatio, '=' ); + } + } else { + m_stream << m_colour->guardColour( Colour::Warning ) + << std::string( CATCH_CONFIG_CONSOLE_WIDTH - 1, '=' ); + } + m_stream << '\n'; +} +void ConsoleReporter::printSummaryDivider() { + m_stream << lineOfChars('-') << '\n'; +} + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + + + + +#include +#include + +namespace Catch { + namespace { + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ): m_other( other ) {} + BySectionInfo( BySectionInfo const& other ): + m_other( other.m_other ) {} + bool operator()( + Detail::unique_ptr const& + node ) const { + return ( + ( node->stats.sectionInfo.name == m_other.name ) && + ( node->stats.sectionInfo.lineInfo == m_other.lineInfo ) ); + } + void operator=( BySectionInfo const& ) = delete; + + private: + SectionInfo const& m_other; + }; + + } // namespace + + namespace Detail { + AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( + AssertionStats const& assertion ): + m_assertion( assertion ) {} + + AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( + BenchmarkStats<> const& benchmark ): + m_benchmark( benchmark ) {} + + bool AssertionOrBenchmarkResult::isAssertion() const { + return m_assertion.some(); + } + bool AssertionOrBenchmarkResult::isBenchmark() const { + return m_benchmark.some(); + } + + AssertionStats const& AssertionOrBenchmarkResult::asAssertion() const { + assert(m_assertion.some()); + + return *m_assertion; + } + BenchmarkStats<> const& AssertionOrBenchmarkResult::asBenchmark() const { + assert(m_benchmark.some()); + + return *m_benchmark; + } + + } + + CumulativeReporterBase::~CumulativeReporterBase() = default; + + void CumulativeReporterBase::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { + m_sectionStack.back()->assertionsAndBenchmarks.emplace_back(benchmarkStats); + } + + void + CumulativeReporterBase::sectionStarting( SectionInfo const& sectionInfo ) { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + SectionNode* node; + if ( m_sectionStack.empty() ) { + if ( !m_rootSection ) { + m_rootSection = + Detail::make_unique( incompleteStats ); + } + node = m_rootSection.get(); + } else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if ( it == parentNode.childSections.end() ) { + auto newNode = + Detail::make_unique( incompleteStats ); + node = newNode.get(); + parentNode.childSections.push_back( CATCH_MOVE( newNode ) ); + } else { + node = it->get(); + } + } + + m_deepestSection = node; + m_sectionStack.push_back( node ); + } + + void CumulativeReporterBase::assertionEnded( + AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + if ( m_shouldStoreFailedAssertions && + !assertionStats.assertionResult.isOk() ) { + static_cast( + assertionStats.assertionResult.getExpandedExpression() ); + } + if ( m_shouldStoreSuccesfulAssertions && + assertionStats.assertionResult.isOk() ) { + static_cast( + assertionStats.assertionResult.getExpandedExpression() ); + } + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertionsAndBenchmarks.emplace_back( assertionStats ); + } + + void CumulativeReporterBase::sectionEnded( SectionStats const& sectionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + + void CumulativeReporterBase::testCaseEnded( + TestCaseStats const& testCaseStats ) { + auto node = Detail::make_unique( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( CATCH_MOVE(m_rootSection) ); + m_testCases.push_back( CATCH_MOVE(node) ); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + + + void CumulativeReporterBase::testRunEnded( TestRunStats const& testRunStats ) { + assert(!m_testRun && "CumulativeReporterBase assumes there can only be one test run"); + m_testRun = Detail::make_unique( testRunStats ); + m_testRun->children.swap( m_testCases ); + testRunEndedCumulative(); + } + + bool CumulativeReporterBase::SectionNode::hasAnyAssertions() const { + return std::any_of( + assertionsAndBenchmarks.begin(), + assertionsAndBenchmarks.end(), + []( Detail::AssertionOrBenchmarkResult const& res ) { + return res.isAssertion(); + } ); + } + +} // end namespace Catch + + + + +namespace Catch { + + void EventListenerBase::fatalErrorEncountered( StringRef ) {} + + void EventListenerBase::benchmarkPreparing( StringRef ) {} + void EventListenerBase::benchmarkStarting( BenchmarkInfo const& ) {} + void EventListenerBase::benchmarkEnded( BenchmarkStats<> const& ) {} + void EventListenerBase::benchmarkFailed( StringRef ) {} + + void EventListenerBase::assertionStarting( AssertionInfo const& ) {} + + void EventListenerBase::assertionEnded( AssertionStats const& ) {} + void EventListenerBase::listReporters( + std::vector const& ) {} + void EventListenerBase::listListeners( + std::vector const& ) {} + void EventListenerBase::listTests( std::vector const& ) {} + void EventListenerBase::listTags( std::vector const& ) {} + void EventListenerBase::noMatchingTestCases( StringRef ) {} + void EventListenerBase::reportInvalidTestSpec( StringRef ) {} + void EventListenerBase::testRunStarting( TestRunInfo const& ) {} + void EventListenerBase::testCaseStarting( TestCaseInfo const& ) {} + void EventListenerBase::testCasePartialStarting(TestCaseInfo const&, uint64_t) {} + void EventListenerBase::sectionStarting( SectionInfo const& ) {} + void EventListenerBase::sectionEnded( SectionStats const& ) {} + void EventListenerBase::testCasePartialEnded(TestCaseStats const&, uint64_t) {} + void EventListenerBase::testCaseEnded( TestCaseStats const& ) {} + void EventListenerBase::testRunEnded( TestRunStats const& ) {} + void EventListenerBase::skipTest( TestCaseInfo const& ) {} +} // namespace Catch + + + + +#include +#include +#include +#include +#include + +namespace Catch { + + namespace { + void listTestNamesOnly(std::ostream& out, + std::vector const& tests) { + for (auto const& test : tests) { + auto const& testCaseInfo = test.getTestCaseInfo(); + + if (startsWith(testCaseInfo.name, '#')) { + out << '"' << testCaseInfo.name << '"'; + } else { + out << testCaseInfo.name; + } + + out << '\n'; + } + out << std::flush; + } + } // end unnamed namespace + + + // Because formatting using c++ streams is stateful, drop down to C is + // required Alternatively we could use stringstream, but its performance + // is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + size_t printedLength = static_cast( + sprintf_s( buffer, "%.3f", duration ) ); +#else + size_t printedLength = static_cast( + std::snprintf( buffer, maxDoubleSize, "%.3f", duration ) ); +#endif + return std::string( buffer, printedLength ); + } + + bool shouldShowDuration( IConfig const& config, double duration ) { + if ( config.showDurations() == ShowDurations::Always ) { + return true; + } + if ( config.showDurations() == ShowDurations::Never ) { + return false; + } + const double min = config.minDuration(); + return min >= 0 && duration >= min; + } + + std::string serializeFilters( std::vector const& filters ) { + // We add a ' ' separator between each filter + size_t serialized_size = filters.size() - 1; + for (auto const& filter : filters) { + serialized_size += filter.size(); + } + + std::string serialized; + serialized.reserve(serialized_size); + bool first = true; + + for (auto const& filter : filters) { + if (!first) { + serialized.push_back(' '); + } + first = false; + serialized.append(filter); + } + + return serialized; + } + + std::ostream& operator<<( std::ostream& out, lineOfChars value ) { + for ( size_t idx = 0; idx < CATCH_CONFIG_CONSOLE_WIDTH - 1; ++idx ) { + out.put( value.c ); + } + return out; + } + + void + defaultListReporters( std::ostream& out, + std::vector const& descriptions, + Verbosity verbosity ) { + out << "Available reporters:\n"; + const auto maxNameLen = + std::max_element( descriptions.begin(), + descriptions.end(), + []( ReporterDescription const& lhs, + ReporterDescription const& rhs ) { + return lhs.name.size() < rhs.name.size(); + } ) + ->name.size(); + + for ( auto const& desc : descriptions ) { + if ( verbosity == Verbosity::Quiet ) { + out << TextFlow::Column( desc.name ) + .indent( 2 ) + .width( 5 + maxNameLen ) + << '\n'; + } else { + out << TextFlow::Column( desc.name + ':' ) + .indent( 2 ) + .width( 5 + maxNameLen ) + + TextFlow::Column( desc.description ) + .initialIndent( 0 ) + .indent( 2 ) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 ) + << '\n'; + } + } + out << '\n' << std::flush; + } + + void defaultListListeners( std::ostream& out, + std::vector const& descriptions ) { + out << "Registered listeners:\n"; + + if(descriptions.empty()) { + return; + } + + const auto maxNameLen = + std::max_element( descriptions.begin(), + descriptions.end(), + []( ListenerDescription const& lhs, + ListenerDescription const& rhs ) { + return lhs.name.size() < rhs.name.size(); + } ) + ->name.size(); + + for ( auto const& desc : descriptions ) { + out << TextFlow::Column( static_cast( desc.name ) + + ':' ) + .indent( 2 ) + .width( maxNameLen + 5 ) + + TextFlow::Column( desc.description ) + .initialIndent( 0 ) + .indent( 2 ) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 ) + << '\n'; + } + + out << '\n' << std::flush; + } + + void defaultListTags( std::ostream& out, + std::vector const& tags, + bool isFiltered ) { + if ( isFiltered ) { + out << "Tags for matching test cases:\n"; + } else { + out << "All available tags:\n"; + } + + for ( auto const& tagCount : tags ) { + ReusableStringStream rss; + rss << " " << std::setw( 2 ) << tagCount.count << " "; + auto str = rss.str(); + auto wrapper = TextFlow::Column( tagCount.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH - 10 ); + out << str << wrapper << '\n'; + } + out << pluralise(tags.size(), "tag"_sr) << "\n\n" << std::flush; + } + + void defaultListTests(std::ostream& out, ColourImpl* streamColour, std::vector const& tests, bool isFiltered, Verbosity verbosity) { + // We special case this to provide the equivalent of old + // `--list-test-names-only`, which could then be used by the + // `--input-file` option. + if (verbosity == Verbosity::Quiet) { + listTestNamesOnly(out, tests); + return; + } + + if (isFiltered) { + out << "Matching test cases:\n"; + } else { + out << "All available test cases:\n"; + } + + for (auto const& test : tests) { + auto const& testCaseInfo = test.getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + auto colourGuard = streamColour->guardColour( colour ).engage( out ); + + out << TextFlow::Column(testCaseInfo.name).indent(2) << '\n'; + if (verbosity >= Verbosity::High) { + out << TextFlow::Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << '\n'; + } + if (!testCaseInfo.tags.empty() && + verbosity > Verbosity::Quiet) { + out << TextFlow::Column(testCaseInfo.tagsAsString()).indent(6) << '\n'; + } + } + + if (isFiltered) { + out << pluralise(tests.size(), "matching test case"_sr); + } else { + out << pluralise(tests.size(), "test case"_sr); + } + out << "\n\n" << std::flush; + } + +} // namespace Catch + + + + +#include +#include +#include +#include + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + time_t rawtime; + std::time(&rawtime); + + std::tm timeInfo = {}; +#if defined (_MSC_VER) || defined (__MINGW32__) + gmtime_s(&timeInfo, &rawtime); +#else + gmtime_r(&rawtime, &timeInfo); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); + + return std::string(timeStamp, timeStampSize - 1); + } + + std::string fileNameTag(std::vector const& tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (Tag const& tag) { + return tag.original.size() > 0 + && tag.original[0] == '#'; }); + if (it != tags.end()) { + return static_cast( + it->original.substr(1, it->original.size() - 1) + ); + } + return std::string(); + } + + // Formats the duration in seconds to 3 decimal places. + // This is done because some genius defined Maven Surefire schema + // in a way that only accepts 3 decimal places, and tools like + // Jenkins use that schema for validation JUnit reporter output. + std::string formatDuration( double seconds ) { + ReusableStringStream rss; + rss << std::fixed << std::setprecision( 3 ) << seconds; + return rss.str(); + } + + static void normalizeNamespaceMarkers(std::string& str) { + std::size_t pos = str.find( "::" ); + while ( pos != str.npos ) { + str.replace( pos, 2, "." ); + pos += 1; + pos = str.find( "::", pos ); + } + } + + } // anonymous namespace + + JunitReporter::JunitReporter( ReporterConfig&& _config ) + : CumulativeReporterBase( CATCH_MOVE(_config) ), + xml( m_stream ) + { + m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertions = true; + m_shouldStoreSuccesfulAssertions = false; + } + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + } + + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } + + void JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + CumulativeReporterBase::assertionEnded( assertionStats ); + } + + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + void JunitReporter::testRunEndedCumulative() { + const auto suiteTime = suiteTimer.getElapsedSeconds(); + writeRun( *m_testRun, suiteTime ); + xml.endElement(); + } + + void JunitReporter::writeRun( TestRunNode const& testRunNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + + TestRunStats const& stats = testRunNode.value; + xml.writeAttribute( "name"_sr, stats.runInfo.name ); + xml.writeAttribute( "errors"_sr, unexpectedExceptions ); + xml.writeAttribute( "failures"_sr, stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests"_sr, stats.totals.assertions.total() ); + xml.writeAttribute( "hostname"_sr, "tbd"_sr ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time"_sr, ""_sr ); + else + xml.writeAttribute( "time"_sr, formatDuration( suiteTime ) ); + xml.writeAttribute( "timestamp"_sr, getCurrentTimestamp() ); + + // Write properties + { + auto properties = xml.scopedElement("properties"); + xml.scopedElement("property") + .writeAttribute("name"_sr, "random-seed"_sr) + .writeAttribute("value"_sr, m_config->rngSeed()); + if (m_config->hasTestFilters()) { + xml.scopedElement("property") + .writeAttribute("name"_sr, "filters"_sr) + .writeAttribute("value"_sr, serializeFilters(m_config->getTestsOrTags())); + } + } + + // Write test cases + for( auto const& child : testRunNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), XmlFormatting::Newline ); + } + + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = + static_cast( stats.testInfo->className ); + + if( className.empty() ) { + className = fileNameTag(stats.testInfo->tags); + if ( className.empty() ) { + className = "global"; + } + } + + if ( !m_config->name().empty() ) + className = static_cast(m_config->name()) + '.' + className; + + normalizeNamespaceMarkers(className); + + writeSection( className, "", rootSection, stats.testInfo->okToFail() ); + } + + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( sectionNode.hasAnyAssertions() + || !sectionNode.stdOut.empty() + || !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname"_sr, name ); + xml.writeAttribute( "name"_sr, "root"_sr ); + } + else { + xml.writeAttribute( "classname"_sr, className ); + xml.writeAttribute( "name"_sr, name ); + } + xml.writeAttribute( "time"_sr, formatDuration( sectionNode.stats.durationInSeconds ) ); + // This is not ideal, but it should be enough to mimic gtest's + // junit output. + // Ideally the JUnit reporter would also handle `skipTest` + // events and write those out appropriately. + xml.writeAttribute( "status"_sr, "run"_sr ); + + if (sectionNode.stats.assertions.failedButOk) { + xml.scopedElement("skipped") + .writeAttribute("message", "TEST_CASE tagged with !mayfail"); + } + + writeAssertions( sectionNode ); + + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), XmlFormatting::Newline ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode, testOkToFail ); + else + writeSection( className, name, *childNode, testOkToFail ); + } + + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { + if (assertionOrBenchmark.isAssertion()) { + writeAssertion(assertionOrBenchmark.asAssertion()); + } + } + } + + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + case ResultWas::ExpressionFailed: + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message"_sr, result.getExpression() ); + xml.writeAttribute( "type"_sr, result.getTestMacroName() ); + + ReusableStringStream rss; + if (stats.totals.assertions.total() > 0) { + rss << "FAILED" << ":\n"; + if (result.hasExpression()) { + rss << " "; + rss << result.getExpressionInMacro(); + rss << '\n'; + } + if (result.hasExpandedExpression()) { + rss << "with expansion:\n"; + rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } else { + rss << '\n'; + } + + if( !result.getMessage().empty() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), XmlFormatting::Newline ); + } + } + +} // end namespace Catch + + + + +#include + +namespace Catch { + void MultiReporter::updatePreferences(IEventListener const& reporterish) { + m_preferences.shouldRedirectStdOut |= + reporterish.getPreferences().shouldRedirectStdOut; + m_preferences.shouldReportAllAssertions |= + reporterish.getPreferences().shouldReportAllAssertions; + } + + void MultiReporter::addListener( IEventListenerPtr&& listener ) { + updatePreferences(*listener); + m_reporterLikes.insert(m_reporterLikes.begin() + m_insertedListeners, CATCH_MOVE(listener) ); + ++m_insertedListeners; + } + + void MultiReporter::addReporter( IEventListenerPtr&& reporter ) { + updatePreferences(*reporter); + + // We will need to output the captured stdout if there are reporters + // that do not want it captured. + // We do not consider listeners, because it is generally assumed that + // listeners are output-transparent, even though they can ask for stdout + // capture to do something with it. + m_haveNoncapturingReporters |= !reporter->getPreferences().shouldRedirectStdOut; + + // Reporters can always be placed to the back without breaking the + // reporting order + m_reporterLikes.push_back( CATCH_MOVE( reporter ) ); + } + + void MultiReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->noMatchingTestCases( unmatchedSpec ); + } + } + + void MultiReporter::fatalErrorEncountered( StringRef error ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->fatalErrorEncountered( error ); + } + } + + void MultiReporter::reportInvalidTestSpec( StringRef arg ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->reportInvalidTestSpec( arg ); + } + } + + void MultiReporter::benchmarkPreparing( StringRef name ) { + for (auto& reporterish : m_reporterLikes) { + reporterish->benchmarkPreparing(name); + } + } + void MultiReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->benchmarkStarting( benchmarkInfo ); + } + } + void MultiReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->benchmarkEnded( benchmarkStats ); + } + } + + void MultiReporter::benchmarkFailed( StringRef error ) { + for (auto& reporterish : m_reporterLikes) { + reporterish->benchmarkFailed(error); + } + } + + void MultiReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testRunStarting( testRunInfo ); + } + } + + void MultiReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCaseStarting( testInfo ); + } + } + + void + MultiReporter::testCasePartialStarting( TestCaseInfo const& testInfo, + uint64_t partNumber ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCasePartialStarting( testInfo, partNumber ); + } + } + + void MultiReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->sectionStarting( sectionInfo ); + } + } + + void MultiReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->assertionStarting( assertionInfo ); + } + } + + // The return value indicates if the messages buffer should be cleared: + void MultiReporter::assertionEnded( AssertionStats const& assertionStats ) { + const bool reportByDefault = + assertionStats.assertionResult.getResultType() != ResultWas::Ok || + m_config->includeSuccessfulResults(); + + for ( auto & reporterish : m_reporterLikes ) { + if ( reportByDefault || + reporterish->getPreferences().shouldReportAllAssertions ) { + reporterish->assertionEnded( assertionStats ); + } + } + } + + void MultiReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->sectionEnded( sectionStats ); + } + } + + void MultiReporter::testCasePartialEnded( TestCaseStats const& testStats, + uint64_t partNumber ) { + if ( m_preferences.shouldRedirectStdOut && + m_haveNoncapturingReporters ) { + if ( !testStats.stdOut.empty() ) { + Catch::cout() << testStats.stdOut << std::flush; + } + if ( !testStats.stdErr.empty() ) { + Catch::cerr() << testStats.stdErr << std::flush; + } + } + + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCasePartialEnded( testStats, partNumber ); + } + } + + void MultiReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCaseEnded( testCaseStats ); + } + } + + void MultiReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testRunEnded( testRunStats ); + } + } + + + void MultiReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->skipTest( testInfo ); + } + } + + void MultiReporter::listReporters(std::vector const& descriptions) { + for (auto& reporterish : m_reporterLikes) { + reporterish->listReporters(descriptions); + } + } + + void MultiReporter::listListeners( + std::vector const& descriptions ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->listListeners( descriptions ); + } + } + + void MultiReporter::listTests(std::vector const& tests) { + for (auto& reporterish : m_reporterLikes) { + reporterish->listTests(tests); + } + } + + void MultiReporter::listTags(std::vector const& tags) { + for (auto& reporterish : m_reporterLikes) { + reporterish->listTags(tags); + } + } + +} // end namespace Catch + + + + + +namespace Catch { + namespace Detail { + + void registerReporterImpl( std::string const& name, + IReporterFactoryPtr reporterPtr ) { + CATCH_TRY { + getMutableRegistryHub().registerReporter( + name, CATCH_MOVE( reporterPtr ) ); + } + CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead + // register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + } // namespace Detail +} // namespace Catch + + + + +#include + +namespace Catch { + + namespace { + std::string createRngSeedString(uint32_t seed) { + ReusableStringStream sstr; + sstr << "rng-seed=" << seed; + return sstr.str(); + } + } + + void SonarQubeReporter::testRunStarting(TestRunInfo const& testRunInfo) { + CumulativeReporterBase::testRunStarting(testRunInfo); + + xml.writeComment( createRngSeedString( m_config->rngSeed() ) ); + xml.startElement("testExecutions"); + xml.writeAttribute("version"_sr, '1'); + } + + void SonarQubeReporter::writeRun( TestRunNode const& runNode ) { + std::map> testsPerFile; + + for ( auto const& child : runNode.children ) { + testsPerFile[child->value.testInfo->lineInfo.file].push_back( + child.get() ); + } + + for ( auto const& kv : testsPerFile ) { + writeTestFile( kv.first, kv.second ); + } + } + + void SonarQubeReporter::writeTestFile(std::string const& filename, std::vector const& testCaseNodes) { + XmlWriter::ScopedElement e = xml.scopedElement("file"); + xml.writeAttribute("path"_sr, filename); + + for (auto const& child : testCaseNodes) + writeTestCase(*child); + } + + void SonarQubeReporter::writeTestCase(TestCaseNode const& testCaseNode) { + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert(testCaseNode.children.size() == 1); + SectionNode const& rootSection = *testCaseNode.children.front(); + writeSection("", rootSection, testCaseNode.value.testInfo->okToFail()); + } + + void SonarQubeReporter::writeSection(std::string const& rootName, SectionNode const& sectionNode, bool okToFail) { + std::string name = trim(sectionNode.stats.sectionInfo.name); + if (!rootName.empty()) + name = rootName + '/' + name; + + if ( sectionNode.hasAnyAssertions() + || !sectionNode.stdOut.empty() + || !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement("testCase"); + xml.writeAttribute("name"_sr, name); + xml.writeAttribute("duration"_sr, static_cast(sectionNode.stats.durationInSeconds * 1000)); + + writeAssertions(sectionNode, okToFail); + } + + for (auto const& childNode : sectionNode.childSections) + writeSection(name, *childNode, okToFail); + } + + void SonarQubeReporter::writeAssertions(SectionNode const& sectionNode, bool okToFail) { + for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { + if (assertionOrBenchmark.isAssertion()) { + writeAssertion(assertionOrBenchmark.asAssertion(), okToFail); + } + } + } + + void SonarQubeReporter::writeAssertion(AssertionStats const& stats, bool okToFail) { + AssertionResult const& result = stats.assertionResult; + if (!result.isOk()) { + std::string elementName; + if (okToFail) { + elementName = "skipped"; + } else { + switch (result.getResultType()) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + } + + XmlWriter::ScopedElement e = xml.scopedElement(elementName); + + ReusableStringStream messageRss; + messageRss << result.getTestMacroName() << '(' << result.getExpression() << ')'; + xml.writeAttribute("message"_sr, messageRss.str()); + + ReusableStringStream textRss; + if (stats.totals.assertions.total() > 0) { + textRss << "FAILED:\n"; + if (result.hasExpression()) { + textRss << '\t' << result.getExpressionInMacro() << '\n'; + } + if (result.hasExpandedExpression()) { + textRss << "with expansion:\n\t" << result.getExpandedExpression() << '\n'; + } + } + + if (!result.getMessage().empty()) + textRss << result.getMessage() << '\n'; + + for (auto const& msg : stats.infoMessages) + if (msg.type == ResultWas::Info) + textRss << msg.message << '\n'; + + textRss << "at " << result.getSourceInfo(); + xml.writeText(textRss.str(), XmlFormatting::Newline); + } + } + +} // end namespace Catch + + + +namespace Catch { + + StreamingReporterBase::~StreamingReporterBase() = default; + + void + StreamingReporterBase::testRunStarting( TestRunInfo const& _testRunInfo ) { + currentTestRunInfo = _testRunInfo; + } + + void StreamingReporterBase::testRunEnded( TestRunStats const& ) { + currentTestCaseInfo = nullptr; + } + +} // end namespace Catch + + + +#include +#include +#include + +namespace Catch { + + namespace { + // Yes, this has to be outside the class and namespaced by naming. + // Making older compiler happy is hard. + static constexpr StringRef tapFailedString = "not ok"_sr; + static constexpr StringRef tapPassedString = "ok"_sr; + static constexpr Colour::Code tapDimColour = Colour::FileName; + + class TapAssertionPrinter { + public: + TapAssertionPrinter& operator= (TapAssertionPrinter const&) = delete; + TapAssertionPrinter(TapAssertionPrinter const&) = delete; + TapAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter, ColourImpl* colour_) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(true) + , counter(_counter) + , colourImpl( colour_ ) {} + + void print() { + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(tapPassedString); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + printResultType(tapPassedString); + } else { + printResultType(tapFailedString); + } + printOriginalExpression(); + printReconstructedExpression(); + if (result.isOk()) { + printIssue(" # TODO"); + } + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(tapFailedString); + printIssue("unexpected exception with message:"_sr); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(tapFailedString); + printIssue("fatal error condition with message:"_sr); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(tapFailedString); + printIssue("expected exception, got none"_sr); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType("info"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType("warning"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(tapFailedString); + printIssue("explicitly"_sr); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType("** internal error **"_sr); + break; + } + } + + private: + void printResultType(StringRef passOrFail) const { + if (!passOrFail.empty()) { + stream << passOrFail << ' ' << counter << " -"; + } + } + + void printIssue(StringRef issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + stream << colourImpl->guardColour( tapDimColour ) + << " expression was:"; + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << colourImpl->guardColour( tapDimColour ) << " for: "; + + std::string expr = result.getExpandedExpression(); + std::replace(expr.begin(), expr.end(), '\n', ' '); + stream << expr; + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = tapDimColour) { + if (itMessage == messages.end()) { + return; + } + + // using messages.end() directly (or auto) yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast(std::distance(itMessage, itEnd)); + + stream << colourImpl->guardColour( colour ) << " with " + << pluralise( N, "message"_sr ) << ':'; + + for (; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + stream << colourImpl->guardColour(tapDimColour) << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + std::size_t counter; + ColourImpl* colourImpl; + }; + + } // End anonymous namespace + + void TAPReporter::testRunStarting( TestRunInfo const& ) { + m_stream << "# rng-seed: " << m_config->rngSeed() << '\n'; + } + + void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + m_stream << "# No test cases matched '" << unmatchedSpec << "'\n"; + } + + void TAPReporter::assertionEnded(AssertionStats const& _assertionStats) { + ++counter; + + m_stream << "# " << currentTestCaseInfo->name << '\n'; + TapAssertionPrinter printer(m_stream, _assertionStats, counter, m_colour.get()); + printer.print(); + + m_stream << '\n' << std::flush; + } + + void TAPReporter::testRunEnded(TestRunStats const& _testRunStats) { + m_stream << "1.." << _testRunStats.totals.assertions.total(); + if (_testRunStats.totals.testCases.total() == 0) { + m_stream << " # Skipped: No tests ran."; + } + m_stream << "\n\n" << std::flush; + StreamingReporterBase::testRunEnded(_testRunStats); + } + + + + +} // end namespace Catch + + + + +#include +#include + +namespace Catch { + + namespace { + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::ostream& os, std::string const& _string, std::size_t indent = 0) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + os << TextFlow::Column(_string) + .indent(indent + i) + .initialIndent(indent) << '\n'; + } + + std::string escape(StringRef str) { + std::string escaped = static_cast(str); + replaceInPlace(escaped, "|", "||"); + replaceInPlace(escaped, "'", "|'"); + replaceInPlace(escaped, "\n", "|n"); + replaceInPlace(escaped, "\r", "|r"); + replaceInPlace(escaped, "[", "|["); + replaceInPlace(escaped, "]", "|]"); + return escaped; + } + } // end anonymous namespace + + + TeamCityReporter::~TeamCityReporter() {} + + void TeamCityReporter::testRunStarting( TestRunInfo const& runInfo ) { + m_stream << "##teamcity[testSuiteStarted name='" << escape( runInfo.name ) + << "']\n"; + } + + void TeamCityReporter::testRunEnded( TestRunStats const& runStats ) { + m_stream << "##teamcity[testSuiteFinished name='" + << escape( runStats.runInfo.name ) << "']\n"; + } + + void TeamCityReporter::assertionEnded(AssertionStats const& assertionStats) { + AssertionResult const& result = assertionStats.assertionResult; + if (!result.isOk()) { + + ReusableStringStream msg; + if (!m_headerPrintedForThisSection) + printSectionHeader(msg.get()); + m_headerPrintedForThisSection = true; + + msg << result.getSourceInfo() << '\n'; + + switch (result.getResultType()) { + case ResultWas::ExpressionFailed: + msg << "expression failed"; + break; + case ResultWas::ThrewException: + msg << "unexpected exception"; + break; + case ResultWas::FatalErrorCondition: + msg << "fatal error condition"; + break; + case ResultWas::DidntThrowException: + msg << "no exception was thrown where one was expected"; + break; + case ResultWas::ExplicitFailure: + msg << "explicit failure"; + break; + + // We shouldn't get here because of the isOk() test + case ResultWas::Ok: + case ResultWas::Info: + case ResultWas::Warning: + CATCH_ERROR("Internal error in TeamCity reporter"); + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + CATCH_ERROR("Not implemented"); + } + if (assertionStats.infoMessages.size() == 1) + msg << " with message:"; + if (assertionStats.infoMessages.size() > 1) + msg << " with messages:"; + for (auto const& messageInfo : assertionStats.infoMessages) + msg << "\n \"" << messageInfo.message << '"'; + + + if (result.hasExpression()) { + msg << + "\n " << result.getExpressionInMacro() << "\n" + "with expansion:\n" + " " << result.getExpandedExpression() << '\n'; + } + + if (currentTestCaseInfo->okToFail()) { + msg << "- failure ignore as test marked as 'ok to fail'\n"; + m_stream << "##teamcity[testIgnored" + << " name='" << escape(currentTestCaseInfo->name) << '\'' + << " message='" << escape(msg.str()) << '\'' + << "]\n"; + } else { + m_stream << "##teamcity[testFailed" + << " name='" << escape(currentTestCaseInfo->name) << '\'' + << " message='" << escape(msg.str()) << '\'' + << "]\n"; + } + } + m_stream.flush(); + } + + void TeamCityReporter::testCaseStarting(TestCaseInfo const& testInfo) { + m_testTimer.start(); + StreamingReporterBase::testCaseStarting(testInfo); + m_stream << "##teamcity[testStarted name='" + << escape(testInfo.name) << "']\n"; + m_stream.flush(); + } + + void TeamCityReporter::testCaseEnded(TestCaseStats const& testCaseStats) { + StreamingReporterBase::testCaseEnded(testCaseStats); + auto const& testCaseInfo = *testCaseStats.testInfo; + if (!testCaseStats.stdOut.empty()) + m_stream << "##teamcity[testStdOut name='" + << escape(testCaseInfo.name) + << "' out='" << escape(testCaseStats.stdOut) << "']\n"; + if (!testCaseStats.stdErr.empty()) + m_stream << "##teamcity[testStdErr name='" + << escape(testCaseInfo.name) + << "' out='" << escape(testCaseStats.stdErr) << "']\n"; + m_stream << "##teamcity[testFinished name='" + << escape(testCaseInfo.name) << "' duration='" + << m_testTimer.getElapsedMilliseconds() << "']\n"; + m_stream.flush(); + } + + void TeamCityReporter::printSectionHeader(std::ostream& os) { + assert(!m_sectionStack.empty()); + + if (m_sectionStack.size() > 1) { + os << lineOfChars('-') << '\n'; + + std::vector::const_iterator + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(os, it->name); + os << lineOfChars('-') << '\n'; + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + os << lineInfo << '\n'; + os << lineOfChars('.') << "\n\n"; + } + +} // end namespace Catch + + + + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + XmlReporter::XmlReporter( ReporterConfig&& _config ) + : StreamingReporterBase( CATCH_MOVE(_config) ), + m_xml(m_stream) + { + m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertions = true; + } + + XmlReporter::~XmlReporter() = default; + + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } + + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } + + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename"_sr, sourceInfo.file ) + .writeAttribute( "line"_sr, sourceInfo.line ); + } + + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement("Catch2TestRun") + .writeAttribute("name"_sr, m_config->name()) + .writeAttribute("rng-seed"_sr, m_config->rngSeed()) + .writeAttribute("catch2-version"_sr, libraryVersion()); + if (m_config->testSpec().hasFilters()) + m_xml.writeAttribute( "filters"_sr, serializeFilters( m_config->getTestsOrTags() ) ); + } + + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name"_sr, trim( testInfo.name ) ) + .writeAttribute( "tags"_sr, testInfo.tagsAsString() ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name"_sr, trim( sectionInfo.name ) ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + void XmlReporter::assertionStarting( AssertionInfo const& ) { } + + void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return; + + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success"_sr, result.succeeded() ) + .writeAttribute( "type"_sr, result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + } + + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes"_sr, sectionStats.assertions.passed ); + e.writeAttribute( "failures"_sr, sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures"_sr, sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds"_sr, sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success"_sr, testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds"_sr, m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), XmlFormatting::Newline ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), XmlFormatting::Newline ); + + m_xml.endElement(); + } + + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes"_sr, testRunStats.totals.assertions.passed ) + .writeAttribute( "failures"_sr, testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures"_sr, testRunStats.totals.assertions.failedButOk ); + m_xml.scopedElement( "OverallResultsCases") + .writeAttribute( "successes"_sr, testRunStats.totals.testCases.passed ) + .writeAttribute( "failures"_sr, testRunStats.totals.testCases.failed ) + .writeAttribute( "expectedFailures"_sr, testRunStats.totals.testCases.failedButOk ); + m_xml.endElement(); + } + + void XmlReporter::benchmarkPreparing( StringRef name ) { + m_xml.startElement("BenchmarkResults") + .writeAttribute("name"_sr, name); + } + + void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { + m_xml.writeAttribute("samples"_sr, info.samples) + .writeAttribute("resamples"_sr, info.resamples) + .writeAttribute("iterations"_sr, info.iterations) + .writeAttribute("clockResolution"_sr, info.clockResolution) + .writeAttribute("estimatedDuration"_sr, info.estimatedDuration) + .writeComment("All values in nano seconds"_sr); + } + + void XmlReporter::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { + m_xml.startElement("mean") + .writeAttribute("value"_sr, benchmarkStats.mean.point.count()) + .writeAttribute("lowerBound"_sr, benchmarkStats.mean.lower_bound.count()) + .writeAttribute("upperBound"_sr, benchmarkStats.mean.upper_bound.count()) + .writeAttribute("ci"_sr, benchmarkStats.mean.confidence_interval); + m_xml.endElement(); + m_xml.startElement("standardDeviation") + .writeAttribute("value"_sr, benchmarkStats.standardDeviation.point.count()) + .writeAttribute("lowerBound"_sr, benchmarkStats.standardDeviation.lower_bound.count()) + .writeAttribute("upperBound"_sr, benchmarkStats.standardDeviation.upper_bound.count()) + .writeAttribute("ci"_sr, benchmarkStats.standardDeviation.confidence_interval); + m_xml.endElement(); + m_xml.startElement("outliers") + .writeAttribute("variance"_sr, benchmarkStats.outlierVariance) + .writeAttribute("lowMild"_sr, benchmarkStats.outliers.low_mild) + .writeAttribute("lowSevere"_sr, benchmarkStats.outliers.low_severe) + .writeAttribute("highMild"_sr, benchmarkStats.outliers.high_mild) + .writeAttribute("highSevere"_sr, benchmarkStats.outliers.high_severe); + m_xml.endElement(); + m_xml.endElement(); + } + + void XmlReporter::benchmarkFailed(StringRef error) { + m_xml.scopedElement("failed"). + writeAttribute("message"_sr, error); + m_xml.endElement(); + } + + void XmlReporter::listReporters(std::vector const& descriptions) { + auto outerTag = m_xml.scopedElement("AvailableReporters"); + for (auto const& reporter : descriptions) { + auto inner = m_xml.scopedElement("Reporter"); + m_xml.startElement("Name", XmlFormatting::Indent) + .writeText(reporter.name, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Description", XmlFormatting::Indent) + .writeText(reporter.description, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + + void XmlReporter::listListeners(std::vector const& descriptions) { + auto outerTag = m_xml.scopedElement( "RegisteredListeners" ); + for ( auto const& listener : descriptions ) { + auto inner = m_xml.scopedElement( "Listener" ); + m_xml.startElement( "Name", XmlFormatting::Indent ) + .writeText( listener.name, XmlFormatting::None ) + .endElement( XmlFormatting::Newline ); + m_xml.startElement( "Description", XmlFormatting::Indent ) + .writeText( listener.description, XmlFormatting::None ) + .endElement( XmlFormatting::Newline ); + } + } + + void XmlReporter::listTests(std::vector const& tests) { + auto outerTag = m_xml.scopedElement("MatchingTests"); + for (auto const& test : tests) { + auto innerTag = m_xml.scopedElement("TestCase"); + auto const& testInfo = test.getTestCaseInfo(); + m_xml.startElement("Name", XmlFormatting::Indent) + .writeText(testInfo.name, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("ClassName", XmlFormatting::Indent) + .writeText(testInfo.className, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Tags", XmlFormatting::Indent) + .writeText(testInfo.tagsAsString(), XmlFormatting::None) + .endElement(XmlFormatting::Newline); + + auto sourceTag = m_xml.scopedElement("SourceInfo"); + m_xml.startElement("File", XmlFormatting::Indent) + .writeText(testInfo.lineInfo.file, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Line", XmlFormatting::Indent) + .writeText(std::to_string(testInfo.lineInfo.line), XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + + void XmlReporter::listTags(std::vector const& tags) { + auto outerTag = m_xml.scopedElement("TagsFromMatchingTests"); + for (auto const& tag : tags) { + auto innerTag = m_xml.scopedElement("Tag"); + m_xml.startElement("Count", XmlFormatting::Indent) + .writeText(std::to_string(tag.count), XmlFormatting::None) + .endElement(XmlFormatting::Newline); + auto aliasTag = m_xml.scopedElement("Aliases"); + for (auto const& alias : tag.spellings) { + m_xml.startElement("Alias", XmlFormatting::Indent) + .writeText(alias, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + } + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/Tests/Contrib/Catch/catch_amalgamated.hpp b/Tests/Contrib/Catch/catch_amalgamated.hpp new file mode 100644 index 00000000..cff2cedf --- /dev/null +++ b/Tests/Contrib/Catch/catch_amalgamated.hpp @@ -0,0 +1,12260 @@ +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +// Catch v3.1.1 +// Generated: 2022-10-17 18:47:20.510385 +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- +#ifndef CATCH_AMALGAMATED_HPP_INCLUDED +#define CATCH_AMALGAMATED_HPP_INCLUDED + + +/** \file + * This is a convenience header for Catch2. It includes **all** of Catch2 headers. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the top level folder, or to the + * corresponding internal subfolder, it should be added here. Headers + * added to the various subparts (e.g. matchers, generators, etc...), + * should go their respective catch-all headers. + */ + +#ifndef CATCH_ALL_HPP_INCLUDED +#define CATCH_ALL_HPP_INCLUDED + + + +/** \file + * This is a convenience header for Catch2's benchmarking. It includes + * **all** of Catch2 headers related to benchmarking. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the `benchmark` folder, or to + * the corresponding internal (detail) subfolder, it should be added here. + */ + +#ifndef CATCH_BENCHMARK_ALL_HPP_INCLUDED +#define CATCH_BENCHMARK_ALL_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_HPP_INCLUDED +#define CATCH_BENCHMARK_HPP_INCLUDED + + + +#ifndef CATCH_INTERFACES_CONFIG_HPP_INCLUDED +#define CATCH_INTERFACES_CONFIG_HPP_INCLUDED + + + +#ifndef CATCH_NONCOPYABLE_HPP_INCLUDED +#define CATCH_NONCOPYABLE_HPP_INCLUDED + +namespace Catch { + namespace Detail { + + //! Deriving classes become noncopyable and nonmovable + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable&& ) = delete; + NonCopyable& operator=( NonCopyable const& ) = delete; + NonCopyable& operator=( NonCopyable&& ) = delete; + + protected: + NonCopyable() noexcept = default; + }; + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_NONCOPYABLE_HPP_INCLUDED + + +#ifndef CATCH_STRINGREF_HPP_INCLUDED +#define CATCH_STRINGREF_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef other ) const noexcept -> bool; + auto operator != (StringRef other) const noexcept -> bool { + return !(*this == other); + } + + constexpr auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + bool operator<(StringRef rhs) const noexcept; + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, start + size()). + // If start > size(), then the substring is empty. + constexpr StringRef substr(size_type start, size_type length) const noexcept { + if (start < m_size) { + const auto shortened_size = m_size - start; + return StringRef(m_start + start, (shortened_size < length) ? shortened_size : length); + } else { + return StringRef(); + } + } + + // Returns the current start pointer. May not be null-terminated. + constexpr char const* data() const noexcept { + return m_start; + } + + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + + + friend std::string& operator += (std::string& lhs, StringRef sr); + friend std::ostream& operator << (std::ostream& os, StringRef sr); + friend std::string operator+(StringRef lhs, StringRef rhs); + + /** + * Provides a three-way comparison with rhs + * + * Returns negative number if lhs < rhs, 0 if lhs == rhs, and a positive + * number if lhs > rhs + */ + int compare( StringRef rhs ) const; + }; + + + constexpr auto operator ""_sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator ""_catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +#endif // CATCH_STRINGREF_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + //! A test case or leaf section did not run any assertions + NoAssertions = 0x01, + //! A command line test spec matched no test cases + UnmatchedTestSpec = 0x02, + }; }; + + enum class ShowDurations { + DefaultForReporter, + Always, + Never + }; + enum class TestRunOrder { + Declared, + LexicographicallySorted, + Randomized + }; + enum class ColourMode : std::uint8_t { + //! Let Catch2 pick implementation based on platform detection + PlatformDefault, + //! Use ANSI colour code escapes + ANSI, + //! Use Win32 console colour API + Win32, + //! Don't use any colour + None + }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + class IStream; + + class IConfig : public Detail::NonCopyable { + public: + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual StringRef name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutUnmatchedTestSpecs() const = 0; + virtual bool zeroTestsCountAsSuccess() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations showDurations() const = 0; + virtual double minDuration() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual std::vector const& getTestsOrTags() const = 0; + virtual TestRunOrder runOrder() const = 0; + virtual uint32_t rngSeed() const = 0; + virtual unsigned int shardCount() const = 0; + virtual unsigned int shardIndex() const = 0; + virtual ColourMode defaultColourMode() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + + virtual bool skipBenchmarks() const = 0; + virtual bool benchmarkNoAnalysis() const = 0; + virtual unsigned int benchmarkSamples() const = 0; + virtual double benchmarkConfidenceInterval() const = 0; + virtual unsigned int benchmarkResamples() const = 0; + virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; + }; +} + +#endif // CATCH_INTERFACES_CONFIG_HPP_INCLUDED + + +#ifndef CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED +#define CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + + + +#ifndef CATCH_PLATFORM_HPP_INCLUDED +#define CATCH_PLATFORM_HPP_INCLUDED + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS + +# if defined( WINAPI_FAMILY ) && ( WINAPI_FAMILY == WINAPI_FAMILY_APP ) +# define CATCH_PLATFORM_WINDOWS_UWP +# endif +#endif + +#endif // CATCH_PLATFORM_HPP_INCLUDED + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +// This only works on GCC 9+. so we have to also add a global suppression of Wparentheses +// for older versions of GCC. +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wuseless-cast\"" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__CUDACC__) && !defined(__clang__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic pop" ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "nv_diag_suppress 177" ) +#endif + +// clang-cl defines _MSC_VER as well as __clang__, which could cause the +// start/stop internal suppression macros to be double defined. +#if defined(__clang__) && !defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ && !_MSC_VER + +#if defined(__clang__) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which +// results in calls to the immediately evaluated lambda expressions to be +// reported as unevaluated lambdas. +// https://developer.nvidia.com/nvidia_bug/3321845. +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) && !defined( __NVCOMPILER ) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(CATCH_PLATFORM_WINDOWS_UWP) +# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + + +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined( CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED ) && \ + !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) && \ + !defined( CATCH_CONFIG_NO_DISABLE_EXCEPTIONS ) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +#if defined( CATCH_PLATFORM_WINDOWS ) && \ + !defined( CATCH_CONFIG_COLOUR_WIN32 ) && \ + !defined( CATCH_CONFIG_NO_COLOUR_WIN32 ) && \ + !defined( CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 ) +# define CATCH_CONFIG_COLOUR_WIN32 +#endif + +#if defined( CATCH_CONFIG_SHARED_LIBRARY ) && defined( _MSC_VER ) && \ + !defined( CATCH_CONFIG_STATIC ) +# ifdef Catch2_EXPORTS +# define CATCH_EXPORT //__declspec( dllexport ) // not needed +# else +# define CATCH_EXPORT __declspec( dllimport ) +# endif +#else +# define CATCH_EXPORT +#endif + +#endif // CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + + +#ifndef CATCH_CONTEXT_HPP_INCLUDED +#define CATCH_CONTEXT_HPP_INCLUDED + + +namespace Catch { + + class IResultCapture; + class IConfig; + + class IContext { + public: + virtual ~IContext(); // = default + + virtual IResultCapture* getResultCapture() = 0; + virtual IConfig const* getConfig() const = 0; + }; + + class IMutableContext : public IContext { + public: + ~IMutableContext() override; // = default + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setConfig( IConfig const* config ) = 0; + + private: + CATCH_EXPORT static IMutableContext* currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); + }; + + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return *IMutableContext::currentContext; + } + + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } + + void cleanUpContext(); + + class SimplePcg32; + SimplePcg32& sharedRng(); +} + +#endif // CATCH_CONTEXT_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_REPORTER_HPP_INCLUDED +#define CATCH_INTERFACES_REPORTER_HPP_INCLUDED + + + +#ifndef CATCH_SECTION_INFO_HPP_INCLUDED +#define CATCH_SECTION_INFO_HPP_INCLUDED + + + +#ifndef CATCH_MOVE_AND_FORWARD_HPP_INCLUDED +#define CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + +#include + +//! Replacement for std::move with better compile time performance +#define CATCH_MOVE(...) static_cast&&>(__VA_ARGS__) + +//! Replacement for std::forward with better compile time performance +#define CATCH_FORWARD(...) static_cast(__VA_ARGS__) + +#endif // CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + + +#ifndef CATCH_SOURCE_LINE_INFO_HPP_INCLUDED +#define CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct SourceLineInfo { + + SourceLineInfo() = delete; + constexpr SourceLineInfo( char const* _file, std::size_t _line ) noexcept: + file( _file ), + line( _line ) + {} + + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + + friend std::ostream& operator << (std::ostream& os, SourceLineInfo const& info); + }; +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +#endif // CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + + +#ifndef CATCH_TOTALS_HPP_INCLUDED +#define CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::uint64_t total() const; + bool allPassed() const; + bool allOk() const; + + std::uint64_t passed = 0; + std::uint64_t failed = 0; + std::uint64_t failedButOk = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + Counts assertions; + Counts testCases; + }; +} + +#endif // CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct SectionInfo { + // The last argument is ignored, so that people can write + // SECTION("ShortName", "Proper description that is long") and + // still use the `-c` flag comfortably. + SectionInfo( SourceLineInfo const& _lineInfo, std::string _name, + const char* const = nullptr ): + name(CATCH_MOVE(_name)), + lineInfo(_lineInfo) + {} + + std::string name; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +#endif // CATCH_SECTION_INFO_HPP_INCLUDED + + +#ifndef CATCH_ASSERTION_RESULT_HPP_INCLUDED +#define CATCH_ASSERTION_RESULT_HPP_INCLUDED + + + +#ifndef CATCH_ASSERTION_INFO_HPP_INCLUDED +#define CATCH_ASSERTION_INFO_HPP_INCLUDED + + + +#ifndef CATCH_RESULT_TYPE_HPP_INCLUDED +#define CATCH_RESULT_TYPE_HPP_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +#endif // CATCH_RESULT_TYPE_HPP_INCLUDED + +namespace Catch { + + struct AssertionInfo { + // AssertionInfo() = delete; + + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_INFO_HPP_INCLUDED + + +#ifndef CATCH_LAZY_EXPR_HPP_INCLUDED +#define CATCH_LAZY_EXPR_HPP_INCLUDED + +#include + +namespace Catch { + + class ITransientExpression; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ): + m_isNegated(isNegated) + {} + LazyExpression(LazyExpression const& other) = default; + LazyExpression& operator = ( LazyExpression const& ) = delete; + + explicit operator bool() const { + return m_transientExpression != nullptr; + } + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + +} // namespace Catch + +#endif // CATCH_LAZY_EXPR_HPP_INCLUDED + +#include + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + StringRef getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_RESULT_HPP_INCLUDED + + +#ifndef CATCH_MESSAGE_INFO_HPP_INCLUDED +#define CATCH_MESSAGE_INFO_HPP_INCLUDED + + + +#ifndef CATCH_INTERFACES_CAPTURE_HPP_INCLUDED +#define CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + +#include +#include + + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct MessageBuilder; + struct Counts; + struct AssertionReaction; + struct SourceLineInfo; + + class ITransientExpression; + class IGeneratorTracker; + + struct BenchmarkInfo; + template > + struct BenchmarkStats; + + class IResultCapture { + public: + virtual ~IResultCapture(); + + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + + virtual auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; + + virtual void benchmarkPreparing( StringRef name ) = 0; + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; + virtual void benchmarkFailed( StringRef error ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void emplaceUnscopedMessage( MessageBuilder const& builder ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( StringRef _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == (MessageInfo const& other) const { + return sequence == other.sequence; + } + bool operator < (MessageInfo const& other) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + +} // end namespace Catch + +#endif // CATCH_MESSAGE_INFO_HPP_INCLUDED + + +#ifndef CATCH_UNIQUE_PTR_HPP_INCLUDED +#define CATCH_UNIQUE_PTR_HPP_INCLUDED + +#include +#include + + +namespace Catch { +namespace Detail { + /** + * A reimplementation of `std::unique_ptr` for improved compilation performance + * + * Does not support arrays nor custom deleters. + */ + template + class unique_ptr { + T* m_ptr; + public: + constexpr unique_ptr(std::nullptr_t = nullptr): + m_ptr{} + {} + explicit constexpr unique_ptr(T* ptr): + m_ptr(ptr) + {} + + template ::value>> + unique_ptr(unique_ptr&& from): + m_ptr(from.release()) + {} + + template ::value>> + unique_ptr& operator=(unique_ptr&& from) { + reset(from.release()); + + return *this; + } + + unique_ptr(unique_ptr const&) = delete; + unique_ptr& operator=(unique_ptr const&) = delete; + + unique_ptr(unique_ptr&& rhs) noexcept: + m_ptr(rhs.m_ptr) { + rhs.m_ptr = nullptr; + } + unique_ptr& operator=(unique_ptr&& rhs) noexcept { + reset(rhs.release()); + + return *this; + } + + ~unique_ptr() { + delete m_ptr; + } + + T& operator*() { + assert(m_ptr); + return *m_ptr; + } + T const& operator*() const { + assert(m_ptr); + return *m_ptr; + } + T* operator->() noexcept { + assert(m_ptr); + return m_ptr; + } + T const* operator->() const noexcept { + assert(m_ptr); + return m_ptr; + } + + T* get() { return m_ptr; } + T const* get() const { return m_ptr; } + + void reset(T* ptr = nullptr) { + delete m_ptr; + m_ptr = ptr; + } + + T* release() { + auto temp = m_ptr; + m_ptr = nullptr; + return temp; + } + + explicit operator bool() const { + return m_ptr; + } + + friend void swap(unique_ptr& lhs, unique_ptr& rhs) { + auto temp = lhs.m_ptr; + lhs.m_ptr = rhs.m_ptr; + rhs.m_ptr = temp; + } + }; + + //! Specialization to cause compile-time error for arrays + template + class unique_ptr; + + template + unique_ptr make_unique(Args&&... args) { + return unique_ptr(new T(CATCH_FORWARD(args)...)); + } + + +} // end namespace Detail +} // end namespace Catch + +#endif // CATCH_UNIQUE_PTR_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_HPP_INCLUDED +#define CATCH_ESTIMATE_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + template + struct Estimate { + Duration point; + Duration lower_bound; + Duration upper_bound; + double confidence_interval; + + template + operator Estimate() const { + return { point, lower_bound, upper_bound, confidence_interval }; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED +#define CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + struct OutlierClassification { + int samples_seen = 0; + int low_severe = 0; // more than 3 times IQR below Q1 + int low_mild = 0; // 1.5 to 3 times IQR below Q1 + int high_mild = 0; // 1.5 to 3 times IQR above Q3 + int high_severe = 0; // more than 3 times IQR above Q3 + + int total() const { + return low_severe + low_mild + high_mild + high_severe; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OUTLIERS_CLASSIFICATION_HPP_INCLUDED + + +#include +#include +#include +#include + +namespace Catch { + + struct ReporterDescription; + struct ListenerDescription; + struct TagInfo; + struct TestCaseInfo; + class TestCaseHandle; + class IConfig; + class IStream; + enum class ColourMode : std::uint8_t; + + struct ReporterConfig { + ReporterConfig( IConfig const* _fullConfig, + Detail::unique_ptr _stream, + ColourMode colourMode, + std::map customOptions ); + + ReporterConfig( ReporterConfig&& ) = default; + ReporterConfig& operator=( ReporterConfig&& ) = default; + ~ReporterConfig(); // = default + + Detail::unique_ptr takeStream() &&; + IConfig const* fullConfig() const; + ColourMode colourMode() const; + std::map const& customOptions() const; + + private: + Detail::unique_ptr m_stream; + IConfig const* m_fullConfig; + ColourMode m_colourMode; + std::map m_customOptions; + }; + + struct TestRunInfo { + constexpr TestRunInfo(StringRef _name) : name(_name) {} + StringRef name; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ); + + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = delete; + AssertionStats& operator = ( AssertionStats && ) = delete; + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ); + + TestCaseInfo const * testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + + struct BenchmarkInfo { + std::string name; + double estimatedDuration; + int iterations; + unsigned int samples; + unsigned int resamples; + double clockResolution; + double clockCost; + }; + + template + struct BenchmarkStats { + BenchmarkInfo info; + + std::vector samples; + Benchmark::Estimate mean; + Benchmark::Estimate standardDeviation; + Benchmark::OutlierClassification outliers; + double outlierVariance; + + template + operator BenchmarkStats() const { + std::vector samples2; + samples2.reserve(samples.size()); + for (auto const& sample : samples) { + samples2.push_back(Duration2(sample)); + } + return { + info, + CATCH_MOVE(samples2), + mean, + standardDeviation, + outliers, + outlierVariance, + }; + } + }; + + //! By setting up its preferences, a reporter can modify Catch2's behaviour + //! in some regards, e.g. it can request Catch2 to capture writes to + //! stdout/stderr during test execution, and pass them to the reporter. + struct ReporterPreferences { + //! Catch2 should redirect writes to stdout and pass them to the + //! reporter + bool shouldRedirectStdOut = false; + //! Catch2 should call `Reporter::assertionEnded` even for passing + //! assertions + bool shouldReportAllAssertions = false; + }; + + /** + * The common base for all reporters and event listeners + * + * Implementing classes must also implement: + * + * //! User-friendly description of the reporter/listener type + * static std::string getDescription() + * + * Generally shouldn't be derived from by users of Catch2 directly, + * instead they should derive from one of the utility bases that + * derive from this class. + */ + class IEventListener { + protected: + //! Derived classes can set up their preferences here + ReporterPreferences m_preferences; + //! The test run's config as filled in from CLI and defaults + IConfig const* m_config; + + public: + IEventListener( IConfig const* config ): m_config( config ) {} + + virtual ~IEventListener(); // = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + + ReporterPreferences const& getPreferences() const { + return m_preferences; + } + + //! Called when no test cases match provided test spec + virtual void noMatchingTestCases( StringRef unmatchedSpec ) = 0; + //! Called for all invalid test specs from the cli + virtual void reportInvalidTestSpec( StringRef invalidArgument ) = 0; + + /** + * Called once in a testing run before tests are started + * + * Not called if tests won't be run (e.g. only listing will happen) + */ + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + + //! Called _once_ for each TEST_CASE, no matter how many times it is entered + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections) + virtual void testCasePartialStarting( TestCaseInfo const& testInfo, uint64_t partNumber ) = 0; + //! Called when a `SECTION` is being entered. Not called for skipped sections + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + //! Called when user-code is being probed before the actual benchmark runs + virtual void benchmarkPreparing( StringRef benchmarkName ) = 0; + //! Called after probe but before the user-code is being benchmarked + virtual void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) = 0; + //! Called with the benchmark results if benchmark successfully finishes + virtual void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) = 0; + //! Called if running the benchmarks fails for any reason + virtual void benchmarkFailed( StringRef benchmarkName ) = 0; + + //! Called before assertion success/failure is evaluated + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + //! Called after assertion was fully evaluated + virtual void assertionEnded( AssertionStats const& assertionStats ) = 0; + + //! Called after a `SECTION` has finished running + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections) + virtual void testCasePartialEnded(TestCaseStats const& testCaseStats, uint64_t partNumber ) = 0; + //! Called _once_ for each TEST_CASE, no matter how many times it is entered + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + /** + * Called once after all tests in a testing run are finished + * + * Not called if tests weren't run (e.g. only listings happened) + */ + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + //! Called with test cases that are skipped due to the test run aborting + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + //! Called if a fatal error (signal/structured exception) occured + virtual void fatalErrorEncountered( StringRef error ) = 0; + + //! Writes out information about provided reporters using reporter-specific format + virtual void listReporters(std::vector const& descriptions) = 0; + //! Writes out the provided listeners descriptions using reporter-specific format + virtual void listListeners(std::vector const& descriptions) = 0; + //! Writes out information about provided tests using reporter-specific format + virtual void listTests(std::vector const& tests) = 0; + //! Writes out information about the provided tags using reporter-specific format + virtual void listTags(std::vector const& tags) = 0; + }; + using IEventListenerPtr = Detail::unique_ptr; + +} // end namespace Catch + +#endif // CATCH_INTERFACES_REPORTER_HPP_INCLUDED + + +#ifndef CATCH_UNIQUE_NAME_HPP_INCLUDED +#define CATCH_UNIQUE_NAME_HPP_INCLUDED + + + + +/** \file + * Wrapper for the CONFIG configuration option + * + * When generating internal unique names, there are two options. Either + * we mix in the current line number, or mix in an incrementing number. + * We prefer the latter, using `__COUNTER__`, but users might want to + * use the former. + */ + +#ifndef CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define CATCH_CONFIG_COUNTER_HPP_INCLUDED + +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +#if defined( CATCH_INTERNAL_CONFIG_COUNTER ) && \ + !defined( CATCH_CONFIG_NO_COUNTER ) && \ + !defined( CATCH_CONFIG_COUNTER ) +# define CATCH_CONFIG_COUNTER +#endif + + +#endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#endif // CATCH_UNIQUE_NAME_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_CHRONOMETER_HPP_INCLUDED +#define CATCH_CHRONOMETER_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_CLOCK_HPP_INCLUDED +#define CATCH_CLOCK_HPP_INCLUDED + +#include +#include + +namespace Catch { + namespace Benchmark { + template + using ClockDuration = typename Clock::duration; + template + using FloatDuration = std::chrono::duration; + + template + using TimePoint = typename Clock::time_point; + + using default_clock = std::chrono::steady_clock; + + template + struct now { + TimePoint operator()() const { + return Clock::now(); + } + }; + + using fp_seconds = std::chrono::duration>; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CLOCK_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_OPTIMIZER_HPP_INCLUDED +#define CATCH_OPTIMIZER_HPP_INCLUDED + +#if defined(_MSC_VER) +# include // atomic_thread_fence +#endif + + +#include + +namespace Catch { + namespace Benchmark { +#if defined(__GNUC__) || defined(__clang__) + template + inline void keep_memory(T* p) { + asm volatile("" : : "g"(p) : "memory"); + } + inline void keep_memory() { + asm volatile("" : : : "memory"); + } + + namespace Detail { + inline void optimizer_barrier() { keep_memory(); } + } // namespace Detail +#elif defined(_MSC_VER) + +#pragma optimize("", off) + template + inline void keep_memory(T* p) { + // thanks @milleniumbug + *reinterpret_cast(p) = *reinterpret_cast(p); + } + // TODO equivalent keep_memory() +#pragma optimize("", on) + + namespace Detail { + inline void optimizer_barrier() { + std::atomic_thread_fence(std::memory_order_seq_cst); + } + } // namespace Detail + +#endif + + template + inline void deoptimize_value(T&& x) { + keep_memory(&x); + } + + template + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t::value> { + deoptimize_value(CATCH_FORWARD(fn) (CATCH_FORWARD(args)...)); + } + + template + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t::value> { + CATCH_FORWARD(fn) (CATCH_FORWARD(args)...); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OPTIMIZER_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_COMPLETE_INVOKE_HPP_INCLUDED +#define CATCH_COMPLETE_INVOKE_HPP_INCLUDED + + + +#ifndef CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED +#define CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + +namespace Catch { + + //! Used to signal that an assertion macro failed + struct TestFailureException{}; + +} // namespace Catch + +#endif // CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + + +#ifndef CATCH_META_HPP_INCLUDED +#define CATCH_META_HPP_INCLUDED + +#include + +namespace Catch { + template + struct always_false : std::false_type {}; + + template struct true_given : std::true_type {}; + struct is_callable_tester { + template + static true_given()(std::declval()...))> test(int); + template + static std::false_type test(...); + }; + + template + struct is_callable; + + template + struct is_callable : decltype(is_callable_tester::test(0)) {}; + + +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 + // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is + // replaced with std::invoke_result here. + template + using FunctionReturnType = std::remove_reference_t>>; +#else + template + using FunctionReturnType = std::remove_reference_t>>; +#endif + +} // namespace Catch + +namespace mpl_{ + struct na; +} + +#endif // CATCH_META_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED +#define CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + + +#include + +namespace Catch { + + class TestCaseHandle; + struct TestCaseInfo; + class ITestCaseRegistry; + class IExceptionTranslatorRegistry; + class IExceptionTranslator; + class IReporterRegistry; + class IReporterFactory; + class ITagAliasRegistry; + class ITestInvoker; + class IMutableEnumValuesRegistry; + struct SourceLineInfo; + + class StartupExceptionRegistry; + class EventListenerFactory; + + using IReporterFactoryPtr = Detail::unique_ptr; + + class IRegistryHub { + public: + virtual ~IRegistryHub(); // = default + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; + + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + class IMutableRegistryHub { + public: + virtual ~IMutableRegistryHub(); // = default + virtual void registerReporter( std::string const& name, IReporterFactoryPtr factory ) = 0; + virtual void registerListener( Detail::unique_ptr factory ) = 0; + virtual void registerTest(Detail::unique_ptr&& testInfo, Detail::unique_ptr&& invoker) = 0; + virtual void registerTranslator( Detail::unique_ptr&& translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; + }; + + IRegistryHub const& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +#endif // CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct CompleteType { using type = T; }; + template <> + struct CompleteType { struct type {}; }; + + template + using CompleteType_t = typename CompleteType::type; + + template + struct CompleteInvoker { + template + static Result invoke(Fun&& fun, Args&&... args) { + return CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + } + }; + template <> + struct CompleteInvoker { + template + static CompleteType_t invoke(Fun&& fun, Args&&... args) { + CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + return {}; + } + }; + + // invoke and not return void :( + template + CompleteType_t> complete_invoke(Fun&& fun, Args&&... args) { + return CompleteInvoker>::invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); + } + + } // namespace Detail + + template + Detail::CompleteType_t> user_code(Fun&& fun) { + return Detail::complete_invoke(CATCH_FORWARD(fun)); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_COMPLETE_INVOKE_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct ChronometerConcept { + virtual void start() = 0; + virtual void finish() = 0; + virtual ~ChronometerConcept(); // = default; + + ChronometerConcept() = default; + ChronometerConcept(ChronometerConcept const&) = default; + ChronometerConcept& operator=(ChronometerConcept const&) = default; + }; + template + struct ChronometerModel final : public ChronometerConcept { + void start() override { started = Clock::now(); } + void finish() override { finished = Clock::now(); } + + ClockDuration elapsed() const { return finished - started; } + + TimePoint started; + TimePoint finished; + }; + } // namespace Detail + + struct Chronometer { + public: + template + void measure(Fun&& fun) { measure(CATCH_FORWARD(fun), is_callable()); } + + int runs() const { return repeats; } + + Chronometer(Detail::ChronometerConcept& meter, int repeats_) + : impl(&meter) + , repeats(repeats_) {} + + private: + template + void measure(Fun&& fun, std::false_type) { + measure([&fun](int) { return fun(); }, std::true_type()); + } + + template + void measure(Fun&& fun, std::true_type) { + Detail::optimizer_barrier(); + impl->start(); + for (int i = 0; i < repeats; ++i) invoke_deoptimized(fun, i); + impl->finish(); + Detail::optimizer_barrier(); + } + + Detail::ChronometerConcept* impl; + int repeats; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CHRONOMETER_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ENVIRONMENT_HPP_INCLUDED +#define CATCH_ENVIRONMENT_HPP_INCLUDED + + +namespace Catch { + namespace Benchmark { + template + struct EnvironmentEstimate { + Duration mean; + OutlierClassification outliers; + + template + operator EnvironmentEstimate() const { + return { mean, outliers }; + } + }; + template + struct Environment { + using clock_type = Clock; + EnvironmentEstimate> clock_resolution; + EnvironmentEstimate> clock_cost; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ENVIRONMENT_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_EXECUTION_PLAN_HPP_INCLUDED +#define CATCH_EXECUTION_PLAN_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED +#define CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct is_related + : std::is_same, std::decay_t> {}; + + /// We need to reinvent std::function because every piece of code that might add overhead + /// in a measurement context needs to have consistent performance characteristics so that we + /// can account for it in the measurement. + /// Implementations of std::function with optimizations that aren't always applicable, like + /// small buffer optimizations, are not uncommon. + /// This is effectively an implementation of std::function without any such optimizations; + /// it may be slow, but it is consistently slow. + struct BenchmarkFunction { + private: + struct callable { + virtual void call(Chronometer meter) const = 0; + virtual Catch::Detail::unique_ptr clone() const = 0; + virtual ~callable(); // = default; + + callable() = default; + callable(callable const&) = default; + callable& operator=(callable const&) = default; + }; + template + struct model : public callable { + model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {} + model(Fun const& fun_) : fun(fun_) {} + + Catch::Detail::unique_ptr clone() const override { + return Catch::Detail::make_unique>( *this ); + } + + void call(Chronometer meter) const override { + call(meter, is_callable()); + } + void call(Chronometer meter, std::true_type) const { + fun(meter); + } + void call(Chronometer meter, std::false_type) const { + meter.measure(fun); + } + + Fun fun; + }; + + struct do_nothing { void operator()() const {} }; + + template + BenchmarkFunction(model* c) : f(c) {} + + public: + BenchmarkFunction() + : f(new model{ {} }) {} + + template ::value, int> = 0> + BenchmarkFunction(Fun&& fun) + : f(new model>(CATCH_FORWARD(fun))) {} + + BenchmarkFunction( BenchmarkFunction&& that ) noexcept: + f( CATCH_MOVE( that.f ) ) {} + + BenchmarkFunction(BenchmarkFunction const& that) + : f(that.f->clone()) {} + + BenchmarkFunction& + operator=( BenchmarkFunction&& that ) noexcept { + f = CATCH_MOVE( that.f ); + return *this; + } + + BenchmarkFunction& operator=(BenchmarkFunction const& that) { + f = that.f->clone(); + return *this; + } + + void operator()(Chronometer meter) const { f->call(meter); } + + private: + Catch::Detail::unique_ptr f; + }; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_REPEAT_HPP_INCLUDED +#define CATCH_REPEAT_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct repeater { + void operator()(int k) const { + for (int i = 0; i < k; ++i) { + fun(); + } + } + Fun fun; + }; + template + repeater> repeat(Fun&& fun) { + return { CATCH_FORWARD(fun) }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_REPEAT_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED +#define CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_MEASURE_HPP_INCLUDED +#define CATCH_MEASURE_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_TIMING_HPP_INCLUDED +#define CATCH_TIMING_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + template + struct Timing { + Duration elapsed; + Result result; + int iterations; + }; + template + using TimingOf = Timing, Detail::CompleteType_t>>; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_TIMING_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + TimingOf measure(Fun&& fun, Args&&... args) { + auto start = Clock::now(); + auto&& r = Detail::complete_invoke(fun, CATCH_FORWARD(args)...); + auto end = Clock::now(); + auto delta = end - start; + return { delta, CATCH_FORWARD(r), 1 }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_MEASURE_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + TimingOf measure_one(Fun&& fun, int iters, std::false_type) { + return Detail::measure(fun, iters); + } + template + TimingOf measure_one(Fun&& fun, int iters, std::true_type) { + Detail::ChronometerModel meter; + auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters)); + + return { meter.elapsed(), CATCH_MOVE(result), iters }; + } + + template + using run_for_at_least_argument_t = std::conditional_t::value, Chronometer, int>; + + + [[noreturn]] + void throw_optimized_away_error(); + + template + TimingOf> + run_for_at_least(ClockDuration how_long, + const int initial_iterations, + Fun&& fun) { + auto iters = initial_iterations; + while (iters < (1 << 30)) { + auto&& Timing = measure_one(fun, iters, is_callable()); + + if (Timing.elapsed >= how_long) { + return { Timing.elapsed, CATCH_MOVE(Timing.result), iters }; + } + iters *= 2; + } + throw_optimized_away_error(); + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + +#include +#include + +namespace Catch { + namespace Benchmark { + template + struct ExecutionPlan { + int iterations_per_sample; + Duration estimated_duration; + Detail::BenchmarkFunction benchmark; + Duration warmup_time; + int warmup_iterations; + + template + operator ExecutionPlan() const { + return { iterations_per_sample, estimated_duration, benchmark, warmup_time, warmup_iterations }; + } + + template + std::vector> run(const IConfig &cfg, Environment> env) const { + // warmup a bit + Detail::run_for_at_least(std::chrono::duration_cast>(warmup_time), warmup_iterations, Detail::repeat(now{})); + + std::vector> times; + times.reserve(cfg.benchmarkSamples()); + std::generate_n(std::back_inserter(times), cfg.benchmarkSamples(), [this, env] { + Detail::ChronometerModel model; + this->benchmark(Chronometer(model, iterations_per_sample)); + auto sample_time = model.elapsed() - env.clock_cost.mean; + if (sample_time < FloatDuration::zero()) sample_time = FloatDuration::zero(); + return sample_time / iterations_per_sample; + }); + return times; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_EXECUTION_PLAN_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_CLOCK_HPP_INCLUDED +#define CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_STATS_HPP_INCLUDED +#define CATCH_STATS_HPP_INCLUDED + + +#include +#include +#include +#include +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + using sample = std::vector; + + // Used when we know we want == comparison of two doubles + // to centralize warning suppression + bool directCompare( double lhs, double rhs ); + + double weighted_average_quantile(int k, int q, std::vector::iterator first, std::vector::iterator last); + + template + OutlierClassification classify_outliers(Iterator first, Iterator last) { + std::vector copy(first, last); + + auto q1 = weighted_average_quantile(1, 4, copy.begin(), copy.end()); + auto q3 = weighted_average_quantile(3, 4, copy.begin(), copy.end()); + auto iqr = q3 - q1; + auto los = q1 - (iqr * 3.); + auto lom = q1 - (iqr * 1.5); + auto him = q3 + (iqr * 1.5); + auto his = q3 + (iqr * 3.); + + OutlierClassification o; + for (; first != last; ++first) { + auto&& t = *first; + if (t < los) ++o.low_severe; + else if (t < lom) ++o.low_mild; + else if (t > his) ++o.high_severe; + else if (t > him) ++o.high_mild; + ++o.samples_seen; + } + return o; + } + + template + double mean(Iterator first, Iterator last) { + auto count = last - first; + double sum = std::accumulate(first, last, 0.); + return sum / static_cast(count); + } + + template + sample jackknife(Estimator&& estimator, Iterator first, Iterator last) { + auto n = static_cast(last - first); + auto second = first; + ++second; + sample results; + results.reserve(n); + + for (auto it = first; it != last; ++it) { + std::iter_swap(it, first); + results.push_back(estimator(second, last)); + } + + return results; + } + + inline double normal_cdf(double x) { + return std::erfc(-x / std::sqrt(2.0)) / 2.0; + } + + double erfc_inv(double x); + + double normal_quantile(double p); + + template + Estimate bootstrap(double confidence_level, Iterator first, Iterator last, sample const& resample, Estimator&& estimator) { + auto n_samples = last - first; + + double point = estimator(first, last); + // Degenerate case with a single sample + if (n_samples == 1) return { point, point, point, confidence_level }; + + sample jack = jackknife(estimator, first, last); + double jack_mean = mean(jack.begin(), jack.end()); + double sum_squares, sum_cubes; + std::tie(sum_squares, sum_cubes) = std::accumulate(jack.begin(), jack.end(), std::make_pair(0., 0.), [jack_mean](std::pair sqcb, double x) -> std::pair { + auto d = jack_mean - x; + auto d2 = d * d; + auto d3 = d2 * d; + return { sqcb.first + d2, sqcb.second + d3 }; + }); + + double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5)); + long n = static_cast(resample.size()); + double prob_n = std::count_if(resample.begin(), resample.end(), [point](double x) { return x < point; }) / static_cast(n); + // degenerate case with uniform samples + if ( directCompare( prob_n, 0. ) ) { + return { point, point, point, confidence_level }; + } + + double bias = normal_quantile(prob_n); + double z1 = normal_quantile((1. - confidence_level) / 2.); + + auto cumn = [n]( double x ) -> long { + return std::lround( normal_cdf( x ) * static_cast(n) ); + }; + auto a = [bias, accel](double b) { return bias + b / (1. - accel * b); }; + double b1 = bias + z1; + double b2 = bias - z1; + double a1 = a(b1); + double a2 = a(b2); + auto lo = static_cast((std::max)(cumn(a1), 0l)); + auto hi = static_cast((std::min)(cumn(a2), n - 1)); + + return { point, resample[lo], resample[hi], confidence_level }; + } + + double outlier_variance(Estimate mean, Estimate stddev, int n); + + struct bootstrap_analysis { + Estimate mean; + Estimate standard_deviation; + double outlier_variance; + }; + + bootstrap_analysis analyse_samples(double confidence_level, unsigned int n_resamples, std::vector::iterator first, std::vector::iterator last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_STATS_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + std::vector resolution(int k) { + std::vector> times; + times.reserve(static_cast(k + 1)); + std::generate_n(std::back_inserter(times), k + 1, now{}); + + std::vector deltas; + deltas.reserve(static_cast(k)); + std::transform(std::next(times.begin()), times.end(), times.begin(), + std::back_inserter(deltas), + [](TimePoint a, TimePoint b) { return static_cast((a - b).count()); }); + + return deltas; + } + + const auto warmup_iterations = 10000; + const auto warmup_time = std::chrono::milliseconds(100); + const auto minimum_ticks = 1000; + const auto warmup_seed = 10000; + const auto clock_resolution_estimation_time = std::chrono::milliseconds(500); + const auto clock_cost_estimation_time_limit = std::chrono::seconds(1); + const auto clock_cost_estimation_tick_limit = 100000; + const auto clock_cost_estimation_time = std::chrono::milliseconds(10); + const auto clock_cost_estimation_iterations = 10000; + + template + int warmup() { + return run_for_at_least(std::chrono::duration_cast>(warmup_time), warmup_seed, &resolution) + .iterations; + } + template + EnvironmentEstimate> estimate_clock_resolution(int iterations) { + auto r = run_for_at_least(std::chrono::duration_cast>(clock_resolution_estimation_time), iterations, &resolution) + .result; + return { + FloatDuration(mean(r.begin(), r.end())), + classify_outliers(r.begin(), r.end()), + }; + } + template + EnvironmentEstimate> estimate_clock_cost(FloatDuration resolution) { + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FloatDuration(clock_cost_estimation_time_limit)); + auto time_clock = [](int k) { + return Detail::measure([k] { + for (int i = 0; i < k; ++i) { + volatile auto ignored = Clock::now(); + (void)ignored; + } + }).elapsed; + }; + time_clock(1); + int iters = clock_cost_estimation_iterations; + auto&& r = run_for_at_least(std::chrono::duration_cast>(clock_cost_estimation_time), iters, time_clock); + std::vector times; + int nsamples = static_cast(std::ceil(time_limit / r.elapsed)); + times.reserve(static_cast(nsamples)); + std::generate_n(std::back_inserter(times), nsamples, [time_clock, &r] { + return static_cast((time_clock(r.iterations) / r.iterations).count()); + }); + return { + FloatDuration(mean(times.begin(), times.end())), + classify_outliers(times.begin(), times.end()), + }; + } + + template + Environment> measure_environment() { +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + static Catch::Detail::unique_ptr>> env; +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + if (env) { + return *env; + } + + auto iters = Detail::warmup(); + auto resolution = Detail::estimate_clock_resolution(iters); + auto cost = Detail::estimate_clock_cost(resolution.mean); + + env = Catch::Detail::make_unique>>( Environment>{resolution, cost} ); + return *env; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ANALYSE_HPP_INCLUDED +#define CATCH_ANALYSE_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED +#define CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + + +#include +#include +#include + +namespace Catch { + namespace Benchmark { + template + struct SampleAnalysis { + std::vector samples; + Estimate mean; + Estimate standard_deviation; + OutlierClassification outliers; + double outlier_variance; + + template + operator SampleAnalysis() const { + std::vector samples2; + samples2.reserve(samples.size()); + std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); }); + return { + CATCH_MOVE(samples2), + mean, + standard_deviation, + outliers, + outlier_variance, + }; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + SampleAnalysis analyse(const IConfig &cfg, Environment, Iterator first, Iterator last) { + if (!cfg.benchmarkNoAnalysis()) { + std::vector samples; + samples.reserve(static_cast(last - first)); + std::transform(first, last, std::back_inserter(samples), [](Duration d) { return d.count(); }); + + auto analysis = Catch::Benchmark::Detail::analyse_samples(cfg.benchmarkConfidenceInterval(), cfg.benchmarkResamples(), samples.begin(), samples.end()); + auto outliers = Catch::Benchmark::Detail::classify_outliers(samples.begin(), samples.end()); + + auto wrap_estimate = [](Estimate e) { + return Estimate { + Duration(e.point), + Duration(e.lower_bound), + Duration(e.upper_bound), + e.confidence_interval, + }; + }; + std::vector samples2; + samples2.reserve(samples.size()); + std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](double d) { return Duration(d); }); + return { + CATCH_MOVE(samples2), + wrap_estimate(analysis.mean), + wrap_estimate(analysis.standard_deviation), + outliers, + analysis.outlier_variance, + }; + } else { + std::vector samples; + samples.reserve(static_cast(last - first)); + + Duration mean = Duration(0); + int i = 0; + for (auto it = first; it < last; ++it, ++i) { + samples.push_back(Duration(*it)); + mean += Duration(*it); + } + mean /= i; + + return { + CATCH_MOVE(samples), + Estimate{mean, mean, mean, 0.0}, + Estimate{Duration(0), Duration(0), Duration(0), 0.0}, + OutlierClassification{}, + 0.0 + }; + } + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ANALYSE_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + namespace Benchmark { + struct Benchmark { + Benchmark(std::string&& benchmarkName) + : name(CATCH_MOVE(benchmarkName)) {} + + template + Benchmark(std::string&& benchmarkName , FUN &&func) + : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} + + template + ExecutionPlan> prepare(const IConfig &cfg, Environment> env) const { + auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = std::max(min_time, std::chrono::duration_cast(cfg.benchmarkWarmupTime())); + auto&& test = Detail::run_for_at_least(std::chrono::duration_cast>(run_time), 1, fun); + int new_iters = static_cast(std::ceil(min_time * test.iterations / test.elapsed)); + return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; + } + + template + void run() { + auto const* cfg = getCurrentContext().getConfig(); + + auto env = Detail::measure_environment(); + + getResultCapture().benchmarkPreparing(name); + CATCH_TRY{ + auto plan = user_code([&] { + return prepare(*cfg, env); + }); + + BenchmarkInfo info { + name, + plan.estimated_duration.count(), + plan.iterations_per_sample, + cfg->benchmarkSamples(), + cfg->benchmarkResamples(), + env.clock_resolution.mean.count(), + env.clock_cost.mean.count() + }; + + getResultCapture().benchmarkStarting(info); + + auto samples = user_code([&] { + return plan.template run(*cfg, env); + }); + + auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end()); + BenchmarkStats> stats{ info, analysis.samples, analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; + getResultCapture().benchmarkEnded(stats); + } CATCH_CATCH_ANON (TestFailureException) { + getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); + } CATCH_CATCH_ALL{ + getResultCapture().benchmarkFailed(translateActiveException()); + // We let the exception go further up so that the + // test case is marked as failed. + std::rethrow_exception(std::current_exception()); + } + } + + // sets lambda to be used in fun *and* executes benchmark! + template ::value, int> = 0> + Benchmark & operator=(Fun func) { + auto const* cfg = getCurrentContext().getConfig(); + if (!cfg->skipBenchmarks()) { + fun = Detail::BenchmarkFunction(func); + run(); + } + return *this; + } + + explicit operator bool() { + return true; + } + + private: + Detail::BenchmarkFunction fun; + std::string name; + }; + } +} // namespace Catch + +#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 +#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 + +#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&](int benchmarkIndex) + +#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&] + +#if defined(CATCH_CONFIG_PREFIX_ALL) + +#define CATCH_BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define CATCH_BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#else + +#define BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#endif + +#endif // CATCH_BENCHMARK_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_CONSTRUCTOR_HPP_INCLUDED +#define CATCH_CONSTRUCTOR_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct ObjectStorage + { + ObjectStorage() = default; + + ObjectStorage(const ObjectStorage& other) + { + new(&data) T(other.stored_object()); + } + + ObjectStorage(ObjectStorage&& other) + { + new(data) T(CATCH_MOVE(other.stored_object())); + } + + ~ObjectStorage() { destruct_on_exit(); } + + template + void construct(Args&&... args) + { + new (data) T(CATCH_FORWARD(args)...); + } + + template + std::enable_if_t destruct() + { + stored_object().~T(); + } + + private: + // If this is a constructor benchmark, destruct the underlying object + template + void destruct_on_exit(std::enable_if_t* = nullptr) { destruct(); } + // Otherwise, don't + template + void destruct_on_exit(std::enable_if_t* = nullptr) { } + +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + T& stored_object() { return *reinterpret_cast( data ); } + + T const& stored_object() const { + return *reinterpret_cast( data ); + } +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic pop +#endif + + alignas( T ) unsigned char data[sizeof( T )]{}; + }; + } // namespace Detail + + template + using storage_for = Detail::ObjectStorage; + + template + using destructable_object = Detail::ObjectStorage; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CONSTRUCTOR_HPP_INCLUDED + +#endif // CATCH_BENCHMARK_ALL_HPP_INCLUDED + + +#ifndef CATCH_APPROX_HPP_INCLUDED +#define CATCH_APPROX_HPP_INCLUDED + + + +#ifndef CATCH_TOSTRING_HPP_INCLUDED +#define CATCH_TOSTRING_HPP_INCLUDED + + +#include +#include +#include +#include + + + + +/** \file + * Wrapper for the WCHAR configuration option + * + * We want to support platforms that do not provide `wchar_t`, so we + * sometimes have to disable providing wchar_t overloads through Catch2, + * e.g. the StringMaker specialization for `std::wstring`. + */ + +#ifndef CATCH_CONFIG_WCHAR_HPP_INCLUDED +#define CATCH_CONFIG_WCHAR_HPP_INCLUDED + +// We assume that WCHAR should be enabled by default, and only disabled +// for a shortlist (so far only DJGPP) of compilers. + +#if defined(__DJGPP__) +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +#if !defined( CATCH_INTERNAL_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_WCHAR ) +# define CATCH_CONFIG_WCHAR +#endif + +#endif // CATCH_CONFIG_WCHAR_HPP_INCLUDED + + +#ifndef CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED +#define CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#include +#include +#include +#include + +namespace Catch { + + class ReusableStringStream : Detail::NonCopyable { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + //! Returns the serialized state + std::string str() const; + //! Sets internal state to `str` + void str(std::string const& str); + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// Old versions of GCC do not understand -Wnonnull-compare +#pragma GCC diagnostic ignored "-Wpragmas" +// Streaming a function pointer triggers Waddress and Wnonnull-compare +// on GCC, because it implicitly converts it to bool and then decides +// that the check it uses (a? true : false) is tautological and cannot +// be null... +#pragma GCC diagnostic ignored "-Waddress" +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + + template + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + auto get() -> std::ostream& { return *m_oss; } + }; +} + +#endif // CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#ifndef CATCH_VOID_TYPE_HPP_INCLUDED +#define CATCH_VOID_TYPE_HPP_INCLUDED + + +namespace Catch { + namespace Detail { + + template + struct make_void { using type = void; }; + + template + using void_t = typename make_void::type; + + } // namespace Detail +} // namespace Catch + + +#endif // CATCH_VOID_TYPE_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED +#define CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + + +#include + +namespace Catch { + + namespace Detail { + struct EnumInfo { + StringRef m_name; + std::vector> m_values; + + ~EnumInfo(); + + StringRef lookup( int value ) const; + }; + } // namespace Detail + + class IMutableEnumValuesRegistry { + public: + virtual ~IMutableEnumValuesRegistry(); // = default; + + virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values ) = 0; + + template + Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list values ) { + static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); + std::vector intValues; + intValues.reserve( values.size() ); + for( auto enumValue : values ) + intValues.push_back( static_cast( enumValue ) ); + return registerEnum( enumName, allEnums, intValues ); + } + }; + +} // Catch + +#endif // CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy{}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + // Bring in global namespace operator<< for ADL lookup in + // `IsStreamInsertable` below. + using ::operator<<; + + namespace Detail { + + inline std::size_t catch_strnlen(const char *str, std::size_t n) { + auto ret = std::char_traits::find(str, n, '\0'); + if (ret != nullptr) { + return static_cast(ret - str); + } + return n; + } + + constexpr StringRef unprintableString = "{?}"_sr; + + //! Encases `string in quotes, and optionally escapes invisibles + std::string convertIntoString( StringRef string, bool escapeInvisibles ); + + //! Encases `string` in quotes, and escapes invisibles if user requested + //! it via CLI + std::string convertIntoString( StringRef string ); + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype(std::declval() << std::declval(), std::true_type()); + + template + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; + + template + std::string convertUnknownEnumToString( E e ); + + template + std::enable_if_t< + !std::is_enum::value && !std::is_base_of::value, + std::string> convertUnstreamable( T const& ) { + return std::string(Detail::unprintableString); + } + template + std::enable_if_t< + !std::is_enum::value && std::is_base_of::value, + std::string> convertUnstreamable(T const& ex) { + return ex.what(); + } + + + template + std::enable_if_t< + std::is_enum::value, + std::string> convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr p = &bytes[0]; + return std::string(reinterpret_cast(p), bytes->Length); + } +#endif + + } // namespace Detail + + + // If we decide for C++14, change these to enable_if_ts + template + struct StringMaker { + template + static + std::enable_if_t<::Catch::Detail::IsStreamInsertable::value, std::string> + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template + static + std::enable_if_t::value, std::string> + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template + std::string stringify(const T& e) { + return ::Catch::StringMaker>>::convert(e); + } + + template + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast>(e)); + } + +#if defined(_MANAGED) + template + std::string stringify( T^ e ) { + return ::Catch::StringMaker::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker { + static std::string convert(const std::string& str); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::string_view str); + }; +#endif + + template<> + struct StringMaker { + static std::string convert(char const * str); + }; + template<> + struct StringMaker { + static std::string convert(char * str); + }; + +#if defined(CATCH_CONFIG_WCHAR) + template<> + struct StringMaker { + static std::string convert(const std::wstring& wstr); + }; + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::wstring_view str); + }; +# endif + + template<> + struct StringMaker { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker { + static std::string convert(wchar_t * str); + }; +#endif // CATCH_CONFIG_WCHAR + + template + struct StringMaker { + static std::string convert(char const* str) { + return Detail::convertIntoString( + StringRef( str, Detail::catch_strnlen( str, SZ ) ) ); + } + }; + template + struct StringMaker { + static std::string convert(signed char const* str) { + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + template + struct StringMaker { + static std::string convert(unsigned char const* str) { + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + +#if defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker { + static std::string convert(std::byte value); + }; +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker { + static std::string convert(int value); + }; + template<> + struct StringMaker { + static std::string convert(long value); + }; + template<> + struct StringMaker { + static std::string convert(long long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker { + static std::string convert(bool b) { + using namespace std::string_literals; + return b ? "true"s : "false"s; + } + }; + + template<> + struct StringMaker { + static std::string convert(char c); + }; + template<> + struct StringMaker { + static std::string convert(signed char c); + }; + template<> + struct StringMaker { + static std::string convert(unsigned char c); + }; + + template<> + struct StringMaker { + static std::string convert(std::nullptr_t) { + using namespace std::string_literals; + return "nullptr"s; + } + }; + + template<> + struct StringMaker { + static std::string convert(float value); + CATCH_EXPORT static int precision; + }; + + template<> + struct StringMaker { + static std::string convert(double value); + CATCH_EXPORT static int precision; + }; + + template + struct StringMaker { + template + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template + struct StringMaker { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template + struct StringMaker { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template + std::string rangeToString(InputIterator first, Sentinel last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in their headers + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +# define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::pair& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::optional& optional) { + if (optional.has_value()) { + return ::Catch::Detail::stringify(*optional); + } else { + return "{ }"; + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get(tuple)); + TupleElementPrinter::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + + template + struct StringMaker> { + static std::string convert(const std::tuple& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include +namespace Catch { + template<> + struct StringMaker { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template + struct StringMaker> { + static std::string convert(const std::variant& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + +namespace Catch { + // Import begin/ end from std here + using std::begin; + using std::end; + + namespace Detail { + template + struct is_range_impl : std::false_type {}; + + template + struct is_range_impl()))>> : std::true_type {}; + } // namespace Detail + + template + struct is_range : Detail::is_range_impl {}; + +#if defined(_MANAGED) // Managed types are never ranges + template + struct is_range { + static const bool value = false; + }; +#endif + + template + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector specially + template + std::string rangeToString( std::vector const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template + struct StringMaker { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + + +} // namespace Catch + +// Separate std::chrono::duration specialization +#include +#include +#include + + +namespace Catch { + +template +struct ratio_string { + static std::string symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); + } +}; + +template <> +struct ratio_string { + static char symbol() { return 'a'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'f'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'p'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'n'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'u'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'm'; } +}; + + //////////// + // std::chrono::duration specializations + template + struct StringMaker> { + static std::string convert(std::chrono::duration const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string::symbol() << 's'; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point specialization + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp, timeStampSize - 1); + } + }; +} + + +#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ +namespace Catch { \ + template<> struct StringMaker { \ + static std::string convert( enumName value ) { \ + static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ + return static_cast(enumInfo.lookup( static_cast( value ) )); \ + } \ + }; \ +} + +#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // CATCH_TOSTRING_HPP_INCLUDED + +#include + +namespace Catch { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Sets and validates the new margin (margin >= 0) + void setMargin(double margin); + // Sets and validates the new epsilon (0 < epsilon < 1) + void setEpsilon(double epsilon); + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template ::value>> + Approx operator()( T const& value ) const { + Approx approx( static_cast(value) ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template ::value>> + explicit Approx( T const& value ): Approx(static_cast(value)) + {} + + + template ::value>> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template ::value>> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast(rhs) || lhs == rhs; + } + + template ::value>> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast(rhs) || lhs == rhs; + } + + template ::value>> + Approx& epsilon( T const& newEpsilon ) { + const auto epsilonAsDouble = static_cast(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template ::value>> + Approx& margin( T const& newMargin ) { + const auto marginAsDouble = static_cast(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template ::value>> + Approx& scale( T const& newScale ) { + m_scale = static_cast(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; + +namespace literals { + Approx operator ""_a(long double val); + Approx operator ""_a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker { + static std::string convert(Catch::Approx const& value); +}; + +} // end namespace Catch + +#endif // CATCH_APPROX_HPP_INCLUDED + + +#ifndef CATCH_CONFIG_HPP_INCLUDED +#define CATCH_CONFIG_HPP_INCLUDED + + + +#ifndef CATCH_TEST_SPEC_HPP_INCLUDED +#define CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + + + +#ifndef CATCH_WILDCARD_PATTERN_HPP_INCLUDED +#define CATCH_WILDCARD_PATTERN_HPP_INCLUDED + + + +#ifndef CATCH_CASE_SENSITIVE_HPP_INCLUDED +#define CATCH_CASE_SENSITIVE_HPP_INCLUDED + +namespace Catch { + + enum class CaseSensitive { Yes, No }; + +} // namespace Catch + +#endif // CATCH_CASE_SENSITIVE_HPP_INCLUDED + +#include + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive caseSensitivity ); + bool matches( std::string const& str ) const; + + private: + std::string normaliseString( std::string const& str ) const; + CaseSensitive m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +#endif // CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include +#include + +namespace Catch { + + class IConfig; + struct TestCaseInfo; + class TestCaseHandle; + + class TestSpec { + + class Pattern { + public: + explicit Pattern( std::string const& name ); + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + std::string const& name() const; + private: + std::string const m_name; + }; + + class NamePattern : public Pattern { + public: + explicit NamePattern( std::string const& name, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + explicit TagPattern( std::string const& tag, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; + private: + std::string m_tag; + }; + + struct Filter { + std::vector> m_required; + std::vector> m_forbidden; + + bool matches( TestCaseInfo const& testCase ) const; + std::string name() const; + }; + + public: + struct FilterMatch { + std::string name; + std::vector tests; + }; + using Matches = std::vector; + using vectorStrings = std::vector; + + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + Matches matchesByFilter( std::vector const& testCases, IConfig const& config ) const; + const vectorStrings & getInvalidSpecs() const; + + private: + std::vector m_filters; + std::vector m_invalidSpecs; + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_TEST_SPEC_HPP_INCLUDED + + +#ifndef CATCH_OPTIONAL_HPP_INCLUDED +#define CATCH_OPTIONAL_HPP_INCLUDED + +#include + +namespace Catch { + + // An optional type + template + class Optional { + public: + Optional() : nullableValue( nullptr ) {} + Optional( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Optional( Optional const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} + + ~Optional() { + reset(); + } + + Optional& operator= ( Optional const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Optional& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; + } + + T& operator*() { + assert(nullableValue); + return *nullableValue; + } + T const& operator*() const { + assert(nullableValue); + return *nullableValue; + } + T* operator->() { + assert(nullableValue); + return nullableValue; + } + const T* operator->() const { + assert(nullableValue); + return nullableValue; + } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + friend bool operator==(Optional const& a, Optional const& b) { + if (a.none() && b.none()) { + return true; + } else if (a.some() && b.some()) { + return *a == *b; + } else { + return false; + } + } + friend bool operator!=(Optional const& a, Optional const& b) { + return !( a == b ); + } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +#endif // CATCH_OPTIONAL_HPP_INCLUDED + + +#ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED +#define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + +#include + +namespace Catch { + + enum class GenerateFrom { + Time, + RandomDevice, + //! Currently equivalent to RandomDevice, but can change at any point + Default + }; + + std::uint32_t generateRandomSeed(GenerateFrom from); + +} // end namespace Catch + +#endif // CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED +#define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + + + +#ifndef CATCH_CONSOLE_COLOUR_HPP_INCLUDED +#define CATCH_CONSOLE_COLOUR_HPP_INCLUDED + + +#include +#include + +namespace Catch { + + enum class ColourMode : std::uint8_t; + class IStream; + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + }; + + class ColourImpl { + protected: + //! The associated stream of this ColourImpl instance + IStream* m_stream; + public: + ColourImpl( IStream* stream ): m_stream( stream ) {} + + //! RAII wrapper around writing specific colour of text using specific + //! colour impl into a stream. + class ColourGuard { + ColourImpl const* m_colourImpl; + Colour::Code m_code; + bool m_engaged = false; + + public: + //! Does **not** engage the guard/start the colour + ColourGuard( Colour::Code code, + ColourImpl const* colour ); + + ColourGuard( ColourGuard const& rhs ) = delete; + ColourGuard& operator=( ColourGuard const& rhs ) = delete; + + ColourGuard( ColourGuard&& rhs ) noexcept; + ColourGuard& operator=( ColourGuard&& rhs ) noexcept; + + //! Removes colour _if_ the guard was engaged + ~ColourGuard(); + + /** + * Explicitly engages colour for given stream. + * + * The API based on operator<< should be preferred. + */ + ColourGuard& engage( std::ostream& stream ) &; + /** + * Explicitly engages colour for given stream. + * + * The API based on operator<< should be preferred. + */ + ColourGuard&& engage( std::ostream& stream ) &&; + + private: + //! Engages the guard and starts using colour + friend std::ostream& operator<<( std::ostream& lhs, + ColourGuard& guard ) { + guard.engageImpl( lhs ); + return lhs; + } + //! Engages the guard and starts using colour + friend std::ostream& operator<<( std::ostream& lhs, + ColourGuard&& guard) { + guard.engageImpl( lhs ); + return lhs; + } + + void engageImpl( std::ostream& stream ); + + }; + + virtual ~ColourImpl(); // = default + /** + * Creates a guard object for given colour and this colour impl + * + * **Important:** + * the guard starts disengaged, and has to be engaged explicitly. + */ + ColourGuard guardColour( Colour::Code colourCode ); + + private: + virtual void use( Colour::Code colourCode ) const = 0; + }; + + //! Provides ColourImpl based on global config and target compilation platform + Detail::unique_ptr makeColourImpl( ColourMode colourSelection, + IStream* stream ); + + //! Checks if specific colour impl has been compiled into the binary + bool isColourImplAvailable( ColourMode colourSelection ); + +} // end namespace Catch + +#endif // CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + enum class ColourMode : std::uint8_t; + + namespace Detail { + //! Splits the reporter spec into reporter name and kv-pair options + std::vector splitReporterSpec( StringRef reporterSpec ); + + Optional stringToColourMode( StringRef colourMode ); + } + + /** + * Structured reporter spec that a reporter can be created from + * + * Parsing has been validated, but semantics have not. This means e.g. + * that the colour mode is known to Catch2, but it might not be + * compiled into the binary, and the output filename might not be + * openable. + */ + class ReporterSpec { + std::string m_name; + Optional m_outputFileName; + Optional m_colourMode; + std::map m_customOptions; + + friend bool operator==( ReporterSpec const& lhs, + ReporterSpec const& rhs ); + friend bool operator!=( ReporterSpec const& lhs, + ReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + + public: + ReporterSpec( + std::string name, + Optional outputFileName, + Optional colourMode, + std::map customOptions ); + + std::string const& name() const { return m_name; } + + Optional const& outputFile() const { + return m_outputFileName; + } + + Optional const& colourMode() const { return m_colourMode; } + + std::map const& customOptions() const { + return m_customOptions; + } + }; + + /** + * Parses provided reporter spec string into + * + * Returns empty optional on errors, e.g. + * * field that is not first and not a key+value pair + * * duplicated keys in kv pair + * * unknown catch reporter option + * * empty key/value in an custom kv pair + * * ... + */ + Optional parseReporterSpec( StringRef reporterSpec ); + +} + +#endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class IStream; + + /** + * `ReporterSpec` but with the defaults filled in. + * + * Like `ReporterSpec`, the semantics are unchecked. + */ + struct ProcessedReporterSpec { + std::string name; + std::string outputFilename; + ColourMode colourMode; + std::map customOptions; + friend bool operator==( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ); + friend bool operator!=( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + }; + + struct ConfigData { + + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listListeners = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + bool allowZeroTests = false; + + int abortAfter = -1; + uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default); + + unsigned int shardCount = 1; + unsigned int shardIndex = 0; + + bool skipBenchmarks = false; + bool benchmarkNoAnalysis = false; + unsigned int benchmarkSamples = 100; + double benchmarkConfidenceInterval = 0.95; + unsigned int benchmarkResamples = 100000; + std::chrono::milliseconds::rep benchmarkWarmupTime = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations showDurations = ShowDurations::DefaultForReporter; + double minDuration = -1; + TestRunOrder runOrder = TestRunOrder::Declared; + ColourMode defaultColourMode = ColourMode::PlatformDefault; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string defaultOutputFilename; + std::string name; + std::string processName; + std::vector reporterSpecifications; + + std::vector testsOrTags; + std::vector sectionsToRun; + }; + + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + ~Config() override; // = default in the cpp file + + bool listTests() const; + bool listTags() const; + bool listReporters() const; + bool listListeners() const; + + std::vector const& getReporterSpecs() const; + std::vector const& + getProcessedReporterSpecs() const; + + std::vector const& getTestsOrTags() const override; + std::vector const& getSectionsToRun() const override; + + TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + StringRef name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutUnmatchedTestSpecs() const override; + bool zeroTestsCountAsSuccess() const override; + ShowDurations showDurations() const override; + double minDuration() const override; + TestRunOrder runOrder() const override; + uint32_t rngSeed() const override; + unsigned int shardCount() const override; + unsigned int shardIndex() const override; + ColourMode defaultColourMode() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + bool skipBenchmarks() const override; + bool benchmarkNoAnalysis() const override; + unsigned int benchmarkSamples() const override; + double benchmarkConfidenceInterval() const override; + unsigned int benchmarkResamples() const override; + std::chrono::milliseconds benchmarkWarmupTime() const override; + + private: + ConfigData m_data; + std::vector m_processedReporterSpecs; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; +} // end namespace Catch + +#endif // CATCH_CONFIG_HPP_INCLUDED + + +#ifndef CATCH_GET_RANDOM_SEED_HPP_INCLUDED +#define CATCH_GET_RANDOM_SEED_HPP_INCLUDED + +#include + +namespace Catch { + //! Returns Catch2's current RNG seed. + std::uint32_t getSeed(); +} + +#endif // CATCH_GET_RANDOM_SEED_HPP_INCLUDED + + +#ifndef CATCH_MESSAGE_HPP_INCLUDED +#define CATCH_MESSAGE_HPP_INCLUDED + + + +#ifndef CATCH_STREAM_END_STOP_HPP_INCLUDED +#define CATCH_STREAM_END_STOP_HPP_INCLUDED + + +namespace Catch { + + // Use this in variadic streaming macros to allow + // << +StreamEndStop + // as well as + // << stuff +StreamEndStop + struct StreamEndStop { + StringRef operator+() const { return StringRef(); } + + template + friend T const& operator+( T const& value, StreamEndStop ) { + return value; + } + }; + +} // namespace Catch + +#endif // CATCH_STREAM_END_STOP_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct SourceLineInfo; + + struct MessageStream { + + template + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( StringRef macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ): + m_info(macroName, lineInfo, type) {} + + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage& duplicate ) = delete; + ScopedMessage( ScopedMessage&& old ) noexcept; + ~ScopedMessage(); + + MessageInfo m_info; + bool m_moved = false; + }; + + class Capturer { + std::vector m_messages; + IResultCapture& m_resultCapture = getResultCapture(); + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + + Capturer(Capturer const&) = delete; + Capturer& operator=(Capturer const&) = delete; + + ~Capturer(); + + void captureValue( size_t index, std::string const& value ); + + template + void captureValues( size_t index, T const& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template + void captureValues( size_t index, T const& value, Ts const&... values ) { + captureValue( index, Catch::Detail::stringify(value) ); + captureValues( index+1, values... ); + } + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + Catch::Capturer varName( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \ + varName.captureValues( 0, __VA_ARGS__ ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ + Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) + #define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg ) + #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ ) + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + + #define CATCH_INFO( msg ) (void)(0) + #define CATCH_UNSCOPED_INFO( msg ) (void)(0) + #define CATCH_WARN( msg ) (void)(0) + #define CATCH_CAPTURE( ... ) (void)(0) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) + #define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg ) + #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + + #define INFO( msg ) (void)(0) + #define UNSCOPED_INFO( msg ) (void)(0) + #define WARN( msg ) (void)(0) + #define CAPTURE( ... ) (void)(0) + +#endif // end of user facing macro declarations + + + + +#endif // CATCH_MESSAGE_HPP_INCLUDED + + +#ifndef CATCH_SESSION_HPP_INCLUDED +#define CATCH_SESSION_HPP_INCLUDED + + + +#ifndef CATCH_COMMANDLINE_HPP_INCLUDED +#define CATCH_COMMANDLINE_HPP_INCLUDED + + + +#ifndef CATCH_CLARA_HPP_INCLUDED +#define CATCH_CLARA_HPP_INCLUDED + +#if defined( __clang__ ) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +# pragma clang diagnostic ignored "-Wshadow" +# pragma clang diagnostic ignored "-Wdeprecated" +#endif + +#if defined( __GNUC__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +# ifdef __has_include +# if __has_include( ) && __cplusplus >= 201703L +# include +# define CLARA_CONFIG_OPTIONAL_TYPE std::optional +# endif +# endif +#endif + + +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + namespace Clara { + + class Args; + class Parser; + + // enum of result types from a parse + enum class ParseResultType { + Matched, + NoMatch, + ShortCircuitAll, + ShortCircuitSame + }; + + struct accept_many_t {}; + constexpr accept_many_t accept_many {}; + + namespace Detail { + struct fake_arg { + template + operator T(); + }; + + template + struct is_unary_function : std::false_type {}; + + template + struct is_unary_function< + F, + Catch::Detail::void_t()( fake_arg() ) ) + > + > : std::true_type {}; + + // Traits for extracting arg and return type of lambdas (for single + // argument lambdas) + template + struct UnaryLambdaTraits + : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = std::remove_const_t>; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Wraps a token coming from a token stream. These may not directly + // correspond to strings as a single string may encode an option + + // its argument if the : or = form is used + enum class TokenType { Option, Argument }; + struct Token { + TokenType type; + std::string token; + }; + + // Abstracts iterators into args as a stream of tokens, with option + // arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + + void loadBuffer(); + + public: + explicit TokenStream( Args const& args ); + TokenStream( Iterator it, Iterator itEnd ); + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + size_t count() const { + return m_tokenBuffer.size() + ( itEnd - it ); + } + + Token operator*() const { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + Token const* operator->() const { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + TokenStream& operator++(); + }; + + //! Denotes type of a parsing result + enum class ResultType { + Ok, ///< No errors + LogicError, ///< Error in user-specified arguments for + ///< construction + RuntimeError ///< Error in parsing inputs + }; + + class ResultBase { + protected: + ResultBase( ResultType type ): m_type( type ) {} + virtual ~ResultBase(); // = default; + + + ResultBase(ResultBase const&) = default; + ResultBase& operator=(ResultBase const&) = default; + ResultBase(ResultBase&&) = default; + ResultBase& operator=(ResultBase&&) = default; + + virtual void enforceOk() const = 0; + + ResultType m_type; + }; + + template class ResultValueBase : public ResultBase { + public: + auto value() const -> T const& { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase( ResultType type ): ResultBase( type ) {} + + ResultValueBase( ResultValueBase const& other ): + ResultBase( other ) { + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( other.m_value ); + } + + ResultValueBase( ResultType, T const& value ): ResultBase( ResultType::Ok ) { + new ( &m_value ) T( value ); + } + + auto operator=( ResultValueBase const& other ) + -> ResultValueBase& { + if ( m_type == ResultType::Ok ) + m_value.~T(); + ResultBase::operator=( other ); + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( other.m_value ); + return *this; + } + + ~ResultValueBase() override { + if ( m_type == ResultType::Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template <> class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult( BasicResult const& other ): + ResultValueBase( other.type() ), + m_errorMessage( other.errorMessage() ) { + assert( type() != ResultType::Ok ); + } + + template + static auto ok( U const& value ) -> BasicResult { + return { ResultType::Ok, value }; + } + static auto ok() -> BasicResult { return { ResultType::Ok }; } + static auto logicError( std::string&& message ) + -> BasicResult { + return { ResultType::LogicError, CATCH_MOVE(message) }; + } + static auto runtimeError( std::string&& message ) + -> BasicResult { + return { ResultType::RuntimeError, CATCH_MOVE(message) }; + } + + explicit operator bool() const { + return m_type == ResultType::Ok; + } + auto type() const -> ResultType { return m_type; } + auto errorMessage() const -> std::string const& { + return m_errorMessage; + } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultType::LogicError ); + assert( m_type != ResultType::RuntimeError ); + if ( m_type != ResultType::Ok ) + std::abort(); + } + + std::string + m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultType type, + std::string&& message ): + ResultValueBase( type ), m_errorMessage( CATCH_MOVE(message) ) { + assert( m_type != ResultType::Ok ); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + class ParseState { + public: + ParseState( ParseResultType type, + TokenStream const& remainingTokens ); + + ParseResultType type() const { return m_type; } + TokenStream const& remainingTokens() const { + return m_remainingTokens; + } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template + ParserResult convertInto( std::string const& source, T& target ) { + std::stringstream ss( source ); + ss >> target; + if ( ss.fail() ) { + return ParserResult::runtimeError( + "Unable to convert '" + source + + "' to destination type" ); + } else { + return ParserResult::ok( ParseResultType::Matched ); + } + } + ParserResult convertInto( std::string const& source, + std::string& target ); + ParserResult convertInto( std::string const& source, bool& target ); + +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template + auto convertInto( std::string const& source, + CLARA_CONFIG_OPTIONAL_TYPE& target ) + -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if ( result ) + target = CATCH_MOVE( temp ); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct BoundRef : Catch::Detail::NonCopyable { + virtual ~BoundRef() = default; + virtual bool isContainer() const; + virtual bool isFlag() const; + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const& arg ) + -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + bool isFlag() const override; + }; + + template struct BoundValueRef : BoundValueRefBase { + T& m_ref; + + explicit BoundValueRef( T& ref ): m_ref( ref ) {} + + ParserResult setValue( std::string const& arg ) override { + return convertInto( arg, m_ref ); + } + }; + + template + struct BoundValueRef> : BoundValueRefBase { + std::vector& m_ref; + + explicit BoundValueRef( std::vector& ref ): m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const& arg ) + -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if ( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool& m_ref; + + explicit BoundFlagRef( bool& ref ): m_ref( ref ) {} + + ParserResult setFlag( bool flag ) override; + }; + + template struct LambdaInvoker { + static_assert( + std::is_same::value, + "Lambda must return void or clara::ParserResult" ); + + template + static auto invoke( L const& lambda, ArgType const& arg ) + -> ParserResult { + return lambda( arg ); + } + }; + + template <> struct LambdaInvoker { + template + static auto invoke( L const& lambda, ArgType const& arg ) + -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + auto invokeLambda( L const& lambda, std::string const& arg ) + -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result ? result + : LambdaInvoker::ReturnType>::invoke( lambda, temp ); + } + + template struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits::isValid, + "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const& lambda ): m_lambda( lambda ) {} + + auto setValue( std::string const& arg ) + -> ParserResult override { + return invokeLambda::ArgType>( + m_lambda, arg ); + } + }; + + template struct BoundManyLambda : BoundLambda { + explicit BoundManyLambda( L const& lambda ): BoundLambda( lambda ) {} + bool isContainer() const override { return true; } + }; + + template struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits::isValid, + "Supplied lambda must take exactly one argument" ); + static_assert( + std::is_same::ArgType, + bool>::value, + "flags must be boolean" ); + + explicit BoundFlagLambda( L const& lambda ): + m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, + TokenStream const& tokens ) const + -> InternalParseResult = 0; + virtual size_t cardinality() const; + + InternalParseResult parse( Args const& args ) const; + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator|( T const& other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl( std::shared_ptr const& ref ): + m_ref( ref ) {} + + public: + template + ParserRefImpl( accept_many_t, + LambdaT const& ref, + std::string const& hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + template ::value>> + ParserRefImpl( T& ref, std::string const& hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + template ::value>> + ParserRefImpl( LambdaT const& ref, std::string const& hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + auto operator()( std::string const& description ) -> DerivedT& { + m_description = description; + return static_cast( *this ); + } + + auto optional() -> DerivedT& { + m_optionality = Optionality::Optional; + return static_cast( *this ); + } + + auto required() -> DerivedT& { + m_optionality = Optionality::Required; + return static_cast( *this ); + } + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if ( m_ref->isContainer() ) + return 0; + else + return 1; + } + + std::string const& hint() const { return m_hint; } + }; + + } // namespace detail + + + // A parser for arguments + class Arg : public Detail::ParserRefImpl { + public: + using ParserRefImpl::ParserRefImpl; + using ParserBase::parse; + + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream const& tokens) const override; + }; + + // A parser for options + class Opt : public Detail::ParserRefImpl { + protected: + std::vector m_optNames; + + public: + template + explicit Opt(LambdaT const& ref) : + ParserRefImpl( + std::make_shared>(ref)) {} + + explicit Opt(bool& ref); + + template ::value>> + Opt( LambdaT const& ref, std::string const& hint ): + ParserRefImpl( ref, hint ) {} + + template + Opt( accept_many_t, LambdaT const& ref, std::string const& hint ): + ParserRefImpl( accept_many, ref, hint ) {} + + template ::value>> + Opt( T& ref, std::string const& hint ): + ParserRefImpl( ref, hint ) {} + + auto operator[](std::string const& optName) -> Opt& { + m_optNames.push_back(optName); + return *this; + } + + std::vector getHelpColumns() const; + + bool isMatch(std::string const& optToken) const; + + using ParserBase::parse; + + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream const& tokens) const override; + + Detail::Result validate() const override; + }; + + // Specifies the name of the executable + class ExeName : public Detail::ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + public: + ExeName(); + explicit ExeName(std::string& ref); + + template + explicit ExeName(LambdaT const& lambda) : ExeName() { + m_ref = std::make_shared>(lambda); + } + + // The exe name is not parsed out of the normal tokens, but is + // handled specially + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream const& tokens) const override; + + std::string const& name() const { return *m_name; } + Detail::ParserResult set(std::string const& newName); + }; + + + // A Combined parser + class Parser : Detail::ParserBase { + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + public: + + auto operator|=(ExeName const& exeName) -> Parser& { + m_exeName = exeName; + return *this; + } + + auto operator|=(Arg const& arg) -> Parser& { + m_args.push_back(arg); + return *this; + } + + auto operator|=(Opt const& opt) -> Parser& { + m_options.push_back(opt); + return *this; + } + + Parser& operator|=(Parser const& other); + + template + auto operator|(T const& other) const -> Parser { + return Parser(*this) |= other; + } + + std::vector getHelpColumns() const; + + void writeToStream(std::ostream& os) const; + + friend auto operator<<(std::ostream& os, Parser const& parser) + -> std::ostream& { + parser.writeToStream(os); + return os; + } + + Detail::Result validate() const override; + + using ParserBase::parse; + Detail::InternalParseResult + parse(std::string const& exeName, + Detail::TokenStream const& tokens) const override; + }; + + // Transport for raw args (copied from main args, or supplied via + // init list for testing) + class Args { + friend Detail::TokenStream; + std::string m_exeName; + std::vector m_args; + + public: + Args(int argc, char const* const* argv); + Args(std::initializer_list args); + + std::string const& exeName() const { return m_exeName; } + }; + + + // Convenience wrapper for option parser that specifies the help option + struct Help : Opt { + Help(bool& showHelpFlag); + }; + + // Result type for parser operation + using Detail::ParserResult; + + namespace Detail { + template + template + Parser + ComposableParserImpl::operator|(T const& other) const { + return Parser() | static_cast(*this) | other; + } + } + + } // namespace Clara +} // namespace Catch + +#if defined( __clang__ ) +# pragma clang diagnostic pop +#endif + +#if defined( __GNUC__ ) +# pragma GCC diagnostic pop +#endif + +#endif // CATCH_CLARA_HPP_INCLUDED + +namespace Catch { + + struct ConfigData; + + Clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +#endif // CATCH_COMMANDLINE_HPP_INCLUDED + +namespace Catch { + + class Session : Detail::NonCopyable { + public: + + Session(); + ~Session(); + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int applyCommandLine( int argc, wchar_t const * const * argv ); + #endif + + void useConfigData( ConfigData const& configData ); + + template + int run(int argc, CharT const * const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + Clara::Parser const& cli() const; + void cli( Clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + Clara::Parser m_cli; + ConfigData m_configData; + Detail::unique_ptr m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +#endif // CATCH_SESSION_HPP_INCLUDED + + +#ifndef CATCH_TAG_ALIAS_HPP_INCLUDED +#define CATCH_TAG_ALIAS_HPP_INCLUDED + + +#include + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo): + tag(_tag), + lineInfo(_lineInfo) + {} + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +#endif // CATCH_TAG_ALIAS_HPP_INCLUDED + + +#ifndef CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED +#define CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + + +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + + +#ifndef CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +// inside templates (so `TEMPLATE_TEST_CASE` and co). +// **THIS IS DIFFERENT FOR STANDARD TESTS, WHERE GCC 9 IS SUFFICIENT** +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +#pragma GCC diagnostic ignored "-Wparentheses" +#endif + + + + +#ifndef CATCH_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEST_MACROS_HPP_INCLUDED + + + +#ifndef CATCH_TEST_MACRO_IMPL_HPP_INCLUDED +#define CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + + + +#ifndef CATCH_ASSERTION_HANDLER_HPP_INCLUDED +#define CATCH_ASSERTION_HANDLER_HPP_INCLUDED + + + +#ifndef CATCH_DECOMPOSER_HPP_INCLUDED +#define CATCH_DECOMPOSER_HPP_INCLUDED + + +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#pragma warning(disable:4800) // Forcing result to true or false +#endif + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wsign-compare" +#elif defined __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-compare" +#endif + +namespace Catch { + + class ITransientExpression { + bool m_isBinaryExpression; + bool m_result; + + public: + auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + auto getResult() const -> bool { return m_result; } + virtual void streamReconstructedExpression( std::ostream &os ) const = 0; + + ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + ITransientExpression() = default; + ITransientExpression(ITransientExpression const&) = default; + ITransientExpression& operator=(ITransientExpression const&) = default; + + // We don't actually need a virtual destructor, but many static analysers + // complain if it's not here :-( + virtual ~ITransientExpression(); // = default; + + friend std::ostream& operator<<(std::ostream& out, ITransientExpression const& expr) { + expr.streamReconstructedExpression(out); + return out; + } + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + + template + auto operator && ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator || ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator == ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator != ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator > ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator < ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator >= ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator <= ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + }; + + template + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, static_cast(lhs) }, + m_lhs( lhs ) + {} + }; + + + // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) + template + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast(lhs == rhs); } + template + auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + template + auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + + template + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast(lhs != rhs); } + template + auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + template + auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + + + template + class ExprLhs { + LhsT m_lhs; + public: + explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + + template>::value, int> = 0> + friend auto operator == ( ExprLhs && lhs, RhsT && rhs ) -> BinaryExpr { + return { compareEqual( lhs.m_lhs, rhs ), lhs.m_lhs, "=="_sr, rhs }; + } + template::value, int> = 0> + friend auto operator == ( ExprLhs && lhs, RhsT rhs ) -> BinaryExpr { + return { compareEqual( lhs.m_lhs, rhs ), lhs.m_lhs, "=="_sr, rhs }; + } + + template>::value, int> = 0> + friend auto operator != ( ExprLhs && lhs, RhsT && rhs ) -> BinaryExpr { + return { compareNotEqual( lhs.m_lhs, rhs ), lhs.m_lhs, "!="_sr, rhs }; + } + template::value, int> = 0> + friend auto operator != ( ExprLhs && lhs, RhsT rhs ) -> BinaryExpr { + return { compareNotEqual( lhs.m_lhs, rhs ), lhs.m_lhs, "!="_sr, rhs }; + } + + #define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(op) \ + template>::value, int> = 0> \ + friend auto operator op ( ExprLhs && lhs, RhsT && rhs ) -> BinaryExpr { \ + return { static_cast(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template::value, int> = 0> \ + friend auto operator op ( ExprLhs && lhs, RhsT rhs ) -> BinaryExpr { \ + return { static_cast(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(<) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(>) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(<=) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(>=) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(|) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(&) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(^) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR + + template + friend auto operator && ( ExprLhs &&, RhsT && ) -> BinaryExpr { + static_assert(always_false::value, + "operator&& is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + friend auto operator || ( ExprLhs &&, RhsT && ) -> BinaryExpr { + static_assert(always_false::value, + "operator|| is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + auto makeUnaryExpr() const -> UnaryExpr { + return UnaryExpr{ m_lhs }; + } + }; + + struct Decomposer { + template>::value, int> = 0> + friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs { + return ExprLhs{ lhs }; + } + + template::value, int> = 0> + friend auto operator <= ( Decomposer &&, T value ) -> ExprLhs { + return ExprLhs{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // CATCH_DECOMPOSER_HPP_INCLUDED + +#include + +namespace Catch { + + class IResultCapture; + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + + template + void handleExpr( ExprLhs const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, StringRef message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); + +} // namespace Catch + +#endif // CATCH_ASSERTION_HANDLER_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ <= 9 + #pragma GCC diagnostic ignored "-Wparentheses" +#endif + +#if !defined(CATCH_CONFIG_DISABLE) + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { /* NOLINT(bugprone-infinite-loop) */ \ + /* The expression should not be evaluated, but warnings should hopefully be checked */ \ + CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, (false) && static_cast( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(expr); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + + + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +#endif // CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + + +#ifndef CATCH_SECTION_HPP_INCLUDED +#define CATCH_SECTION_HPP_INCLUDED + + + +#ifndef CATCH_TIMER_HPP_INCLUDED +#define CATCH_TIMER_HPP_INCLUDED + +#include + +namespace Catch { + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +#endif // CATCH_TIMER_HPP_INCLUDED + +namespace Catch { + + class Section : Detail::NonCopyable { + public: + Section( SectionInfo&& info ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif // CATCH_SECTION_HPP_INCLUDED + + +#ifndef CATCH_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEST_REGISTRY_HPP_INCLUDED + + + +#ifndef CATCH_INTERFACES_TESTCASE_HPP_INCLUDED +#define CATCH_INTERFACES_TESTCASE_HPP_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + struct TestCaseInfo; + + class ITestInvoker { + public: + virtual void invoke () const = 0; + virtual ~ITestInvoker(); // = default + }; + + class TestCaseHandle; + class IConfig; + + class ITestCaseRegistry { + public: + virtual ~ITestCaseRegistry(); // = default + // TODO: this exists only for adding filenames to test cases -- let's expose this in a saner way later + virtual std::vector const& getAllInfos() const = 0; + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config ); + bool matchTest( TestCaseHandle const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +#endif // CATCH_INTERFACES_TESTCASE_HPP_INCLUDED + + +#ifndef CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED +#define CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +#define INTERNAL_CATCH_EXPAND1( param ) INTERNAL_CATCH_EXPAND2( param ) +#define INTERNAL_CATCH_EXPAND2( ... ) INTERNAL_CATCH_NO##__VA_ARGS__ +#define INTERNAL_CATCH_DEF( ... ) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#define INTERNAL_CATCH_REMOVE_PARENS( ... ) \ + INTERNAL_CATCH_EXPAND1( INTERNAL_CATCH_DEF __VA_ARGS__ ) + +#endif // CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +// GCC 5 and older do not properly handle disabling unused-variable warning +// with a _Pragma. This means that we have to leak the suppression to the +// user code as well :-( +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5 +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + + + +namespace Catch { + +template +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +Detail::unique_ptr makeTestInvoker( void(*testAsFunction)() ); + +template +Detail::unique_ptr makeTestInvoker( void (C::*testAsMethod)() ) { + return Detail::make_unique>( testAsMethod ); +} + +struct NameAndTags { + constexpr NameAndTags( StringRef name_ = StringRef(), + StringRef tags_ = StringRef() ) noexcept: + name( name_ ), tags( tags_ ) {} + StringRef name; + StringRef tags; +}; + +struct AutoReg : Detail::NonCopyable { + AutoReg( Detail::unique_ptr invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept; +}; + +} // end namespace Catch + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static inline void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() +#endif + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + do { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } while(false) + + +#endif // CATCH_TEST_REGISTRY_HPP_INCLUDED + + +// All of our user-facing macros support configuration toggle, that +// forces them to be defined prefixed with CATCH_. We also like to +// support another toggle that can minimize (disable) their implementation. +// Given this, we have 4 different configuration options below + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + + #define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) + #define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + #define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + #define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + + #define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) + #define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) + #else + #define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) CATCH_CHECK( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) CATCH_CHECK_FALSE( __VA_ARGS__ ) + #endif + + + // "BDD-style" convenience wrappers + #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) + #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + #define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) + #define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) + #define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) + #define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) + #define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) + #define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, implemented | vv prefixed, disabled + + #define CATCH_REQUIRE( ... ) (void)(0) + #define CATCH_REQUIRE_FALSE( ... ) (void)(0) + + #define CATCH_REQUIRE_THROWS( ... ) (void)(0) + #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) + #define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + + #define CATCH_CHECK( ... ) (void)(0) + #define CATCH_CHECK_FALSE( ... ) (void)(0) + #define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) + #define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) + #define CATCH_CHECK_NOFAIL( ... ) (void)(0) + + #define CATCH_CHECK_THROWS( ... ) (void)(0) + #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) + #define CATCH_CHECK_NOTHROW( ... ) (void)(0) + + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) + #define CATCH_SECTION( ... ) + #define CATCH_DYNAMIC_SECTION( ... ) + #define CATCH_FAIL( ... ) (void)(0) + #define CATCH_FAIL_CHECK( ... ) (void)(0) + #define CATCH_SUCCEED( ... ) (void)(0) + + #define CATCH_STATIC_REQUIRE( ... ) (void)(0) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define CATCH_STATIC_CHECK( ... ) (void)(0) + #define CATCH_STATIC_CHECK_FALSE( ... ) (void)(0) + + // "BDD-style" convenience wrappers + #define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) + #define CATCH_GIVEN( desc ) + #define CATCH_AND_GIVEN( desc ) + #define CATCH_WHEN( desc ) + #define CATCH_AND_WHEN( desc ) + #define CATCH_THEN( desc ) + #define CATCH_AND_THEN( desc ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, disabled | vv unprefixed, implemented + + #define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + + #define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) + #define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + #define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + #define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + + #define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) + #define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) + #define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) + #define STATIC_CHECK( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) + #else + #define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) + #define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) + #define STATIC_CHECK( ... ) CHECK( __VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) CHECK_FALSE( __VA_ARGS__ ) + #endif + + // "BDD-style" convenience wrappers + #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) + #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + #define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) + #define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) + #define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) + #define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) + #define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) + #define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ unprefixed, implemented | vv unprefixed, disabled + + #define REQUIRE( ... ) (void)(0) + #define REQUIRE_FALSE( ... ) (void)(0) + + #define REQUIRE_THROWS( ... ) (void)(0) + #define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) + #define REQUIRE_NOTHROW( ... ) (void)(0) + + #define CHECK( ... ) (void)(0) + #define CHECK_FALSE( ... ) (void)(0) + #define CHECKED_IF( ... ) if (__VA_ARGS__) + #define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) + #define CHECK_NOFAIL( ... ) (void)(0) + + #define CHECK_THROWS( ... ) (void)(0) + #define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) + #define CHECK_NOTHROW( ... ) (void)(0) + + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define METHOD_AS_TEST_CASE( method, ... ) + #define REGISTER_TEST_CASE( Function, ... ) (void)(0) + #define SECTION( ... ) + #define DYNAMIC_SECTION( ... ) + #define FAIL( ... ) (void)(0) + #define FAIL_CHECK( ... ) (void)(0) + #define SUCCEED( ... ) (void)(0) + + #define STATIC_REQUIRE( ... ) (void)(0) + #define STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define STATIC_CHECK( ... ) (void)(0) + #define STATIC_CHECK_FALSE( ... ) (void)(0) + + // "BDD-style" convenience wrappers + #define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ) ) + #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) + + #define GIVEN( desc ) + #define AND_GIVEN( desc ) + #define WHEN( desc ) + #define AND_WHEN( desc ) + #define THEN( desc ) + #define AND_THEN( desc ) + +#endif // ^^ unprefixed, disabled + +// end of user facing macros + +#endif // CATCH_TEST_MACROS_HPP_INCLUDED + + +#ifndef CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED + + + +#ifndef CATCH_PREPROCESSOR_HPP_INCLUDED +#define CATCH_PREPROCESSOR_HPP_INCLUDED + + +#if defined(__GNUC__) +// We need to silence "empty __VA_ARGS__ warning", and using just _Pragma does not work +#pragma GCC system_header +#endif + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template