├── .clang-format ├── .clang-tidy ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── BUILDING.md ├── CMakeLists.txt ├── CMakePresets.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── HACKING.md ├── LICENSE ├── README.md ├── cmake ├── coverage.cmake ├── dev-mode.cmake ├── fetch-fftw.cmake ├── fetch-nlohmann_json.cmake ├── fetch-stb.cmake ├── folders.cmake ├── install-config.cmake ├── install-rules.cmake ├── lint-targets.cmake ├── lint.cmake ├── prelude.cmake ├── project-is-top-level.cmake ├── variables.cmake └── version.cmake ├── codecov.yml ├── examples ├── CMakeLists.txt └── src │ ├── custom_alloc.c │ ├── custom_libc.c │ ├── f64.c │ ├── json.cpp │ ├── periodogram.c │ ├── simple.c │ └── simple.cpp ├── images ├── attempts_300.png ├── seed_1981.png ├── seed_and_attempts.pdf ├── seed_and_attempts.png ├── simple.png ├── simple_circles.png ├── simple_example.pdf ├── simple_example.png └── tph_poisson_periodogram_512.png ├── include └── thinks │ └── tph_poisson.h ├── python └── poisson_plot.py └── test ├── CMakeLists.txt └── src ├── require.h ├── tph_poisson_alloc_test.c ├── tph_poisson_f32.c ├── tph_poisson_f32.h ├── tph_poisson_f64.c ├── tph_poisson_f64.h ├── tph_poisson_libc_test.c ├── tph_poisson_test.cpp └── tph_poisson_vec_test.c /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -2 2 | AlignAfterOpenBracket: DontAlign 3 | AlignConsecutiveAssignments: false 4 | AlignConsecutiveDeclarations: false 5 | AlignEscapedNewlines: Left 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: true 10 | AllowShortCaseLabelsOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: All 12 | AllowShortIfStatementsOnASingleLine: true 13 | AllowShortLoopsOnASingleLine: true 14 | AlwaysBreakAfterDefinitionReturnType: None 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: false 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BraceWrapping: 21 | AfterClass: true 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: true 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: true 28 | AfterUnion: false 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | SplitEmptyFunction: false 33 | SplitEmptyNamespace: true 34 | SplitEmptyRecord: true 35 | BreakAfterJavaFieldAnnotations: true 36 | BreakBeforeBinaryOperators: NonAssignment 37 | BreakBeforeBraces: Custom 38 | BreakBeforeInheritanceComma: true 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializers: BeforeColon 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakStringLiterals: true 43 | ColumnLimit: 100 44 | CommentPragmas: '^ IWYU pragma:' 45 | CompactNamespaces: false 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 47 | ConstructorInitializerIndentWidth: 2 48 | ContinuationIndentWidth: 2 49 | Cpp11BracedListStyle: false 50 | DerivePointerAlignment: false 51 | DisableFormat: false 52 | ExperimentalAutoDetectBinPacking: true 53 | FixNamespaceComments: true 54 | ForEachMacros: 55 | - foreach 56 | - Q_FOREACH 57 | - BOOST_FOREACH 58 | IncludeCategories: 59 | - Priority: 2 60 | Regex: ^"(llvm|llvm-c|clang|clang-c)/ 61 | - Priority: 3 62 | Regex: ^(<|"(gtest|gmock|isl|json)/) 63 | - Priority: 1 64 | Regex: .* 65 | IncludeIsMainRegex: (Test)?$ 66 | IndentCaseLabels: false 67 | IndentWidth: 2 68 | IndentWrappedFunctionNames: true 69 | JavaScriptQuotes: Leave 70 | JavaScriptWrapImports: true 71 | KeepEmptyLinesAtTheStartOfBlocks: true 72 | Language: Cpp 73 | MacroBlockBegin: '' 74 | MacroBlockEnd: '' 75 | MaxEmptyLinesToKeep: 2 76 | NamespaceIndentation: Inner 77 | ObjCBlockIndentWidth: 7 78 | ObjCSpaceAfterProperty: true 79 | ObjCSpaceBeforeProtocolList: false 80 | PointerAlignment: Right 81 | ReflowComments: true 82 | SortIncludes: true 83 | SortUsingDeclarations: false 84 | SpaceAfterCStyleCast: false 85 | SpaceAfterTemplateKeyword: false 86 | SpaceBeforeAssignmentOperators: true 87 | SpaceBeforeParens: ControlStatements 88 | SpaceInEmptyParentheses: false 89 | SpacesBeforeTrailingComments: 0 90 | SpacesInAngles: false 91 | SpacesInCStyleCastParentheses: false 92 | SpacesInContainerLiterals: true 93 | SpacesInParentheses: false 94 | SpacesInSquareBrackets: false 95 | Standard: c++20 96 | TabWidth: 8 97 | UseTab: Never -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | # Enable ALL the things! Except not really. 3 | # 4 | # NOTE(thinks): 5 | # Sadly, clang-analyzer-unix.Malloc gives false positives when using our 6 | # custom allocators. 7 | Checks: "-*,\ 8 | -google-readability-todo,\ 9 | clang-analyzer-*,\ 10 | -clang-analyzer-unix.Malloc,\ 11 | -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,\ 12 | cert-*,\ 13 | bugprone-*,\ 14 | -bugprone-easily-swappable-parameters,\ 15 | concurrency-*,\ 16 | misc-*,\ 17 | -misc-definitions-in-headers,\ 18 | portability-*,\ 19 | openmp-*,\ 20 | readability-*,\ 21 | -readability-magic-numbers,\ 22 | -readability-identifier-length,\ 23 | -readability-function-cognitive-complexity" 24 | WarningsAsErrors: '*' 25 | CheckOptions: 26 | - key: 'bugprone-argument-comment.StrictMode' 27 | value: 'true' 28 | - key: 'bugprone-argument-comment.CommentBoolLiterals' 29 | value: 'true' 30 | - key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts' 31 | value: 'true' 32 | - key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression' 33 | value: 'true' 34 | - key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison' 35 | value: 'true' 36 | - key: 'readability-simplify-boolean-expr.ChainedConditionalReturn' 37 | value: 'true' 38 | - key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment' 39 | value: 'true' 40 | - key: 'readability-else-after-return.WarnOnUnfixable' 41 | value: 'true' 42 | - key: 'readability-else-after-return.WarnOnConditionVariables' 43 | value: 'true' 44 | - key: 'readability-inconsistent-declaration-parameter-name.Strict' 45 | value: 'true' 46 | ... 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-22.04 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Install clang-format 20 | run: sudo apt update -q 21 | && sudo apt install clang-format -q -y 22 | 23 | - name: Lint 24 | run: cmake -D FORMAT_COMMAND=clang-format -P cmake/lint.cmake 25 | 26 | coverage: 27 | needs: [lint] 28 | 29 | runs-on: ubuntu-22.04 30 | if: github.repository_owner == 'thinks' 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - name: Install LCov 36 | run: sudo apt-get update -q 37 | && sudo apt-get install lcov -q -y 38 | 39 | - name: Configure 40 | run: cmake --preset=ci-coverage 41 | 42 | - name: Build 43 | run: cmake --build build/coverage -j 2 44 | 45 | - name: Test 46 | working-directory: build/coverage 47 | run: ctest --output-on-failure --no-tests=error -j 2 48 | 49 | - name: Process coverage info 50 | run: cmake --build build/coverage -t coverage 51 | 52 | - name: Submit to codecov.io 53 | uses: codecov/codecov-action@v4 54 | with: 55 | file: build/coverage/coverage.info 56 | token: ${{ secrets.CODECOV_TOKEN }} 57 | 58 | sanitize: 59 | needs: [lint] 60 | 61 | runs-on: ubuntu-22.04 62 | 63 | env: { CC: clang-14 } 64 | 65 | steps: 66 | - uses: actions/checkout@v4 67 | 68 | - name: Configure 69 | run: cmake --preset=ci-sanitize 70 | 71 | - name: Build 72 | run: cmake --build build/sanitize -j 2 73 | 74 | - name: Test 75 | working-directory: build/sanitize 76 | env: 77 | ASAN_OPTIONS: "strict_string_checks=1:\ 78 | detect_stack_use_after_return=1:\ 79 | check_initialization_order=1:\ 80 | strict_init_order=1:\ 81 | detect_leaks=1:\ 82 | halt_on_error=1" 83 | UBSAN_OPTIONS: "print_stacktrace=1:\ 84 | halt_on_error=1" 85 | run: | 86 | ctest --output-on-failure --no-tests=error -j 2 87 | cmake --build . --target run-examples 88 | 89 | test: 90 | needs: [lint] 91 | 92 | strategy: 93 | matrix: 94 | os: [macos-14, ubuntu-22.04, windows-2022] 95 | 96 | runs-on: ${{ matrix.os }} 97 | 98 | steps: 99 | - uses: actions/checkout@v4 100 | 101 | - name: Install static analyzers 102 | if: matrix.os == 'ubuntu-22.04' 103 | run: >- 104 | sudo apt-get install clang-tidy-14 cppcheck -y -q 105 | 106 | sudo update-alternatives --install 107 | /usr/bin/clang-tidy clang-tidy 108 | /usr/bin/clang-tidy-14 140 109 | 110 | - name: Setup MultiToolTask 111 | if: matrix.os == 'windows-2022' 112 | run: | 113 | Add-Content "$env:GITHUB_ENV" 'UseMultiToolTask=true' 114 | Add-Content "$env:GITHUB_ENV" 'EnforceProcessCountAcrossBuilds=true' 115 | 116 | - name: Configure 117 | shell: pwsh 118 | run: cmake "--preset=ci-$("${{ matrix.os }}".split("-")[0])" 119 | 120 | - name: Build 121 | run: cmake --build build --config Release -j 2 122 | 123 | - name: Install 124 | run: cmake --install build --config Release --prefix prefix 125 | 126 | - name: Test 127 | working-directory: build 128 | run: ctest --output-on-failure --no-tests=error -C Release -j 2 129 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | *.sqlite 34 | *.db 35 | *.opendb 36 | *.ipch 37 | 38 | .vs/ 39 | .vscode/ 40 | ipch/ 41 | build/ 42 | INSTALL/ 43 | CMakeSettings.json 44 | CMakeUserPresets.json 45 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Building with CMake 2 | 3 | ## Build 4 | 5 | This project doesn't require any special command-line flags to build to keep 6 | things simple. 7 | 8 | Here are the steps for building in release mode with a single-configuration 9 | generator, like the Unix Makefiles one: 10 | 11 | ```sh 12 | cmake -S . -B build -D CMAKE_BUILD_TYPE=Release 13 | cmake --build build 14 | ``` 15 | 16 | Here are the steps for building in release mode with a multi-configuration 17 | generator, like the Visual Studio ones: 18 | 19 | ```sh 20 | cmake -S . -B build 21 | cmake --build build --config Release 22 | ``` 23 | 24 | ### Building with MSVC 25 | 26 | Note that MSVC by default is not standards compliant and you need to pass some 27 | flags to make it behave properly. See the `flags-msvc` preset in the 28 | [CMakePresets.json](CMakePresets.json) file for the flags and with what 29 | variable to provide them to CMake during configuration. 30 | 31 | ### Building on Apple Silicon 32 | 33 | CMake supports building on Apple Silicon properly since 3.20.1. Make sure you 34 | have the [latest version][1] installed. 35 | 36 | ## Install 37 | 38 | This project doesn't require any special command-line flags to install to keep 39 | things simple. As a prerequisite, the project has to be built with the above 40 | commands already. 41 | 42 | The below commands require at least CMake 3.15 to run, because that is the 43 | version in which [Install a Project][2] was added. 44 | 45 | Here is the command for installing the release mode artifacts with a 46 | single-configuration generator, like the Unix Makefiles one: 47 | 48 | ```sh 49 | cmake --install build 50 | ``` 51 | 52 | Here is the command for installing the release mode artifacts with a 53 | multi-configuration generator, like the Visual Studio ones: 54 | 55 | ```sh 56 | cmake --install build --config Release 57 | ``` 58 | 59 | ### CMake package 60 | 61 | This project exports a CMake package to be used with the [`find_package`][3] 62 | command of CMake: 63 | 64 | * Package name: `tph_poisson` 65 | * Target name: `thinks::tph_poisson` 66 | 67 | Example usage: 68 | 69 | ```cmake 70 | find_package(tph_poisson REQUIRED) 71 | # Declare the imported target as a build requirement using PRIVATE, where 72 | # project_target is a target created in the consuming project. 73 | target_link_libraries(project_target 74 | PRIVATE thinks::tph_poisson 75 | ) 76 | ``` 77 | 78 | ### Note to packagers 79 | 80 | The `CMAKE_INSTALL_INCLUDEDIR` is set to a path other than just `include` if 81 | the project is configured as a top level project to avoid indirectly including 82 | other libraries when installed to a common prefix. Please review the 83 | [install-rules.cmake](cmake/install-rules.cmake) file for the full set of 84 | install rules. 85 | 86 | [1]: https://cmake.org/download/ 87 | [2]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project 88 | [3]: https://cmake.org/cmake/help/latest/command/find_package.html 89 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | message(STATUS "CMake version: ${CMAKE_VERSION}") 3 | 4 | include(cmake/prelude.cmake) 5 | include(cmake/version.cmake) 6 | 7 | project(tph_poisson 8 | VERSION "${tph_poisson_MAJOR_VERSION}.${tph_poisson_MINOR_VERSION}.${tph_poisson_PATCH_VERSION}" 9 | DESCRIPTION "A single file implementation of Poisson disk sampling in arbitrary dimensions." 10 | HOMEPAGE_URL "https://github.com/thinks/poisson-disk-sampling" 11 | LANGUAGES C CXX 12 | ) 13 | 14 | message(STATUS "${PROJECT_NAME}-${PROJECT_VERSION}") 15 | 16 | include(cmake/project-is-top-level.cmake) 17 | include(cmake/variables.cmake) 18 | 19 | # ---- Declare library ---- 20 | 21 | add_library(thinks_tph_poisson INTERFACE) 22 | add_library(thinks::tph_poisson ALIAS thinks_tph_poisson) 23 | 24 | set_property( 25 | TARGET thinks_tph_poisson PROPERTY 26 | EXPORT_NAME tph_poisson 27 | ) 28 | 29 | target_include_directories(thinks_tph_poisson ${warning_guard} 30 | INTERFACE 31 | "\$" 32 | ) 33 | 34 | # ---- Install rules ---- 35 | 36 | if(NOT CMAKE_SKIP_INSTALL_RULES) 37 | include(cmake/install-rules.cmake) 38 | endif() 39 | 40 | # ---- Examples ---- 41 | 42 | if(PROJECT_IS_TOP_LEVEL) 43 | option(BUILD_EXAMPLES "Build examples tree." "${tph_poisson_DEVELOPER_MODE}") 44 | if(BUILD_EXAMPLES) 45 | add_subdirectory(examples) 46 | endif() 47 | endif() 48 | 49 | # ---- Developer mode ---- 50 | 51 | if(NOT tph_poisson_DEVELOPER_MODE) 52 | return() 53 | elseif(NOT PROJECT_IS_TOP_LEVEL) 54 | message( 55 | AUTHOR_WARNING 56 | "Developer mode is intended for developers of tph_poisson" 57 | ) 58 | endif() 59 | 60 | include(cmake/dev-mode.cmake) 61 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 14, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "cmake-pedantic", 11 | "hidden": true, 12 | "warnings": { 13 | "dev": true, 14 | "deprecated": true, 15 | "uninitialized": true, 16 | "unusedCli": true, 17 | "systemVars": false 18 | }, 19 | "errors": { 20 | "dev": true, 21 | "deprecated": true 22 | } 23 | }, 24 | { 25 | "name": "dev-mode", 26 | "hidden": true, 27 | "inherits": "cmake-pedantic", 28 | "cacheVariables": { 29 | "tph_poisson_DEVELOPER_MODE": "ON" 30 | } 31 | }, 32 | { 33 | "name": "cppcheck", 34 | "hidden": true, 35 | "cacheVariables": { 36 | "CMAKE_C_CPPCHECK": "cppcheck;--inline-suppr", 37 | "CMAKE_CXX_CPPCHECK": "cppcheck;--inline-suppr" 38 | } 39 | }, 40 | { 41 | "name": "clang-tidy", 42 | "hidden": true, 43 | "cacheVariables": { 44 | "CMAKE_C_CLANG_TIDY": "clang-tidy;--header-filter=^${sourceDir}/", 45 | "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=^${sourceDir}/" 46 | } 47 | }, 48 | { 49 | "name": "ci-std", 50 | "description": "This preset makes sure the project actually builds with at least the specified standard", 51 | "hidden": true, 52 | "cacheVariables": { 53 | "CMAKE_C_EXTENSIONS": "OFF", 54 | "CMAKE_C_STANDARD": "11", 55 | "CMAKE_C_STANDARD_REQUIRED": "ON", 56 | "CMAKE_CXX_EXTENSIONS": "OFF", 57 | "CMAKE_CXX_STANDARD": "17", 58 | "CMAKE_CXX_STANDARD_REQUIRED": "ON" 59 | } 60 | }, 61 | { 62 | "name": "flags-gcc-clang", 63 | "description": "These flags are supported by both GCC and Clang. Warnings are treated as errors.", 64 | "hidden": true, 65 | "cacheVariables": { 66 | "CMAKE_C_FLAGS": "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fcf-protection=full -fstack-clash-protection -Wall -Wextra -Wpedantic -Werror -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wfloat-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers", 67 | "CMAKE_CXX_FLAGS": "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fcf-protection=full -fstack-clash-protection -Wall -Wextra -Wpedantic -Werror -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wfloat-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wwrite-strings -Wno-missing-field-initializers", 68 | "CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen", 69 | "CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen" 70 | } 71 | }, 72 | { 73 | "name": "flags-appleclang", 74 | "description": "Warnings are treated as errors.", 75 | "hidden": true, 76 | "cacheVariables": { 77 | "CMAKE_C_FLAGS": "-fstack-protector-strong -Wall -Wextra -Wpedantic -Werror -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wfloat-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wstrict-prototypes -Wwrite-strings", 78 | "CMAKE_CXX_FLAGS": "-fstack-protector-strong -Wall -Wextra -Wpedantic -Werror -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wfloat-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wwrite-strings" 79 | } 80 | }, 81 | { 82 | "name": "flags-msvc", 83 | "description": "Note that all the flags after /W4 are required for MSVC to conform to the language standard. Warnings are treated as errors.", 84 | "hidden": true, 85 | "cacheVariables": { 86 | "CMAKE_C_FLAGS": "/sdl /guard:cf /utf-8 /diagnostics:caret /w14165 /w44242 /w44254 /w34287 /w44296 /w44365 /w44388 /w44464 /w14545 /w14546 /w14547 /w14549 /w14555 /w34619 /w44774 /w44777 /w24826 /w14905 /w14906 /w14928 /WX /W4 /permissive- /volatile:iso /Zc:inline /Zc:preprocessor", 87 | "CMAKE_CXX_FLAGS": "/sdl /guard:cf /utf-8 /diagnostics:caret /w14165 /w44242 /w44254 /w34287 /w44296 /w44365 /w44388 /w44464 /w14545 /w14546 /w14547 /w14549 /w14555 /w34619 /w44774 /w44777 /w24826 /w14905 /w14906 /w14928 /WX /EHsc /W4 /permissive- /volatile:iso /Zc:inline /Zc:preprocessor", 88 | "CMAKE_EXE_LINKER_FLAGS": "/machine:x64 /guard:cf" 89 | } 90 | }, 91 | { 92 | "name": "ci-linux", 93 | "inherits": ["flags-gcc-clang", "ci-std"], 94 | "generator": "Unix Makefiles", 95 | "hidden": true, 96 | "cacheVariables": { 97 | "CMAKE_BUILD_TYPE": "Release" 98 | } 99 | }, 100 | { 101 | "name": "ci-darwin", 102 | "inherits": ["flags-appleclang", "ci-std"], 103 | "generator": "Xcode", 104 | "hidden": true 105 | }, 106 | { 107 | "name": "ci-win64", 108 | "inherits": ["flags-msvc", "ci-std"], 109 | "generator": "Visual Studio 17 2022", 110 | "architecture": "x64", 111 | "hidden": true 112 | }, 113 | { 114 | "name": "coverage-linux", 115 | "binaryDir": "${sourceDir}/build/coverage", 116 | "inherits": "ci-linux", 117 | "hidden": true, 118 | "cacheVariables": { 119 | "ENABLE_COVERAGE": "ON", 120 | "CMAKE_BUILD_TYPE": "Coverage", 121 | "CMAKE_C_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions", 122 | "CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions", 123 | "CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage", 124 | "CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage" 125 | } 126 | }, 127 | { 128 | "name": "ci-coverage", 129 | "inherits": ["coverage-linux", "dev-mode"], 130 | "cacheVariables": { 131 | "COVERAGE_HTML_COMMAND": "" 132 | } 133 | }, 134 | { 135 | "name": "ci-sanitize", 136 | "binaryDir": "${sourceDir}/build/sanitize", 137 | "inherits": ["ci-linux", "dev-mode"], 138 | "cacheVariables": { 139 | "CMAKE_BUILD_TYPE": "Sanitize", 140 | "CMAKE_C_FLAGS_SANITIZE": "-U_FORTIFY_SOURCE -O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common", 141 | "CMAKE_CXX_FLAGS_SANITIZE": "-U_FORTIFY_SOURCE -O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common" 142 | } 143 | }, 144 | { 145 | "name": "ci-build", 146 | "binaryDir": "${sourceDir}/build", 147 | "hidden": true 148 | }, 149 | { 150 | "name": "ci-multi-config", 151 | "description": "Speed up multi-config generators by generating only one configuration instead of the defaults", 152 | "hidden": true, 153 | "cacheVariables": { 154 | "CMAKE_CONFIGURATION_TYPES": "Release" 155 | } 156 | }, 157 | { 158 | "name": "ci-macos", 159 | "inherits": ["ci-build", "ci-darwin", "dev-mode", "ci-multi-config"] 160 | }, 161 | { 162 | "name": "ci-ubuntu", 163 | "inherits": ["ci-build", "ci-linux", "clang-tidy", "cppcheck", "dev-mode"] 164 | }, 165 | { 166 | "name": "ci-windows", 167 | "inherits": ["ci-build", "ci-win64", "dev-mode", "ci-multi-config"] 168 | } 169 | ] 170 | } 171 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | * You will be judged by your contributions first, and your sense of humor second. 4 | * Nobody owes you anything. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 7 | 8 | ## Code of Conduct 9 | 10 | Please see the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) document. 11 | 12 | ## Getting started 13 | 14 | Helpful notes for developers can be found in the [`HACKING.md`](HACKING.md) 15 | document. 16 | 17 | In addition to he above, if you use the presets file as instructed, then you 18 | should NOT check it into source control, just as the CMake documentation 19 | suggests. 20 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Hacking 2 | 3 | Here is some wisdom to help you build and test this project as a developer and potential contributor. 4 | 5 | If you plan to contribute, please read the [CONTRIBUTING](CONTRIBUTING.md) guide. 6 | 7 | ## Developer mode 8 | 9 | Build system targets that are only useful for developers of this project are hidden if the `tph_poisson_DEVELOPER_MODE` option is disabled. Enabling this option makes tests and other developer targets and options available. Not enabling this option means that you are a consumer of this project and thus you have no need for these targets and options. 10 | 11 | Developer mode is always set to `ON` in CI workflows. 12 | 13 | ### Presets 14 | 15 | This project makes use of [presets][1] to simplify the process of configuring the project. As a developer, you are recommended to always have the [latest CMake version][2] installed to make use of the latest Quality-of-Life additions. 16 | 17 | You have a few options to pass `tph_poisson_DEVELOPER_MODE` to the configure command, but this project prefers to use presets. 18 | 19 | As a developer, you should create a `CMakeUserPresets.json` file at the root of the project: 20 | 21 | ```json 22 | { 23 | "version": 2, 24 | "cmakeMinimumRequired": { 25 | "major": 3, 26 | "minor": 14, 27 | "patch": 0 28 | }, 29 | "configurePresets": [ 30 | { 31 | "name": "dev", 32 | "binaryDir": "${sourceDir}/build/dev", 33 | "inherits": ["dev-mode", "ci-"], 34 | "cacheVariables": { 35 | "CMAKE_BUILD_TYPE": "Debug" 36 | } 37 | } 38 | ], 39 | "buildPresets": [ 40 | { 41 | "name": "dev", 42 | "configurePreset": "dev", 43 | "configuration": "Debug" 44 | } 45 | ], 46 | "testPresets": [ 47 | { 48 | "name": "dev", 49 | "configurePreset": "dev", 50 | "configuration": "Debug", 51 | "output": { 52 | "outputOnFailure": true 53 | } 54 | } 55 | ] 56 | } 57 | ``` 58 | 59 | You should replace `` in your newly created presets file with the name of the operating system you have, which may be `win64`, `linux` or `darwin`. You can see what these correspond to in the [`CMakePresets.json`](CMakePresets.json) file. 60 | 61 | `CMakeUserPresets.json` is also the perfect place in which you can put all sorts of things that you would otherwise want to pass to the configure command in the terminal. 62 | 63 | > **Note** 64 | > Some editors are pretty greedy with how they open projects with presets. 65 | > Some just randomly pick a preset and start configuring without your consent, 66 | > which can be confusing. Make sure that your editor configures when you 67 | > actually want it to, for example in CLion you have to make sure only the 68 | > `dev-dev preset` has `Enable profile` ticked in 69 | > `File > Settings... > Build, Execution, Deployment > CMake` and in Visual 70 | > Studio you have to set the option `Never run configure step automatically` 71 | > in `Tools > Options > CMake` **prior to opening the project**, after which 72 | > you can manually configure using `Project > Configure Cache`. 73 | 74 | ### Configure, build and test 75 | 76 | If you followed the above instructions, then you can configure, build and test the project respectively with the following commands from the project root on any operating system with any build system: 77 | 78 | ```sh 79 | cmake --preset=dev 80 | cmake --build --preset=dev 81 | ctest --preset=dev 82 | ``` 83 | 84 | If you are using a compatible editor (e.g. VSCode) or IDE (e.g. CLion, VS), you will also be able to select the above created user presets for automatic integration. 85 | 86 | Please note that both the build and test commands accept a `-j` flag to specify the number of jobs to use, which should ideally be specified to the number of threads your CPU has. You may also want to add that to your preset using the `jobs` property, see the [presets documentation][1] for more details. 87 | 88 | ### Developer mode targets 89 | 90 | These are targets you may invoke using the build command from above, with an additional `-t ` flag: 91 | 92 | #### `coverage` 93 | 94 | Available if `ENABLE_COVERAGE` is enabled. This target processes the output of the previously run tests when built with coverage configuration. The commands this target runs can be found in the `COVERAGE_TRACE_COMMAND` and `COVERAGE_HTML_COMMAND` cache variables. The trace command produces an info file by default, which can be submitted to services with CI integration. The HTML command uses the trace command's output to generate an HTML document to `/coverage_html` by default. 95 | 96 | #### `format-check` and `format-fix` 97 | 98 | These targets run the clang-format tool on the codebase to check errors and to fix them respectively. Customization available using the `FORMAT_PATTERNS` and `FORMAT_COMMAND` cache variables. 99 | 100 | #### `run-examples` 101 | 102 | Runs all the examples created by the `add_example` command. 103 | 104 | [1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html 105 | [2]: https://cmake.org/download/ 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Tommy Hinks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Version](https://img.shields.io/badge/version-0.4.0-blue) 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 3 | 4 | ![CI](https://github.com/thinks/poisson-disk-sampling/actions/workflows/ci.yml/badge.svg?branch=master) 5 | [![codecov](https://codecov.io/github/thinks/poisson-disk-sampling/graph/badge.svg?token=NXIAKWPKAB)](https://codecov.io/github/thinks/poisson-disk-sampling) 6 | 7 | [![Standard](https://img.shields.io/badge/c-11-blue.svg)](https://en.wikipedia.org/wiki/C11_(C_standard_revision)) 8 | [![Standard](https://img.shields.io/badge/c%2B%2B-17-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B17) 9 | 10 | # tph_poisson 11 | This repository contains a [single file](include/thinks/tph_poisson.h), header-only, no-dependencies, C library for generating Poisson disk samplings in arbitrary dimensions. The implementation uses the techniques reported in the paper [Fast Poisson Disk Sampling in Arbitrary Dimensions](http://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf) published by [Rook Bridson](http://www.cs.ubc.ca/~rbridson/) in 2007. 12 | 13 | Main features: 14 | * Custom allocator interface for memory management at run-time. 15 | * Possibility to override libc function at compile-time (using macros). 16 | 17 | ## Usage 18 | 19 | Poisson disk sampling aims to generate a set of points within a bounded region such that no two points are closer than some user-specified radius to each other. Let's consider a simple [example](examples/src/simple.c) written in C. 20 | 21 | ```C 22 | /* C11 */ 23 | 24 | #include /* assert */ 25 | #include /* ptrdiff_t */ 26 | #include /* UINT64_C, etc */ 27 | #include /* printf */ 28 | #include /* EXIT_FAILURE, etc */ 29 | #include /* memset */ 30 | 31 | #define TPH_POISSON_IMPLEMENTATION 32 | #include "thinks/tph_poisson.h" 33 | 34 | static_assert(sizeof(tph_poisson_real) == sizeof(float), ""); 35 | 36 | int main(int argc, char *argv[]) 37 | { 38 | (void)argc; 39 | (void)argv; 40 | 41 | const tph_poisson_real bounds_min[2] = { 42 | (tph_poisson_real)-10, (tph_poisson_real)-10 }; 43 | const tph_poisson_real bounds_max[2] = { 44 | (tph_poisson_real)10, (tph_poisson_real)10 }; 45 | 46 | /* Configure arguments. */ 47 | const tph_poisson_args args = { 48 | .bounds_min = bounds_min, 49 | .bounds_max = bounds_max, 50 | .radius = (tph_poisson_real)3, 51 | .ndims = INT32_C(2), 52 | .max_sample_attempts = UINT32_C(30), 53 | .seed = UINT64_C(1981) }; 54 | 55 | /* Using default allocator (libc malloc). */ 56 | const tph_poisson_allocator *alloc = NULL; 57 | 58 | /* Initialize empty sampling. */ 59 | tph_poisson_sampling sampling; 60 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 61 | 62 | /* Populate sampling with points. */ 63 | const int ret = tph_poisson_create(&args, alloc, &sampling); 64 | if (ret != TPH_POISSON_SUCCESS) { 65 | /* No need to destroy sampling here! */ 66 | printf("Failed creating Poisson sampling! Error code: %d\n", ret); 67 | return EXIT_FAILURE; 68 | } 69 | 70 | /* Retrieve sampling points. */ 71 | const tph_poisson_real *samples = tph_poisson_get_samples(&sampling); 72 | if (samples == NULL) { 73 | /* Shouldn't happen since we check the return value from tph_poisson_create! */ 74 | printf("Bad samples!\n"); 75 | tph_poisson_destroy(&sampling); 76 | return EXIT_FAILURE; 77 | } 78 | 79 | /* Print first and last sample positions. */ 80 | assert(sampling.nsamples >= 2); 81 | printf("\n%s:\n" 82 | "samples[%td] = ( %.3f, %.3f )\n" 83 | "...\n" 84 | "samples[%td] = ( %.3f, %.3f )\n\n", 85 | "simple (C)", 86 | (ptrdiff_t)0, 87 | (double)samples[0], 88 | (double)samples[1], 89 | (ptrdiff_t)(sampling.nsamples - 1), 90 | (double)samples[(sampling.nsamples - 1) * sampling.ndims], 91 | (double)samples[(sampling.nsamples - 1) * sampling.ndims + 1]); 92 | 93 | /* Free memory. */ 94 | tph_poisson_destroy(&sampling); 95 | 96 | return EXIT_SUCCESS; 97 | } 98 | ``` 99 | 100 | When using C++ it is possible to safely manage the memory allocated by the tph_poisson functions ([example](examples/src/simple.cpp)), as illustrated below: 101 | 102 | ```C++ 103 | // C++17 104 | 105 | #include // std::array 106 | #include // assert 107 | #include // UINT64_C, etc 108 | #include // std::printf 109 | #include // std::function 110 | #include // std::unique_ptr 111 | 112 | #define TPH_POISSON_IMPLEMENTATION 113 | #include "thinks/tph_poisson.h" 114 | 115 | static_assert(std::is_same_v); 116 | 117 | int main(int /*argc*/, char * /*argv*/[]) 118 | { 119 | constexpr std::array bounds_min{ 120 | static_cast(-10), static_cast(-10) }; 121 | constexpr std::array bounds_max{ 122 | static_cast(10), static_cast(10) }; 123 | 124 | // Configure arguments. 125 | tph_poisson_args args = {}; 126 | args.radius = static_cast(3); 127 | args.ndims = INT32_C(2); 128 | args.bounds_min = bounds_min.data(); 129 | args.bounds_max = bounds_max.data(); 130 | args.max_sample_attempts = UINT32_C(30); 131 | args.seed = UINT64_C(1981); 132 | 133 | // Using default allocator (libc malloc). 134 | const tph_poisson_allocator *alloc = NULL; 135 | 136 | // Initialize empty sampling. 137 | using unique_poisson_ptr = 138 | std::unique_ptr>; 139 | auto sampling = unique_poisson_ptr{ new tph_poisson_sampling{}, [](tph_poisson_sampling *s) { 140 | tph_poisson_destroy(s); 141 | delete s; 142 | } }; 143 | 144 | // Populate sampling with points. 145 | if (const int ret = tph_poisson_create(&args, alloc, sampling.get()); 146 | ret != TPH_POISSON_SUCCESS) { 147 | std::printf("Failed creating Poisson sampling! Error code: %d\n", ret); 148 | return EXIT_FAILURE; 149 | }; 150 | 151 | // Retrieve sampling points. 152 | const tph_poisson_real *samples = tph_poisson_get_samples(sampling.get()); 153 | if (samples == nullptr) { 154 | // Shouldn't happen since we check the return value from tph_poisson_create! 155 | std::printf("Bad samples!\n"); 156 | return EXIT_FAILURE; 157 | } 158 | 159 | // Print first and last sample positions. 160 | assert(sampling->nsamples >= 2); 161 | std::printf("\n%s:\n" 162 | "samples[%td] = ( %.3f, %.3f )\n" 163 | "...\n" 164 | "samples[%td] = ( %.3f, %.3f )\n\n", 165 | "simple (Cpp)", 166 | static_cast(0), 167 | static_cast(samples[0]), 168 | static_cast(samples[1]), 169 | static_cast(sampling->nsamples - 1), 170 | static_cast(samples[(sampling->nsamples - 1) * sampling->ndims]), 171 | static_cast(samples[(sampling->nsamples - 1) * sampling->ndims + 1])); 172 | 173 | // tph_poisson_destroy is called by unique_poisson_ptr destructor. 174 | 175 | return EXIT_SUCCESS; 176 | } 177 | ``` 178 | 179 | The code snippets above generate sets of points in the 2D (`ndims`) range [-10, 10] (`bounds_min` / `bounds_max`) separated by a distance (`radius`) of 3 units. The image below visualizes the results (generated using a simple [Python script](python/poisson_plot.py)). On the right-hand side the radius has been plotted to illustrate the distance separating the points. Here it is "clear" that each circle contains only a single point. 180 | 181 | ![Simple example](images/simple_example.png "Simple example") 182 | 183 | Besides radius and bounds, there are two additional arguments: `seed` and `max_sample_attempts`. The `seed` parameter is used to deterministically generate pseudo-random numbers. Changing the seed gives slightly different patterns. The `max_sample_attempts` controls the number of attempts that are made at finding neighboring points for each sample. Increasing this number typically leads to a more tightly packed sampling, at the cost of additional computation time. The images below illustrate the effects of varying `seed` and `max_sample_attempts`. 184 | 185 | ![Seed and attempts](images/seed_and_attempts.png "Seed and attempts") 186 | 187 | ## Periodogram 188 | 189 | Poisson disk sampling generates samples from a blue noise distribution. We can verify this by plotting the corresponding [periodogram](https://en.wikipedia.org/wiki/Periodogram), noticing that there are minimal low frequency components (close to the center) and no concentrated spikes in energy. 190 | 191 | The image below was generated using the provided [periodogram example](examples/src/periodogram.c) and is an average over 100 sampling patterns (original pixel resolution was 2048x2048). 192 | 193 | ![Average periodogram](images/tph_poisson_periodogram_512.png "Average periodogram") 194 | 195 | # Building and installing 196 | 197 | See the [BUILDING](BUILDING.md) document. 198 | 199 | # Contributing 200 | 201 | See the [CONTRIBUTING](CONTRIBUTING.md) document. 202 | 203 | # Licensing 204 | 205 | All code in this repository is released under the [MIT license](https://en.wikipedia.org/wiki/MIT_License). 206 | -------------------------------------------------------------------------------- /cmake/coverage.cmake: -------------------------------------------------------------------------------- 1 | # ---- Variables ---- 2 | 3 | # We use variables separate from what CTest uses, because those have 4 | # customization issues. 5 | set( 6 | COVERAGE_TRACE_COMMAND 7 | lcov -c -q 8 | -o "${PROJECT_BINARY_DIR}/coverage.info" 9 | -d "${PROJECT_BINARY_DIR}" 10 | --include "${PROJECT_SOURCE_DIR}/*" 11 | CACHE STRING 12 | "; separated command to generate a trace for the 'coverage' target" 13 | ) 14 | 15 | set( 16 | COVERAGE_HTML_COMMAND 17 | genhtml --legend -f -q 18 | "${PROJECT_BINARY_DIR}/coverage.info" 19 | -p "${PROJECT_SOURCE_DIR}" 20 | -o "${PROJECT_BINARY_DIR}/coverage_html" 21 | CACHE STRING 22 | "; separated command to generate an HTML report for the 'coverage' target" 23 | ) 24 | 25 | # ---- Coverage target ---- 26 | 27 | add_custom_target( 28 | coverage 29 | COMMAND ${COVERAGE_TRACE_COMMAND} 30 | COMMAND ${COVERAGE_HTML_COMMAND} 31 | COMMENT "Generating coverage report" 32 | VERBATIM 33 | ) 34 | -------------------------------------------------------------------------------- /cmake/dev-mode.cmake: -------------------------------------------------------------------------------- 1 | include(cmake/folders.cmake) 2 | 3 | include(CTest) 4 | if(BUILD_TESTING) 5 | add_subdirectory(test) 6 | endif() 7 | 8 | option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF) 9 | if(ENABLE_COVERAGE) 10 | include(cmake/coverage.cmake) 11 | endif() 12 | 13 | include(cmake/lint-targets.cmake) 14 | 15 | add_folders(Project) 16 | -------------------------------------------------------------------------------- /cmake/fetch-fftw.cmake: -------------------------------------------------------------------------------- 1 | # ---- Fetch and install FFTW ---- 2 | 3 | cmake_policy(PUSH) 4 | 5 | if(POLICY CMP0169) 6 | # Allow calling FetchContent_Populate directly. 7 | cmake_policy(SET CMP0169 OLD) 8 | endif() 9 | if(POLICY CMP0135) 10 | # Set the timestamps of extracted contents to the time of extraction. 11 | cmake_policy(SET CMP0135 NEW) 12 | endif() 13 | 14 | include(FetchContent) 15 | 16 | function(fetch_fftw) 17 | set(options) 18 | set(oneValueArgs VERSION) 19 | set(multiValueArgs) 20 | cmake_parse_arguments(args 21 | "${options}" 22 | "${oneValueArgs}" 23 | "${multiValueArgs}" 24 | ${ARGN} 25 | ) 26 | 27 | FetchContent_Declare(fftw 28 | URL "http://fftw.org/fftw-${args_VERSION}.tar.gz" 29 | ) 30 | 31 | FetchContent_GetProperties(fftw) 32 | if(NOT fftw_POPULATED) 33 | FetchContent_Populate(fftw) 34 | 35 | set(fftw_CACHE_ARGS 36 | "-DCMAKE_INSTALL_PREFIX=${fftw_BINARY_DIR}/install" 37 | "-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON" # PIC 38 | 39 | "-DBUILD_SHARED_LIBS:BOOL=OFF" # static libs 40 | "-DBUILD_TESTS:BOOL=OFF" # no tests 41 | 42 | "-DENABLE_THREADS:BOOL=ON" # Use pthread 43 | "-DWITH_COMBINED_THREADS:BOOL=ON" # Don't need to link in fftw3f_threads 44 | 45 | # "-DENABLE_FLOAT:BOOL=ON" # 46 | 47 | # Use SSE, but not AVX. 48 | "-DENABLE_SSE:BOOL=ON" 49 | "-DENABLE_SSE2:BOOL=ON" 50 | "-DENABLE_AVX:BOOL=OFF" 51 | "-DENABLE_AVX2:BOOL=OFF" 52 | 53 | "-DDISABLE_FORTRAN:BOOL=ON" 54 | ) 55 | 56 | if(CMAKE_TOOLCHAIN_FILE) 57 | list(APPEND fftw_CACHE_ARGS 58 | "-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_TOOLCHAIN_FILE}" 59 | ) 60 | else() 61 | list(APPEND fftw_CACHE_ARGS 62 | "-DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}" 63 | ) 64 | endif() 65 | 66 | get_property(isMulti GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 67 | if(NOT isMulti) 68 | list(APPEND fftw_CACHE_ARGS "-DCMAKE_BUILD_TYPE:STRING=Release") 69 | endif() 70 | unset(isMulti) 71 | 72 | set(fftw_GENERATOR_ARGS "") 73 | if(CMAKE_GENERATOR_PLATFORM) 74 | list(APPEND fftw_GENERATOR_ARGS 75 | --build-generator-platform "${CMAKE_GENERATOR_PLATFORM}" 76 | ) 77 | endif() 78 | if(CMAKE_GENERATOR_TOOLSET) 79 | list(APPEND fftw_GENERATOR_ARGS 80 | --build-generator-toolset "${CMAKE_GENERATOR_TOOLSET}" 81 | ) 82 | endif() 83 | 84 | message(STATUS "Configuring and building FFTW-${args_VERSION} immediately") 85 | if (MSVC) 86 | set(generator "Visual Studio 17 2022") 87 | else() 88 | set(generator "Unix Makefiles") 89 | endif() 90 | 91 | execute_process( 92 | COMMAND ${CMAKE_CTEST_COMMAND} 93 | --build-and-test ${fftw_SOURCE_DIR} ${fftw_BINARY_DIR} 94 | --build-generator ${generator} ${fftw_GENERATOR_ARGS} 95 | --build-target install 96 | --build-noclean 97 | --build-options ${fftw_CACHE_ARGS} 98 | WORKING_DIRECTORY ${fftw_SOURCE_DIR} 99 | OUTPUT_FILE ${fftw_BINARY_DIR}/build_output.log 100 | ERROR_FILE ${fftw_BINARY_DIR}/build_output.log 101 | RESULT_VARIABLE result 102 | ) 103 | 104 | unset(generator) 105 | 106 | if(result) 107 | file(READ ${fftw_BINARY_DIR}/build_output.log build_log) 108 | message(FATAL_ERROR "Result = ${result}\nFailed FFTW-${args_VERSION} build, see build log:\n" 109 | "${build_log}") 110 | unset(build_log) 111 | endif() 112 | unset(result) 113 | message(STATUS "FFTW-${args_VERSION} build complete") 114 | endif() # fftw_POPULATED 115 | 116 | # Confirm that we can find FFTW. 117 | 118 | # Ugly work-around for CMake errors in CI builds. 119 | set(_cmake_import_check_xcframework_for_FFTW3::fftw3 "") 120 | 121 | find_package(FFTW3 122 | QUIET 123 | REQUIRED 124 | CONFIG 125 | PATHS "${fftw_BINARY_DIR}/install" 126 | NO_DEFAULT_PATH 127 | ) 128 | 129 | unset(_cmake_import_check_xcframework_for_FFTW3::fftw3) 130 | 131 | if(NOT FFTW3_FOUND) 132 | message(FATAL_ERROR "FFTW-${args_VERSION} not found") 133 | endif() 134 | endfunction() 135 | 136 | cmake_policy(POP) 137 | -------------------------------------------------------------------------------- /cmake/fetch-nlohmann_json.cmake: -------------------------------------------------------------------------------- 1 | # ---- Fetch and install nlohmann_json ---- 2 | 3 | cmake_policy(PUSH) 4 | 5 | if(POLICY CMP0169) 6 | # Allow calling FetchContent_Populate directly. 7 | cmake_policy(SET CMP0169 OLD) 8 | endif() 9 | if(POLICY CMP0135) 10 | # Set the timestamps of extracted contents to the time of extraction. 11 | cmake_policy(SET CMP0135 NEW) 12 | endif() 13 | 14 | include(FetchContent) 15 | 16 | function(fetch_nlohmann_json) 17 | set(options) 18 | set(oneValueArgs VERSION) 19 | set(multiValueArgs) 20 | cmake_parse_arguments(args 21 | "${options}" 22 | "${oneValueArgs}" 23 | "${multiValueArgs}" 24 | ${ARGN} 25 | ) 26 | 27 | FetchContent_Declare(nlohmann_json 28 | URL "https://github.com/nlohmann/json/releases/download/v${args_VERSION}/json.tar.xz" 29 | ) 30 | 31 | FetchContent_GetProperties(nlohmann_json) 32 | if(NOT nlohmann_json_POPULATED) 33 | FetchContent_Populate(nlohmann_json) 34 | 35 | set(nlohmann_json_CACHE_ARGS 36 | "-DCMAKE_INSTALL_PREFIX:STRING=${nlohmann_json_BINARY_DIR}/install" 37 | "-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON" 38 | "-DJSON_BuildTests:BOOL=OFF" 39 | ) 40 | 41 | set(nlohmann_json_GENERATOR_ARGS "") 42 | if(CMAKE_GENERATOR_PLATFORM) 43 | list(APPEND nlohmann_json_GENERATOR_ARGS 44 | --build-generator-platform "${CMAKE_GENERATOR_PLATFORM}" 45 | ) 46 | endif() 47 | 48 | message(STATUS "Configuring and building nlohmann_json-${args_VERSION} immediately") 49 | execute_process( 50 | COMMAND ${CMAKE_CTEST_COMMAND} 51 | --build-and-test ${nlohmann_json_SOURCE_DIR} ${nlohmann_json_BINARY_DIR} 52 | --build-generator ${CMAKE_GENERATOR} ${nlohmann_json_GENERATOR_ARGS} 53 | --build-target install 54 | --build-noclean 55 | --build-options ${nlohmann_json_CACHE_ARGS} 56 | WORKING_DIRECTORY ${nlohmann_json_SOURCE_DIR} 57 | OUTPUT_FILE ${nlohmann_json_BINARY_DIR}/build_output.log 58 | ERROR_FILE ${nlohmann_json_BINARY_DIR}/build_output.log 59 | RESULT_VARIABLE result 60 | ) 61 | if(result) 62 | file(READ ${nlohmann_json_BINARY_DIR}/build_output.log build_log) 63 | message(FATAL_ERROR "Result = ${result}\nFailed nlohmann_json-${args_VERSION} build, see build log:\n" 64 | "${build_log}") 65 | unset(build_log) 66 | endif() 67 | unset(result) 68 | message(STATUS "nlohmann_json-${args_VERSION} build complete") 69 | endif() 70 | 71 | # Confirm that we can find nlohmann_json. 72 | find_package(nlohmann_json 73 | QUIET 74 | REQUIRED 75 | CONFIG 76 | PATHS "${nlohmann_json_BINARY_DIR}/install" 77 | NO_DEFAULT_PATH 78 | ) 79 | if(NOT nlohmann_json_FOUND) 80 | message(FATAL_ERROR "nlohmann_json-${args_VERSION} not found") 81 | endif() 82 | endfunction() 83 | 84 | cmake_policy(POP) 85 | -------------------------------------------------------------------------------- /cmake/fetch-stb.cmake: -------------------------------------------------------------------------------- 1 | # ---- Fetch and install STB ---- 2 | 3 | cmake_policy(PUSH) 4 | 5 | if(POLICY CMP0169) 6 | # Allow calling FetchContent_Populate directly. 7 | cmake_policy(SET CMP0169 OLD) 8 | endif() 9 | 10 | include(FetchContent) 11 | 12 | function(fetch_stb) 13 | set(options) 14 | set(oneValueArgs) 15 | set(multiValueArgs) 16 | cmake_parse_arguments(args 17 | "${options}" 18 | "${oneValueArgs}" 19 | "${multiValueArgs}" 20 | ${ARGN} 21 | ) 22 | 23 | FetchContent_Declare(nothings_stb 24 | GIT_REPOSITORY https://github.com/nothings/stb.git 25 | GIT_TAG 5c205738c191bcb0abc65c4febfa9bd25ff35234 # master 26 | GIT_SHALLOW TRUE 27 | GIT_PROGRESS TRUE 28 | ) 29 | 30 | FetchContent_GetProperties(nothings_stb) 31 | if(NOT nothings_stb_POPULATED) 32 | FetchContent_Populate(nothings_stb) 33 | 34 | message(STATUS "Configuring and building stb immediately") 35 | message(STATUS "stb build complete") 36 | endif() # nothings_stb_POPULATED 37 | 38 | add_library(nothings::stb INTERFACE IMPORTED) 39 | 40 | set_target_properties(nothings::stb PROPERTIES 41 | INTERFACE_INCLUDE_DIRECTORIES "${nothings_stb_SOURCE_DIR}" 42 | ) 43 | 44 | endfunction() 45 | 46 | cmake_policy(POP) 47 | -------------------------------------------------------------------------------- /cmake/folders.cmake: -------------------------------------------------------------------------------- 1 | set_property(GLOBAL PROPERTY USE_FOLDERS YES) 2 | 3 | # Call this function at the end of a directory scope to assign a folder to 4 | # targets created in that directory. Utility targets will be assigned to the 5 | # UtilityTargets folder, otherwise to the ${name}Targets folder. If a target 6 | # already has a folder assigned, then that target will be skipped. 7 | function(add_folders name) 8 | get_property(targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS) 9 | foreach(target IN LISTS targets) 10 | get_property(folder TARGET "${target}" PROPERTY FOLDER) 11 | if(DEFINED folder) 12 | continue() 13 | endif() 14 | set(folder Utility) 15 | get_property(type TARGET "${target}" PROPERTY TYPE) 16 | if(NOT type STREQUAL "UTILITY") 17 | set(folder "${name}") 18 | endif() 19 | set_property(TARGET "${target}" PROPERTY FOLDER "${folder}Targets") 20 | endforeach() 21 | endfunction() 22 | -------------------------------------------------------------------------------- /cmake/install-config.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/tph_poissonTargets.cmake") 2 | -------------------------------------------------------------------------------- /cmake/install-rules.cmake: -------------------------------------------------------------------------------- 1 | if(PROJECT_IS_TOP_LEVEL) 2 | set( 3 | CMAKE_INSTALL_INCLUDEDIR "include/tph_poisson-${PROJECT_VERSION}" 4 | CACHE STRING "" 5 | ) 6 | set_property(CACHE CMAKE_INSTALL_INCLUDEDIR PROPERTY TYPE PATH) 7 | endif() 8 | 9 | # Project is configured with no languages, so tell GNUInstallDirs the lib dir 10 | set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "") 11 | 12 | include(CMakePackageConfigHelpers) 13 | include(GNUInstallDirs) 14 | 15 | # find_package() call for consumers to find this project 16 | set(package tph_poisson) 17 | 18 | install( 19 | DIRECTORY include/ 20 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 21 | COMPONENT tph_poisson_Development 22 | ) 23 | 24 | install( 25 | TARGETS thinks_tph_poisson 26 | EXPORT tph_poissonTargets 27 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 28 | ) 29 | 30 | write_basic_package_version_file( 31 | "${package}ConfigVersion.cmake" 32 | COMPATIBILITY SameMinorVersion 33 | ARCH_INDEPENDENT 34 | ) 35 | 36 | # Allow package maintainers to freely override the path for the configs 37 | set( 38 | tph_poisson_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}" 39 | CACHE STRING "CMake package config location relative to the install prefix" 40 | ) 41 | set_property(CACHE tph_poisson_INSTALL_CMAKEDIR PROPERTY TYPE PATH) 42 | mark_as_advanced(tph_poisson_INSTALL_CMAKEDIR) 43 | 44 | install( 45 | FILES cmake/install-config.cmake 46 | DESTINATION "${tph_poisson_INSTALL_CMAKEDIR}" 47 | RENAME "${package}Config.cmake" 48 | COMPONENT tph_poisson_Development 49 | ) 50 | 51 | install( 52 | FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake" 53 | DESTINATION "${tph_poisson_INSTALL_CMAKEDIR}" 54 | COMPONENT tph_poisson_Development 55 | ) 56 | 57 | install( 58 | EXPORT tph_poissonTargets 59 | NAMESPACE thinks:: 60 | DESTINATION "${tph_poisson_INSTALL_CMAKEDIR}" 61 | COMPONENT tph_poisson_Development 62 | ) 63 | 64 | if(PROJECT_IS_TOP_LEVEL) 65 | include(CPack) 66 | endif() 67 | -------------------------------------------------------------------------------- /cmake/lint-targets.cmake: -------------------------------------------------------------------------------- 1 | set( 2 | FORMAT_PATTERNS 3 | include/*.h 4 | test/*.c test/*.cpp test/*.h 5 | examples/*.c examples/*.cpp examples/*.h 6 | CACHE STRING 7 | "; separated patterns relative to the project source dir to format" 8 | ) 9 | 10 | set(FORMAT_COMMAND clang-format CACHE STRING "Formatter to use") 11 | 12 | add_custom_target( 13 | format-check 14 | COMMAND "${CMAKE_COMMAND}" 15 | -D "FORMAT_COMMAND=${FORMAT_COMMAND}" 16 | -D "PATTERNS=${FORMAT_PATTERNS}" 17 | -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 18 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 19 | COMMENT "Linting the code" 20 | VERBATIM 21 | ) 22 | 23 | add_custom_target( 24 | format-fix 25 | COMMAND "${CMAKE_COMMAND}" 26 | -D "FORMAT_COMMAND=${FORMAT_COMMAND}" 27 | -D "PATTERNS=${FORMAT_PATTERNS}" 28 | -D FIX=YES 29 | -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 30 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 31 | COMMENT "Fixing the code" 32 | VERBATIM 33 | ) 34 | -------------------------------------------------------------------------------- /cmake/lint.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | macro(default name) 4 | if(NOT DEFINED "${name}") 5 | set("${name}" "${ARGN}") 6 | endif() 7 | endmacro() 8 | 9 | default(FORMAT_COMMAND clang-format) 10 | default( 11 | PATTERNS 12 | include/*.h 13 | test/*.c test/*.cpp test/*.h 14 | examples/*.c examples/*.cpp examples/*.h 15 | ) 16 | default(FIX NO) 17 | 18 | set(flag --output-replacements-xml) 19 | set(args OUTPUT_VARIABLE output) 20 | if(FIX) 21 | set(flag -i) 22 | set(args "") 23 | endif() 24 | 25 | file(GLOB_RECURSE files ${PATTERNS}) 26 | set(badly_formatted "") 27 | set(output "") 28 | string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length) 29 | 30 | foreach(file IN LISTS files) 31 | execute_process( 32 | COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}" 33 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 34 | RESULT_VARIABLE result 35 | ${args} 36 | ) 37 | if(NOT result EQUAL "0") 38 | message(FATAL_ERROR "'${file}': formatter returned with ${result}") 39 | endif() 40 | if(NOT FIX AND output MATCHES "\n /* assert */ 2 | #include /* ptrdiff_t */ 3 | #include /* UINT64_C, etc */ 4 | #include /* printf */ 5 | #include /* EXIT_FAILURE, etc */ 6 | #include /* memset */ 7 | 8 | #define TPH_POISSON_IMPLEMENTATION 9 | #include "thinks/tph_poisson.h" 10 | 11 | static_assert(sizeof(tph_poisson_real) == 4, ""); 12 | 13 | typedef struct my_alloc_ctx_ 14 | { 15 | ptrdiff_t total_malloc; 16 | ptrdiff_t total_free; 17 | } my_alloc_ctx; 18 | 19 | static void *my_alloc_malloc(ptrdiff_t size, void *ctx) 20 | { 21 | my_alloc_ctx *a_ctx = (my_alloc_ctx *)ctx; 22 | if (size == 0) { return NULL; } 23 | void *ptr = malloc((size_t)(size)); 24 | a_ctx->total_malloc += size; 25 | return ptr; 26 | } 27 | 28 | static void my_alloc_free(void *ptr, ptrdiff_t size, void *ctx) 29 | { 30 | my_alloc_ctx *a_ctx = (my_alloc_ctx *)ctx; 31 | if (ptr == NULL) { return; } 32 | a_ctx->total_free += size; 33 | free(ptr); 34 | } 35 | 36 | int main(int argc, char *argv[]) 37 | { 38 | (void)argc; 39 | (void)argv; 40 | 41 | /* clang-format off */ 42 | const tph_poisson_real bounds_min[2] = { 43 | (tph_poisson_real)-10, (tph_poisson_real)-10 }; 44 | const tph_poisson_real bounds_max[2] = { 45 | (tph_poisson_real)10, (tph_poisson_real)10 }; 46 | /* clang-format on */ 47 | 48 | /* Configure arguments. */ 49 | const tph_poisson_args args = { .bounds_min = bounds_min, 50 | .bounds_max = bounds_max, 51 | .radius = (tph_poisson_real)3, 52 | .ndims = INT32_C(2), 53 | .max_sample_attempts = UINT32_C(30), 54 | .seed = UINT64_C(1981) }; 55 | 56 | /* Using custom allocator. */ 57 | my_alloc_ctx alloc_ctx = { .total_malloc = 0, .total_free = 0 }; 58 | tph_poisson_allocator alloc = { 59 | .malloc = my_alloc_malloc, .free = my_alloc_free, .ctx = &alloc_ctx 60 | }; 61 | 62 | /* Initialize empty sampling. */ 63 | tph_poisson_sampling sampling; 64 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 65 | 66 | /* Populate sampling with points. */ 67 | const int ret = tph_poisson_create(&args, &alloc, &sampling); 68 | if (ret != TPH_POISSON_SUCCESS) { 69 | /* No need to destroy sampling here! */ 70 | printf("Failed creating Poisson sampling! Error code: %d\n", ret); 71 | return EXIT_FAILURE; 72 | } 73 | 74 | /* Retrieve sampling points. */ 75 | const tph_poisson_real *samples = tph_poisson_get_samples(&sampling); 76 | if (samples == NULL) { 77 | /* Shouldn't happen since we check the return value from tph_poisson_create! */ 78 | printf("Bad samples!\n"); 79 | tph_poisson_destroy(&sampling); 80 | return EXIT_FAILURE; 81 | } 82 | 83 | /* Print first and last sample positions. */ 84 | /* clang-format off */ 85 | assert(sampling.nsamples >= 2); 86 | printf("\n%s:\n" 87 | "samples[%td] = ( %.3f, %.3f )\n" 88 | "...\n" 89 | "samples[%td] = ( %.3f, %.3f )\n\n", 90 | "custom_alloc", 91 | (ptrdiff_t)0, 92 | (double)samples[0], 93 | (double)samples[1], 94 | (ptrdiff_t)(sampling.nsamples - 1), 95 | (double)samples[(sampling.nsamples - 1) * sampling.ndims], 96 | (double)samples[(sampling.nsamples - 1) * sampling.ndims + 1]); 97 | /* clang-format on */ 98 | 99 | /* Free memory. */ 100 | tph_poisson_destroy(&sampling); 101 | 102 | printf( 103 | "total malloc = %td\n" 104 | "total free = %td\n", 105 | alloc_ctx.total_malloc, 106 | alloc_ctx.total_free); 107 | 108 | return EXIT_SUCCESS; 109 | } 110 | -------------------------------------------------------------------------------- /examples/src/custom_libc.c: -------------------------------------------------------------------------------- 1 | #include /* assert */ 2 | #include /* ptrdiff_t */ 3 | #include /* UINT64_C, etc */ 4 | #include /* printf */ 5 | #include /* EXIT_FAILURE, etc */ 6 | #include /* memset */ 7 | 8 | static void *my_malloc(size_t size); 9 | static void my_free(void *ptr); 10 | static void *my_memcpy(void *dest, const void *src, size_t count); 11 | static void *my_memset(void *dest, int ch, size_t count); 12 | 13 | /* Provide custom functions for both malloc and free. Note that this 14 | * is quite different from using a custom allocator, since it is not 15 | * really possible to use an allocation context with this approach. */ 16 | #define TPH_POISSON_MALLOC my_malloc 17 | #define TPH_POISSON_FREE my_free 18 | #define TPH_POISSON_MEMCPY my_memcpy 19 | #define TPH_POISSON_MEMSET my_memset 20 | #define TPH_POISSON_IMPLEMENTATION 21 | #include "thinks/tph_poisson.h" 22 | 23 | static_assert(sizeof(tph_poisson_real) == 4, ""); 24 | 25 | static void *my_malloc(size_t size) 26 | { 27 | static int call_count = 0; 28 | void *ptr = malloc(size); 29 | printf("%d - my_malloc(%zu) -> %p\n", call_count++, size, ptr); 30 | return ptr; 31 | } 32 | 33 | static void my_free(void *ptr) 34 | { 35 | static int call_count = 0; 36 | printf("%d - my_free(%p)\n", call_count++, ptr); 37 | free(ptr); 38 | } 39 | 40 | static int memcpy_calls = 0; 41 | static int memset_calls = 0; 42 | 43 | static void *my_memcpy(void *dest, const void *src, size_t count) 44 | { 45 | ++memcpy_calls; 46 | return memcpy(dest, src, count); 47 | } 48 | 49 | static void *my_memset(void *dest, int ch, size_t count) 50 | { 51 | ++memset_calls; 52 | return memset(dest, ch, count); 53 | } 54 | 55 | int main(int argc, char *argv[]) 56 | { 57 | (void)argc; 58 | (void)argv; 59 | 60 | /* clang-format off */ 61 | const tph_poisson_real bounds_min[2] = { 62 | (tph_poisson_real)-10, (tph_poisson_real)-10 }; 63 | const tph_poisson_real bounds_max[2] = { 64 | (tph_poisson_real)10, (tph_poisson_real)10 }; 65 | /* clang-format on */ 66 | 67 | /* Configure arguments. */ 68 | const tph_poisson_args args = { .bounds_min = bounds_min, 69 | .bounds_max = bounds_max, 70 | .radius = (tph_poisson_real)1.47, 71 | .ndims = INT32_C(2), 72 | .max_sample_attempts = UINT32_C(40), 73 | .seed = UINT64_C(2017) }; 74 | 75 | /* Using default allocator (libc malloc). */ 76 | const tph_poisson_allocator *alloc = NULL; 77 | 78 | /* Initialize empty sampling. */ 79 | tph_poisson_sampling sampling; 80 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 81 | 82 | /* Populate sampling with points. */ 83 | const int ret = tph_poisson_create(&args, alloc, &sampling); 84 | if (ret != TPH_POISSON_SUCCESS) { 85 | /* No need to destroy sampling here! */ 86 | printf("tph_poisson_create error, code: %d\n", ret); 87 | return EXIT_FAILURE; 88 | } 89 | 90 | /* Retrieve samples. */ 91 | const tph_poisson_real *samples = tph_poisson_get_samples(&sampling); 92 | if (samples == NULL) { 93 | /* Shouldn't happen since we check the return value from tph_poisson_create! */ 94 | printf("Bad samples!\n"); 95 | tph_poisson_destroy(&sampling); 96 | return EXIT_FAILURE; 97 | } 98 | 99 | printf( 100 | "memcpy calls = %d\n" 101 | "memset calls = %d\n", 102 | memcpy_calls, 103 | memset_calls); 104 | 105 | /* Print first and last sample positions. */ 106 | /* clang-format off */ 107 | assert(sampling.nsamples >= 2); 108 | printf("\n%s:\n" 109 | "samples[%td] = ( %.3f, %.3f )\n" 110 | "...\n" 111 | "samples[%td] = ( %.3f, %.3f )\n\n", 112 | "custom_libc", 113 | (ptrdiff_t)0, 114 | (double)samples[0], 115 | (double)samples[1], 116 | (ptrdiff_t)(sampling.nsamples - 1), 117 | (double)samples[(sampling.nsamples - 1) * sampling.ndims], 118 | (double)samples[(sampling.nsamples - 1) * sampling.ndims + 1]); 119 | /* clang-format on */ 120 | 121 | /* Free memory. */ 122 | tph_poisson_destroy(&sampling); 123 | 124 | return EXIT_SUCCESS; 125 | } 126 | -------------------------------------------------------------------------------- /examples/src/f64.c: -------------------------------------------------------------------------------- 1 | #include /* assert */ 2 | #include /* sqrt, ceil, floor */ 3 | #include /* ptrdiff_t */ 4 | #include /* UINT64_C, etc */ 5 | #include /* printf */ 6 | #include /* EXIT_FAILURE, etc */ 7 | #include /* memset */ 8 | 9 | #define TPH_POISSON_REAL_TYPE double 10 | #define TPH_POISSON_SQRT sqrt 11 | #define TPH_POISSON_CEIL ceil 12 | #define TPH_POISSON_FLOOR floor 13 | #define TPH_POISSON_IMPLEMENTATION 14 | #include "thinks/tph_poisson.h" 15 | 16 | static_assert(sizeof(tph_poisson_real) == 8, ""); 17 | 18 | int main(int argc, char *argv[]) 19 | { 20 | (void)argc; 21 | (void)argv; 22 | 23 | /* clang-format off */ 24 | const tph_poisson_real bounds_min[2] = { 25 | (tph_poisson_real)-10, (tph_poisson_real)-10 }; 26 | const tph_poisson_real bounds_max[2] = { 27 | (tph_poisson_real)10, (tph_poisson_real)10 }; 28 | /* clang-format on */ 29 | 30 | /* Configure arguments. */ 31 | const tph_poisson_args args = { .bounds_min = bounds_min, 32 | .bounds_max = bounds_max, 33 | .radius = (tph_poisson_real)3, 34 | .ndims = INT32_C(2), 35 | .max_sample_attempts = UINT32_C(30), 36 | .seed = UINT64_C(1981) }; 37 | 38 | /* Using default allocator (libc malloc). */ 39 | const tph_poisson_allocator *alloc = NULL; 40 | 41 | /* Initialize empty sampling. */ 42 | tph_poisson_sampling sampling; 43 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 44 | 45 | /* Populate sampling with points. */ 46 | const int ret = tph_poisson_create(&args, alloc, &sampling); 47 | if (ret != TPH_POISSON_SUCCESS) { 48 | /* No need to destroy sampling here! */ 49 | printf("Failed creating Poisson sampling! Error code: %d\n", ret); 50 | return EXIT_FAILURE; 51 | } 52 | 53 | /* Retrieve sampling points. */ 54 | const tph_poisson_real *samples = tph_poisson_get_samples(&sampling); 55 | if (samples == NULL) { 56 | /* Shouldn't happen since we check the return value from tph_poisson_create! */ 57 | printf("Bad samples!\n"); 58 | tph_poisson_destroy(&sampling); 59 | return EXIT_FAILURE; 60 | } 61 | 62 | /* Print first and last sample positions. */ 63 | /* clang-format off */ 64 | assert(sampling.nsamples >= 2); 65 | printf("\n%s:\n" 66 | "samples[%td] = ( %.3f, %.3f )\n" 67 | "...\n" 68 | "samples[%td] = ( %.3f, %.3f )\n\n", 69 | "f64 (double)", 70 | (ptrdiff_t)0, 71 | (double)samples[0], 72 | (double)samples[1], 73 | (ptrdiff_t)(sampling.nsamples - 1), 74 | (double)samples[(sampling.nsamples - 1) * sampling.ndims], 75 | (double)samples[(sampling.nsamples - 1) * sampling.ndims + 1]); 76 | /* clang-format on */ 77 | 78 | /* Free memory. */ 79 | tph_poisson_destroy(&sampling); 80 | 81 | return EXIT_SUCCESS; 82 | } 83 | -------------------------------------------------------------------------------- /examples/src/json.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // UINT32_C, etc 3 | #include // std::abort, EXIT_FAILURE 4 | #include // std::ofstream 5 | #include // std::function 6 | #include // std::setw 7 | #include // std::unique_ptr 8 | #include // std::is_same_v 9 | #include 10 | 11 | #include 12 | 13 | #define TPH_POISSON_IMPLEMENTATION 14 | #include 15 | 16 | static_assert(std::is_same_v); 17 | 18 | int main(int /*argc*/, char * /*argv*/[]) 19 | { 20 | constexpr std::array bounds_min{ -10, -10 }; 21 | constexpr std::array bounds_max{ 10, 10 }; 22 | tph_poisson_args args = {}; 23 | args.ndims = 2; 24 | args.radius = static_cast(3); 25 | args.bounds_min = bounds_min.data(); 26 | args.bounds_max = bounds_max.data(); 27 | args.max_sample_attempts = UINT32_C(30); 28 | args.seed = UINT64_C(0); 29 | 30 | using unique_poisson_ptr = 31 | std::unique_ptr>; 32 | auto sampling = unique_poisson_ptr{ new tph_poisson_sampling{}, [](tph_poisson_sampling *s) { 33 | tph_poisson_destroy(s); 34 | delete s; 35 | } }; 36 | 37 | if (tph_poisson_create(&args, /*alloc=*/nullptr, sampling.get()) != TPH_POISSON_SUCCESS) { 38 | return EXIT_FAILURE; 39 | } 40 | 41 | const tph_poisson_real *samples = tph_poisson_get_samples(sampling.get()); 42 | if (samples == nullptr) { return EXIT_FAILURE; } 43 | 44 | try { 45 | std::vector> points; 46 | for (ptrdiff_t i = 0; i < sampling->nsamples; ++i) { 47 | points.push_back(std::array{ { 48 | samples[i * sampling->ndims], 49 | samples[i * sampling->ndims + 1], 50 | } }); 51 | } 52 | 53 | using json = nlohmann::json; 54 | json j; 55 | j["bounds_min"] = bounds_min; 56 | j["bounds_max"] = bounds_max; 57 | j["seed"] = args.seed; 58 | j["max_sample_attempts"] = args.max_sample_attempts; 59 | j["radius"] = args.radius; 60 | j["ndims"] = args.ndims; 61 | j["points"] = points; 62 | 63 | std::ofstream ofs{ "./tph_poisson.json" }; 64 | if (!ofs) { return EXIT_FAILURE; } 65 | ofs << std::setw(2) << j; 66 | ofs.close(); 67 | } catch (...) { 68 | std::printf("Unknown exception\n"); 69 | return EXIT_FAILURE; 70 | } 71 | 72 | return EXIT_SUCCESS; 73 | } 74 | -------------------------------------------------------------------------------- /examples/src/periodogram.c: -------------------------------------------------------------------------------- 1 | #include /* sqrt, ceil, floor, round */ 2 | #include /* memset, memcpy */ 3 | 4 | #include 5 | 6 | #define STB_IMAGE_WRITE_IMPLEMENTATION 7 | #include 8 | 9 | #define TPH_POISSON_REAL_TYPE double 10 | #define TPH_POISSON_SQRT sqrt 11 | #define TPH_POISSON_CEIL ceil 12 | #define TPH_POISSON_FLOOR floor 13 | #define TPH_POISSON_IMPLEMENTATION 14 | #include "thinks/tph_poisson.h" 15 | 16 | static_assert(sizeof(tph_poisson_real) == sizeof(double), ""); 17 | 18 | static void fft_shift(const int n0, const int n1, double *inout) 19 | { 20 | /* Simple and slow implementation, not optimized. */ 21 | 22 | const int s0 = n0 / 2; 23 | const int s1 = n1 / 2; 24 | 25 | /* Shift rows. */ 26 | double x = 0.0; /* swap */ 27 | int jj = 0; 28 | for (int j = 0; j < n1; ++j) { 29 | jj = n0 * j; 30 | for (int i = 0; i < s0; ++i) { 31 | x = inout[i + jj]; 32 | inout[i + jj] = inout[i + s0 + jj]; 33 | inout[i + s0 + jj] = x; 34 | } 35 | } 36 | 37 | /* Shift columns. */ 38 | for (int i = 0; i < n0; ++i) { 39 | for (int j = 0; j < s1; ++j) { 40 | jj = n0 * j; 41 | x = inout[i + jj]; 42 | inout[i + jj] = inout[i + n0 * (s1 + j)]; 43 | inout[i + n0 * (s1 + j)] = x; 44 | } 45 | } 46 | } 47 | 48 | static void sampling_image(const tph_poisson_args *args, 49 | const tph_poisson_sampling *s, 50 | const int n0, 51 | const int n1, 52 | fftw_complex *img) 53 | { 54 | /* Reset, start from zero image. Imaginary part will remain zero. */ 55 | const int sz = n0 * n1; 56 | for (int i = 0; i < sz; ++i) { 57 | img[i][0] = 0.0; 58 | img[i][1] = 0.0; 59 | } 60 | 61 | const double *p = tph_poisson_get_samples(s); 62 | if (p == NULL) { abort(); } 63 | 64 | const ptrdiff_t nsamples = s->nsamples; 65 | const ptrdiff_t ndims = s->ndims; 66 | const double x_min = args->bounds_min[0]; 67 | const double y_min = args->bounds_min[1]; 68 | const double x_max = args->bounds_max[0]; 69 | const double y_max = args->bounds_max[1]; 70 | for (ptrdiff_t i = 0; i < nsamples; ++i) { 71 | int ix = (int)floor(((p[i * ndims] - x_min) / (x_max - x_min)) * (double)n0); 72 | ix = ix < 0 ? 0 : ((n0 - 1) < ix ? (n0 - 1) : ix); 73 | 74 | int iy = (int)floor(((p[i * ndims + 1] - y_min) / (y_max - y_min)) * (double)n1); 75 | iy = iy < 0 ? 0 : ((n1 - 1) < iy ? (n1 - 1) : iy); 76 | 77 | img[ix + n0 * iy][0] = 1.0; 78 | } 79 | 80 | /* Subtract average. */ 81 | double avg = 0.0; 82 | for (int i = 0; i < sz; ++i) { avg += img[i][0]; } 83 | avg /= (double)sz; 84 | for (int i = 0; i < sz; ++i) { img[i][0] -= avg; } 85 | } 86 | 87 | static void 88 | accum_periodogram(const double scale, const int n0, const int n1, fftw_complex *in, double *out) 89 | { 90 | const int sz = n0 * n1; 91 | for (int i = 0; i < sz; ++i) { out[i] += scale * (in[i][0] * in[i][0] + in[i][1] * in[i][1]); } 92 | } 93 | 94 | static bool write_png(const char *filename, const int n0, const int n1, const double *data) 95 | { 96 | static const int comp = 1; /* Greyscale. */ 97 | 98 | const ptrdiff_t sz = (ptrdiff_t)n0 * (ptrdiff_t)n1; 99 | double min_val = data[0]; 100 | double max_val = data[0]; 101 | for (ptrdiff_t i = 1; i < sz; ++i) { 102 | if (data[i] < min_val) { min_val = data[i]; } 103 | if (data[i] > max_val) { max_val = data[i]; } 104 | } 105 | 106 | const size_t buf_size = (size_t)sz * sizeof(uint8_t); 107 | uint8_t *buf = (uint8_t *)malloc(buf_size); 108 | memset(buf, 0, buf_size); 109 | 110 | for (ptrdiff_t i = 0; i < sz; ++i) { 111 | const int iv = (int)round(((data[i] - min_val) / (max_val - min_val)) * 255.0); 112 | buf[i] = (uint8_t)(iv < 0 ? 0 : (255 < iv ? 255 : iv)); 113 | } 114 | 115 | const int ret = stbi_write_png(filename, n0, n1, comp, buf, n0); 116 | free(buf); 117 | 118 | return ret != 0; 119 | } 120 | 121 | int main(int argc, char *argv[]) 122 | { 123 | (void)argc; 124 | (void)argv; 125 | 126 | /* Periodogram settings. */ 127 | const int image_count = 100; 128 | const int n0 = 2048; 129 | const int n1 = 2048; 130 | 131 | /* clang-format off */ 132 | const tph_poisson_real bounds_min[2] = { 133 | (tph_poisson_real)0, (tph_poisson_real)0 }; 134 | const tph_poisson_real bounds_max[2] = { 135 | (tph_poisson_real)128, (tph_poisson_real)128 }; 136 | /* clang-format on */ 137 | 138 | /* Configure tph_poisson arguments. Set varying seed later. */ 139 | tph_poisson_args args = { .bounds_min = bounds_min, 140 | .bounds_max = bounds_max, 141 | .radius = (tph_poisson_real)1, 142 | .ndims = INT32_C(2), 143 | .max_sample_attempts = UINT32_C(30), 144 | .seed = UINT64_C(1981) }; 145 | 146 | /* Initialize empty tph_poisson sampling. */ 147 | tph_poisson_sampling sampling; 148 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 149 | 150 | /* Initlialize buffers used to accumulate the average periodogram. */ 151 | double *periodogram = (double *)malloc((size_t)n0 * (size_t)n1 * sizeof(double)); 152 | for (int i = 0; i < (n0 * n1); ++i) { periodogram[i] = 0.0; } 153 | 154 | /* Initialize FFT buffers and plan. */ 155 | fftw_complex *in = fftw_alloc_complex((size_t)n0 * (size_t)n1); 156 | fftw_complex *out = fftw_alloc_complex((size_t)n0 * (size_t)n1); 157 | fftw_plan plan = fftw_plan_dft_2d(n0, n1, in, out, FFTW_FORWARD, FFTW_ESTIMATE); 158 | 159 | const double scale = 1. / (double)image_count; 160 | for (int i = 0; i < image_count; ++i) { 161 | /* Vary the seed for each image. */ 162 | args.seed = (uint64_t)i; 163 | 164 | /* Populate sampling with points. Using default allocator (libc malloc). */ 165 | if (tph_poisson_create(&args, /*alloc=*/NULL, &sampling) != TPH_POISSON_SUCCESS) { abort(); } 166 | 167 | /* Construct FFT input from sampling. */ 168 | sampling_image(&args, &sampling, n0, n1, in); 169 | 170 | /* Perform FFT. */ 171 | fftw_execute(plan); 172 | 173 | /* Accumulate (scaled) results. */ 174 | accum_periodogram(scale, n0, n1, out, periodogram); 175 | } 176 | 177 | /* Shift DC bin to the center of the image and write png file. */ 178 | fft_shift(n0, n1, periodogram); 179 | if (!write_png("./tph_poisson_periodogram.png", n0, n1, periodogram)) { abort(); } 180 | 181 | /* Free resources. */ 182 | fftw_destroy_plan(plan); 183 | free(in); 184 | free(out); 185 | free(periodogram); 186 | tph_poisson_destroy(&sampling); 187 | 188 | return EXIT_SUCCESS; 189 | } 190 | -------------------------------------------------------------------------------- /examples/src/simple.c: -------------------------------------------------------------------------------- 1 | #include /* assert */ 2 | #include /* ptrdiff_t */ 3 | #include /* UINT64_C, etc */ 4 | #include /* printf */ 5 | #include /* EXIT_FAILURE, etc */ 6 | #include /* memset */ 7 | 8 | #define TPH_POISSON_IMPLEMENTATION 9 | #include "thinks/tph_poisson.h" 10 | 11 | static_assert(sizeof(tph_poisson_real) == sizeof(float), ""); 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | (void)argc; 16 | (void)argv; 17 | 18 | /* clang-format off */ 19 | const tph_poisson_real bounds_min[2] = { 20 | (tph_poisson_real)-10, (tph_poisson_real)-10 }; 21 | const tph_poisson_real bounds_max[2] = { 22 | (tph_poisson_real)10, (tph_poisson_real)10 }; 23 | /* clang-format on */ 24 | 25 | /* Configure arguments. */ 26 | const tph_poisson_args args = { .bounds_min = bounds_min, 27 | .bounds_max = bounds_max, 28 | .radius = (tph_poisson_real)3, 29 | .ndims = INT32_C(2), 30 | .max_sample_attempts = UINT32_C(30), 31 | .seed = UINT64_C(1981) }; 32 | 33 | /* Using default allocator (libc malloc). */ 34 | const tph_poisson_allocator *alloc = NULL; 35 | 36 | /* Initialize empty sampling. */ 37 | tph_poisson_sampling sampling; 38 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 39 | 40 | /* Populate sampling with points. */ 41 | const int ret = tph_poisson_create(&args, alloc, &sampling); 42 | if (ret != TPH_POISSON_SUCCESS) { 43 | /* No need to destroy sampling here! */ 44 | printf("Failed creating Poisson sampling! Error code: %d\n", ret); 45 | return EXIT_FAILURE; 46 | } 47 | 48 | /* Retrieve sampling points. */ 49 | const tph_poisson_real *samples = tph_poisson_get_samples(&sampling); 50 | if (samples == NULL) { 51 | /* Shouldn't happen since we check the return value from tph_poisson_create! */ 52 | printf("Bad samples!\n"); 53 | tph_poisson_destroy(&sampling); 54 | return EXIT_FAILURE; 55 | } 56 | 57 | /* Print first and last sample positions. */ 58 | /* clang-format off */ 59 | assert(sampling.nsamples >= 2); 60 | printf("\n%s:\n" 61 | "samples[%td] = ( %.3f, %.3f )\n" 62 | "...\n" 63 | "samples[%td] = ( %.3f, %.3f )\n\n", 64 | "simple (C)", 65 | (ptrdiff_t)0, 66 | (double)samples[0], 67 | (double)samples[1], 68 | (ptrdiff_t)(sampling.nsamples - 1), 69 | (double)samples[(sampling.nsamples - 1) * sampling.ndims], 70 | (double)samples[(sampling.nsamples - 1) * sampling.ndims + 1]); 71 | /* clang-format on */ 72 | 73 | /* Free memory. */ 74 | tph_poisson_destroy(&sampling); 75 | 76 | return EXIT_SUCCESS; 77 | } 78 | -------------------------------------------------------------------------------- /examples/src/simple.cpp: -------------------------------------------------------------------------------- 1 | #include // std::array 2 | #include // assert 3 | #include // UINT64_C, etc 4 | #include // std::printf 5 | #include // std::function 6 | #include // std::unique_ptr 7 | #include // std::is_same_v 8 | 9 | #define TPH_POISSON_IMPLEMENTATION 10 | #include "thinks/tph_poisson.h" 11 | 12 | static_assert(std::is_same_v); 13 | 14 | int main(int /*argc*/, char * /*argv*/[]) 15 | { 16 | // clang-format off 17 | constexpr std::array bounds_min{ 18 | static_cast(-10), static_cast(-10) }; 19 | constexpr std::array bounds_max{ 20 | static_cast(10), static_cast(10) }; 21 | // clang-format on 22 | 23 | // Configure arguments. 24 | tph_poisson_args args = {}; 25 | args.radius = static_cast(3); 26 | args.ndims = INT32_C(2); 27 | args.bounds_min = bounds_min.data(); 28 | args.bounds_max = bounds_max.data(); 29 | args.max_sample_attempts = UINT32_C(30); 30 | args.seed = UINT64_C(1981); 31 | 32 | // Using default allocator (libc malloc). 33 | const tph_poisson_allocator *alloc = NULL; 34 | 35 | // Initialize empty sampling. 36 | using unique_poisson_ptr = 37 | std::unique_ptr>; 38 | auto sampling = unique_poisson_ptr{ new tph_poisson_sampling{}, [](tph_poisson_sampling *s) { 39 | tph_poisson_destroy(s); 40 | delete s; 41 | } }; 42 | 43 | // Populate sampling with points. 44 | if (const int ret = tph_poisson_create(&args, alloc, sampling.get()); 45 | ret != TPH_POISSON_SUCCESS) { 46 | std::printf("Failed creating Poisson sampling! Error code: %d\n", ret); 47 | return EXIT_FAILURE; 48 | }; 49 | 50 | // Retrieve sampling points. 51 | const tph_poisson_real *samples = tph_poisson_get_samples(sampling.get()); 52 | if (samples == nullptr) { 53 | /* Shouldn't happen since we check the return value from tph_poisson_create! */ 54 | std::printf("Bad samples!\n"); 55 | return EXIT_FAILURE; 56 | } 57 | 58 | // Print first and last sample positions. 59 | // clang-format off 60 | assert(sampling->nsamples >= 2); 61 | std::printf("\n%s:\n" 62 | "samples[%td] = ( %.3f, %.3f )\n" 63 | "...\n" 64 | "samples[%td] = ( %.3f, %.3f )\n\n", 65 | "simple (Cpp)", 66 | static_cast(0), 67 | static_cast(samples[0]), 68 | static_cast(samples[1]), 69 | static_cast(sampling->nsamples - 1), 70 | static_cast(samples[(sampling->nsamples - 1) * sampling->ndims]), 71 | static_cast(samples[(sampling->nsamples - 1) * sampling->ndims + 1])); 72 | // clang-format on 73 | 74 | // tph_poisson_destroy is called by unique_poisson_ptr destructor. 75 | 76 | return EXIT_SUCCESS; 77 | } 78 | -------------------------------------------------------------------------------- /images/attempts_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinks/tph_poisson/e436117bfc00d8f64c4391f0bae28ff6da0b309f/images/attempts_300.png -------------------------------------------------------------------------------- /images/seed_1981.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinks/tph_poisson/e436117bfc00d8f64c4391f0bae28ff6da0b309f/images/seed_1981.png -------------------------------------------------------------------------------- /images/seed_and_attempts.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinks/tph_poisson/e436117bfc00d8f64c4391f0bae28ff6da0b309f/images/seed_and_attempts.pdf -------------------------------------------------------------------------------- /images/seed_and_attempts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinks/tph_poisson/e436117bfc00d8f64c4391f0bae28ff6da0b309f/images/seed_and_attempts.png -------------------------------------------------------------------------------- /images/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinks/tph_poisson/e436117bfc00d8f64c4391f0bae28ff6da0b309f/images/simple.png -------------------------------------------------------------------------------- /images/simple_circles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinks/tph_poisson/e436117bfc00d8f64c4391f0bae28ff6da0b309f/images/simple_circles.png -------------------------------------------------------------------------------- /images/simple_example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinks/tph_poisson/e436117bfc00d8f64c4391f0bae28ff6da0b309f/images/simple_example.pdf -------------------------------------------------------------------------------- /images/simple_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinks/tph_poisson/e436117bfc00d8f64c4391f0bae28ff6da0b309f/images/simple_example.png -------------------------------------------------------------------------------- /images/tph_poisson_periodogram_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinks/tph_poisson/e436117bfc00d8f64c4391f0bae28ff6da0b309f/images/tph_poisson_periodogram_512.png -------------------------------------------------------------------------------- /include/thinks/tph_poisson.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 2024 Tommy Hinks 3 | * For LICENSE (MIT), USAGE, and HISTORY see the end of this file. 4 | */ 5 | 6 | #ifndef TPH_POISSON_H 7 | #define TPH_POISSON_H 8 | 9 | #define TPH_POISSON_MAJOR_VERSION 0 10 | #define TPH_POISSON_MINOR_VERSION 4 11 | #define TPH_POISSON_PATCH_VERSION 0 12 | 13 | #include /* size_t, ptrdiff_t, NULL */ 14 | #include /* uint32_t, uintptr_t, etc */ 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | /* BEGIN PUBLIC API --------------------------------------------------------- */ 21 | 22 | /* clang-format off */ 23 | #if (defined(TPH_POISSON_REAL_TYPE) \ 24 | && (!defined(TPH_POISSON_SQRT) || !defined(TPH_POISSON_CEIL) || !defined(TPH_POISSON_FLOOR))) \ 25 | || (defined(TPH_POISSON_SQRT) \ 26 | && (!defined(TPH_POISSON_REAL_TYPE) || !defined(TPH_POISSON_CEIL) || !defined(TPH_POISSON_FLOOR))) \ 27 | || (defined(TPH_POISSON_CEIL) \ 28 | && (!defined(TPH_POISSON_REAL_TYPE) || !defined(TPH_POISSON_SQRT) || !defined(TPH_POISSON_FLOOR))) \ 29 | || (defined(TPH_POISSON_FLOOR) \ 30 | && (!defined(TPH_POISSON_REAL_TYPE) || !defined(TPH_POISSON_SQRT) || !defined(TPH_POISSON_CEIL))) 31 | #error \ 32 | "TPH_POISSON_REAL_TYPE, TPH_POISSON_SQRT, TPH_POISSON_CEIL and TPH_POISSON_FLOOR must all be defined; or none of them." 33 | #endif 34 | #if !defined(TPH_POISSON_REAL_TYPE) && !defined(TPH_POISSON_SQRT) && !defined(TPH_POISSON_CEIL) \ 35 | && !defined(TPH_POISSON_FLOOR) 36 | #define TPH_POISSON_REAL_TYPE float 37 | #include 38 | #define TPH_POISSON_SQRT(_X_) sqrtf((_X_)) 39 | #define TPH_POISSON_CEIL(_X_) ceilf((_X_)) 40 | #define TPH_POISSON_FLOOR(_X_) floorf((_X_)) 41 | #endif 42 | 43 | typedef TPH_POISSON_REAL_TYPE tph_poisson_real; 44 | 45 | typedef struct tph_poisson_args_ tph_poisson_args; 46 | typedef struct tph_poisson_allocator_ tph_poisson_allocator; 47 | typedef struct tph_poisson_sampling_ tph_poisson_sampling; 48 | typedef struct tph_poisson_sampling_internal_ tph_poisson_sampling_internal; 49 | 50 | typedef void *(*tph_poisson_malloc_fn)(ptrdiff_t size, void *ctx); 51 | typedef void (*tph_poisson_free_fn)(void *ptr, ptrdiff_t size, void *ctx); 52 | /* clang-format on */ 53 | 54 | #pragma pack(push, 1) 55 | 56 | /** 57 | * Allocator interface. Must provide malloc and free functions. 58 | * Context is optional and may be NULL. 59 | */ 60 | struct tph_poisson_allocator_ 61 | { 62 | tph_poisson_malloc_fn malloc; 63 | tph_poisson_free_fn free; 64 | void *ctx; 65 | }; 66 | 67 | /** 68 | * Parameters used when creating a Poisson disk sampling. 69 | * bounds_min/max are assumed to point to arrays of length ndims. 70 | */ 71 | struct tph_poisson_args_ 72 | { 73 | const tph_poisson_real *bounds_min; 74 | const tph_poisson_real *bounds_max; 75 | uint64_t seed; 76 | tph_poisson_real radius; 77 | int32_t ndims; 78 | uint32_t max_sample_attempts; 79 | }; 80 | 81 | /** 82 | * Result of creating a Poisson disk sampling. 83 | * Use with tph_poisson_get_samples to retrieve sample positions. 84 | * Memory must be freed after use by calling tph_poisson_destroy. 85 | */ 86 | struct tph_poisson_sampling_ 87 | { 88 | tph_poisson_sampling_internal *internal; 89 | ptrdiff_t nsamples; 90 | int32_t ndims; 91 | }; 92 | 93 | #pragma pack(pop) 94 | 95 | /* clang-format off */ 96 | 97 | /* Possible return values from tph_poisson_create. */ 98 | #define TPH_POISSON_SUCCESS 0 99 | #define TPH_POISSON_BAD_ALLOC 1 100 | #define TPH_POISSON_INVALID_ARGS 2 101 | #define TPH_POISSON_OVERFLOW 3 102 | 103 | /* clang-format on */ 104 | 105 | /** 106 | * Generates a list of samples with the guarantees: 107 | * (1) No two samples are closer to each other than args.radius; 108 | * (2) No sample is outside the region [args.bounds_min, args.bounds_max]. 109 | * 110 | * The algorithm tries to fit as many samples as possible into the provided region 111 | * without violating the above requirements. After creation, the samples can be 112 | * accessed using the tph_poisson_get_samples function. 113 | * 114 | * Errors: 115 | * TPH_POISSON_BAD_ALLOC - Failed memory allocation. 116 | * TPH_POISSON_INVALID_ARGS - The arguments are invalid if: 117 | * - args.radius is <= 0, or 118 | * - args.ndims is < 1, or 119 | * - args.bounds_min[i] >= args.bounds_max[i], or 120 | * - args.max_sample_attempts == 0, or 121 | * - an invalid allocator is provided. 122 | * TPH_POISSON_OVERFLOW - The number of samples exceeds the maximum number. 123 | * 124 | * Note that when an error is returned the sampling doesn't need to be destroyed 125 | * using the tph_poisson_destroy function. 126 | * 127 | * @param sampling Sampling to store samples. 128 | * @param args Arguments. 129 | * @param alloc Optional custom allocator (may be null). 130 | * @return TPH_POISSON_SUCCESS if no errors; otherwise a non-zero error code. 131 | */ 132 | extern int tph_poisson_create(const tph_poisson_args *args, 133 | const tph_poisson_allocator *alloc, 134 | tph_poisson_sampling *sampling); 135 | 136 | /** 137 | * @brief Frees all memory used by the sampling. Note that the sampling itself is not free'd. 138 | * @param sampling Sampling to store samples. 139 | */ 140 | extern void tph_poisson_destroy(tph_poisson_sampling *sampling); 141 | 142 | /** 143 | * Returns a pointer to the samples in the provided sampling. Samples are stored as 144 | * N-dimensional points, i.e. the first N values are the coordinates of the first point, etc. 145 | * Note that sampling.ndims and sampling.nsamples can be used to unpack the raw samples into points. 146 | * @param sampling Sampling to store samples. 147 | * @return Pointer to samples, or NULL if the sampling has not been successfully initialized by a 148 | * call to the tph_poisson_create function (without being destroy by the tph_poisson_destroy 149 | * function in between). 150 | */ 151 | extern const tph_poisson_real *tph_poisson_get_samples(const tph_poisson_sampling *sampling); 152 | 153 | /* END PUBLIC API ----------------------------------------------------------- */ 154 | 155 | #ifdef __cplusplus 156 | } /* extern "C" */ 157 | #endif 158 | 159 | #endif /* TPH_POISSON_H */ 160 | 161 | /* BEGIN IMPLEMENTATION ------------------------------------------------------*/ 162 | 163 | #ifdef TPH_POISSON_IMPLEMENTATION 164 | #undef TPH_POISSON_IMPLEMENTATION 165 | 166 | #include /* alignof */ 167 | #include /* bool, true, false */ 168 | 169 | #if defined(_MSC_VER) && !defined(__cplusplus) 170 | #define TPH_POISSON_INLINE __inline 171 | #else 172 | #define TPH_POISSON_INLINE inline 173 | #endif 174 | 175 | #ifndef TPH_POISSON_ASSERT 176 | #include 177 | #define TPH_POISSON_ASSERT(_X_) assert((_X_)) 178 | #endif 179 | 180 | #ifndef TPH_POISSON_MEMCPY 181 | #include 182 | #define TPH_POISSON_MEMCPY(_DST_, _SRC_, _N_) memcpy((_DST_), (_SRC_), (_N_)) 183 | #endif 184 | 185 | #ifndef TPH_POISSON_MEMSET 186 | #include 187 | #define TPH_POISSON_MEMSET(_S_, _C_, _N_) memset((_S_), (_C_), (_N_)) 188 | #endif 189 | 190 | /* 191 | * MEMORY 192 | */ 193 | 194 | /* clang-format off */ 195 | #if ( defined(TPH_POISSON_MALLOC) && !defined(TPH_POISSON_FREE)) || \ 196 | (!defined(TPH_POISSON_MALLOC) && defined(TPH_POISSON_FREE)) 197 | #error \ 198 | "TPH_POISSON_MALLOC and TPH_POISSON_FREE must both be defined; or none of them." 199 | #endif 200 | #if !defined(TPH_POISSON_MALLOC) && !defined(TPH_POISSON_FREE) 201 | #include 202 | #define TPH_POISSON_MALLOC(_SIZE_) malloc((_SIZE_)) 203 | #define TPH_POISSON_FREE(_PTR_) free((_PTR_)) 204 | #endif 205 | /* clang-format on */ 206 | 207 | /* Adapt libc memory functions to our allocator interface. These functions will be called if no 208 | * custom allocator is provided at run-time. */ 209 | 210 | static TPH_POISSON_INLINE void *tph_poisson_malloc(ptrdiff_t size, void *ctx) 211 | { 212 | (void)ctx; 213 | return size > 0 ? TPH_POISSON_MALLOC((size_t)size) : (void *)NULL; 214 | } 215 | 216 | static TPH_POISSON_INLINE void tph_poisson_free(void *ptr, ptrdiff_t size, void *ctx) 217 | { 218 | (void)size; 219 | (void)ctx; 220 | TPH_POISSON_FREE(ptr); 221 | } 222 | 223 | /** 224 | * Default allocator used when no custom allocator is provided. 225 | */ 226 | static tph_poisson_allocator tph_poisson_default_alloc = { tph_poisson_malloc, 227 | tph_poisson_free, 228 | /*.ctx=*/NULL }; 229 | 230 | /** 231 | * @brief Returns a pointer aligned to the provided alignment. The address pointed 232 | * to is a multiple of the alignment. Assumes that alignment is a power of two (which 233 | * seems to always be the case when querying the value using the alignof function). The 234 | * alignment is done by (potentially) moving the pointer 'forward' until it satisfies 235 | * that the address is a multiple of alignment. 236 | * @param ptr Pointer to align. 237 | * @param alignment Requested alignment. Assumed to be a (non-zero) power of two. 238 | * @return An aligned pointer. 239 | */ 240 | static TPH_POISSON_INLINE void *tph_poisson_align(void *const ptr, const size_t alignment) 241 | { 242 | TPH_POISSON_ASSERT((alignment > 0) & ((alignment & (alignment - 1)) == 0)); 243 | return (void *)(((uintptr_t)ptr + (alignment - 1)) & ~(alignment - 1)); 244 | } 245 | 246 | /* 247 | * PSEUDO-RANDOM NUMBER GENERATION 248 | */ 249 | 250 | typedef struct tph_poisson_splitmix64_state_ 251 | { 252 | uint64_t s; 253 | } tph_poisson_splitmix64_state; 254 | 255 | /** 256 | * @brief Returns a pseudo-random number generated using the SplitMix64 algorithm and mutates the 257 | * state in preparation for subsequent calls. 258 | * @param state State to be mutated into the next number in the sequence. 259 | * @return A pseudo-random number 260 | */ 261 | static uint64_t tph_poisson_splitmix64(tph_poisson_splitmix64_state *state) 262 | { 263 | uint64_t result = (state->s += 0x9E3779B97f4A7C15); 264 | result = (result ^ (result >> 30)) * 0xBF58476D1CE4E5B9; 265 | result = (result ^ (result >> 27)) * 0x94D049BB133111EB; 266 | return result ^ (result >> 31); 267 | } 268 | 269 | typedef struct tph_poisson_xoshiro256p_state_ 270 | { 271 | uint64_t s[4]; 272 | } tph_poisson_xoshiro256p_state; 273 | 274 | /** 275 | * @brief Initializes a xoshiro256p state. 276 | * @param state The state to initialize. 277 | * @param seed Seed value, can be zero. 278 | */ 279 | static void tph_poisson_xoshiro256p_init(tph_poisson_xoshiro256p_state *state, const uint64_t seed) 280 | { 281 | /* As suggested at https://prng.di.unimi.it, use SplitMix64 to initialize the state of a 282 | * generator starting from a 64-bit seed. It has been shown that initialization must be 283 | * performed with a generator radically different in nature from the one used to avoid 284 | * correlation on similar seeds. */ 285 | tph_poisson_splitmix64_state sm_state = { seed }; 286 | state->s[0] = tph_poisson_splitmix64(&sm_state); 287 | state->s[1] = tph_poisson_splitmix64(&sm_state); 288 | state->s[2] = tph_poisson_splitmix64(&sm_state); 289 | state->s[3] = tph_poisson_splitmix64(&sm_state); 290 | } 291 | 292 | /** 293 | * @brief Returns a pseudo-random number generated using the xoshiro256+ algorithm and mutates the 294 | * state in preparation for subsequent calls. Assumes that the value of the passed in state is not 295 | * all zeros. 296 | * @param state State to be mutated into the next number in the sequence. 297 | * @return A pseudo-random number. 298 | */ 299 | static uint64_t tph_poisson_xoshiro256p_next(tph_poisson_xoshiro256p_state *state) 300 | { 301 | uint64_t *s = state->s; 302 | const uint64_t result = s[0] + s[3]; 303 | const uint64_t t = s[1] << 17; 304 | s[2] ^= s[0]; 305 | s[3] ^= s[1]; 306 | s[1] ^= s[2]; 307 | s[0] ^= s[3]; 308 | s[2] ^= t; 309 | 310 | /* Hard-code: s[3] = rotl(s[3], 45) */ 311 | s[3] = (s[3] << 45) | (s[3] >> 49); 312 | return result; 313 | } 314 | 315 | /* 316 | * MISC 317 | */ 318 | 319 | /** 320 | * @brief Returns a floating point number in [0..1). 321 | * @param x Bit representation. 322 | * @return A number in [0..1). 323 | */ 324 | static TPH_POISSON_INLINE double tph_poisson_to_double(const uint64_t x) 325 | { 326 | /* Convert to double, as suggested at https://prng.di.unimi.it. This conversion prefers the high 327 | * bits of x (usually, a good idea). */ 328 | return (double)(x >> 11) * 0x1.0p-53; 329 | } 330 | 331 | /* 332 | * VECTOR 333 | */ 334 | 335 | /* clang-format off */ 336 | typedef struct tph_poisson_vec_ 337 | { 338 | ptrdiff_t mem_size; /** Size of memory buffer in bytes. */ 339 | void *mem; /** Memory buffer. */ 340 | void *begin; /** Pointer to first element, or NULL if empty. */ 341 | void *end; /** Pointer to first byte after last element, or NULL if empty. */ 342 | } tph_poisson_vec; 343 | /* clang-format on */ 344 | 345 | /** 346 | * @brief Frees all memory associated with the vector. 347 | * @param vec Vector. 348 | * @param alloc Allocator. 349 | */ 350 | static TPH_POISSON_INLINE void tph_poisson_vec_free(tph_poisson_vec *vec, 351 | const tph_poisson_allocator *alloc) 352 | { 353 | TPH_POISSON_ASSERT(vec != NULL); 354 | TPH_POISSON_ASSERT(alloc != NULL); 355 | if (((int)(vec->mem != NULL) & (int)(vec->mem_size > 0)) == 1) { 356 | alloc->free(vec->mem, vec->mem_size, alloc->ctx); 357 | } 358 | } 359 | 360 | /** 361 | * @brief Returns the number of bytes in the vector. 362 | * @param vec Vector. 363 | * @return The number of bytes in the vector. 364 | */ 365 | static TPH_POISSON_INLINE ptrdiff_t tph_poisson_vec_size(const tph_poisson_vec *vec) 366 | { 367 | TPH_POISSON_ASSERT((intptr_t)vec->end >= (intptr_t)vec->begin); 368 | return (ptrdiff_t)((intptr_t)vec->end - (intptr_t)vec->begin); 369 | } 370 | 371 | /** 372 | * @brief Increase the capacity of the vector (the total number of elements that the vector can hold 373 | * without requiring reallocation) to a value that's greater or equal to new_cap. If new_cap is 374 | * greater than the current capacity, new storage is allocated, otherwise the function does 375 | * nothing. 376 | * @param vec Vector. 377 | * @param alloc Allocator. 378 | * @param new_cap Minimum number of elements that can be stored without requiring reallocation. 379 | * @param alignment Alignment of objects intended to be stored in the vector. 380 | * @return TPH_POISSON_SUCCESS, or a non-zero error code. 381 | */ 382 | static int tph_poisson_vec_reserve(tph_poisson_vec *vec, 383 | const tph_poisson_allocator *alloc, 384 | const ptrdiff_t new_cap, 385 | const ptrdiff_t alignment) 386 | { 387 | TPH_POISSON_ASSERT(vec != NULL); 388 | TPH_POISSON_ASSERT(alloc != NULL); 389 | TPH_POISSON_ASSERT(new_cap > 0); 390 | TPH_POISSON_ASSERT(alignment > 0); 391 | 392 | /* NOTE: For zero-initialized vector cap is 0. */ 393 | const ptrdiff_t cap = vec->mem_size - ((intptr_t)vec->begin - (intptr_t)vec->mem); 394 | if (new_cap <= cap) { return TPH_POISSON_SUCCESS; } 395 | 396 | /* Allocate and align a new buffer with sufficient capacity. Take into account that 397 | * the memory returned by the allocator may not match the requested alignment. */ 398 | const ptrdiff_t new_mem_size = new_cap + alignment; 399 | void *const new_mem = alloc->malloc(new_mem_size, alloc->ctx); 400 | if (new_mem == NULL) { return TPH_POISSON_BAD_ALLOC; } 401 | void *const new_begin = tph_poisson_align(new_mem, (size_t)alignment); 402 | 403 | const ptrdiff_t size = (intptr_t)vec->end - (intptr_t)vec->begin; 404 | 405 | /* Copy existing data (if any) to the new buffer. */ 406 | if (size > 0) { 407 | TPH_POISSON_ASSERT(vec->begin != NULL); 408 | TPH_POISSON_MEMCPY(new_begin, vec->begin, (size_t)size); 409 | } 410 | 411 | /* Destroy the old buffer (if any). */ 412 | if (vec->mem_size > 0) { 413 | TPH_POISSON_ASSERT(vec->mem != NULL); 414 | alloc->free(vec->mem, vec->mem_size, alloc->ctx); 415 | } 416 | 417 | /* Configure vector to use the new buffer. */ 418 | vec->mem = new_mem; 419 | vec->mem_size = new_mem_size; 420 | vec->begin = new_begin; 421 | vec->end = (void *)((intptr_t)new_begin + size); 422 | 423 | TPH_POISSON_ASSERT(vec->mem_size - ((intptr_t)vec->begin - (intptr_t)vec->mem) >= new_cap); 424 | 425 | return TPH_POISSON_SUCCESS; 426 | } 427 | 428 | /** 429 | * @brief Add elements to the end of the vector. 430 | * @param vec Vector. 431 | * @param alloc Allocator. 432 | * @param buf Pointer to values to be added. Assumed to be non-null. 433 | * @param n Number of bytes to copy from values. Assumed to be > 0. 434 | * @param alignment Alignment of objects intended to be stored in the vector. 435 | * @return TPH_POISSON_SUCCESS, or a non-zero error code. 436 | */ 437 | static int tph_poisson_vec_append(tph_poisson_vec *vec, 438 | const tph_poisson_allocator *alloc, 439 | const void *buf, 440 | const ptrdiff_t n, 441 | const ptrdiff_t alignment) 442 | { 443 | TPH_POISSON_ASSERT(vec != NULL); 444 | TPH_POISSON_ASSERT(alloc != NULL); 445 | TPH_POISSON_ASSERT(buf != NULL); 446 | TPH_POISSON_ASSERT(n > 0); 447 | TPH_POISSON_ASSERT(alignment > 0); 448 | 449 | const ptrdiff_t cap = vec->mem_size - ((intptr_t)vec->begin - (intptr_t)vec->mem); 450 | const ptrdiff_t req_cap = (intptr_t)vec->end - (intptr_t)vec->begin + n; 451 | if (req_cap > cap) { 452 | /* Current buffer does not have enough capacity. Try doubling the vector 453 | * capacity and check if it's enough to hold the new elements. If not, 454 | * set the new capacity to hold exactly the new elements. */ 455 | ptrdiff_t new_cap = cap <= (PTRDIFF_MAX >> 1) ? (cap << 1) : cap; 456 | if (new_cap < req_cap) { new_cap = req_cap; } 457 | 458 | /* Allocate and align a new buffer with sufficient capacity. Take into 459 | * account that the memory returned by the allocator may not be aligned to 460 | * the type of element that will be stored. */ 461 | new_cap += alignment; 462 | void *new_mem = alloc->malloc(new_cap, alloc->ctx); 463 | if (new_mem == NULL) { return TPH_POISSON_BAD_ALLOC; } 464 | void *new_begin = tph_poisson_align(new_mem, (size_t)alignment); 465 | 466 | const ptrdiff_t size = (intptr_t)vec->end - (intptr_t)vec->begin; 467 | 468 | /* Copy existing data (if any) to the new buffer. */ 469 | if (size > 0) { 470 | TPH_POISSON_ASSERT(vec->begin != NULL); 471 | TPH_POISSON_MEMCPY(new_begin, vec->begin, (size_t)size); 472 | } 473 | 474 | /* Destroy the old buffer (if any). */ 475 | if (vec->mem_size > 0) { 476 | TPH_POISSON_ASSERT(vec->mem != NULL); 477 | alloc->free(vec->mem, vec->mem_size, alloc->ctx); 478 | } 479 | 480 | /* Configure vector to use the new buffer. */ 481 | vec->mem = new_mem; 482 | vec->mem_size = new_cap; 483 | vec->begin = new_begin; 484 | vec->end = (void *)((intptr_t)new_begin + size); 485 | } 486 | TPH_POISSON_ASSERT(vec->mem_size - ((intptr_t)vec->begin - (intptr_t)vec->mem) >= req_cap); 487 | TPH_POISSON_ASSERT((intptr_t)vec->end % alignment == 0); 488 | 489 | TPH_POISSON_MEMCPY(vec->end, buf, (size_t)n); 490 | vec->end = (void *)((intptr_t)vec->end + n); 491 | return TPH_POISSON_SUCCESS; 492 | } 493 | 494 | /** 495 | * @brief Erase an element from the vector using a "swap & pop" approach. Note that this may 496 | * re-order bytes. 497 | * @param vec Vector. 498 | * @param pos Position to be erased. Assuming 0 <= pos and pos < vector size. 499 | * @return TPH_POISSON_SUCCESS, or a non-zero error code. 500 | */ 501 | static void tph_poisson_vec_erase_swap(tph_poisson_vec *vec, const ptrdiff_t pos, const ptrdiff_t n) 502 | { 503 | TPH_POISSON_ASSERT(vec != NULL); 504 | TPH_POISSON_ASSERT(pos >= 0); 505 | TPH_POISSON_ASSERT((intptr_t)vec->begin + pos < (intptr_t)vec->end); 506 | TPH_POISSON_ASSERT(n >= 1); 507 | TPH_POISSON_ASSERT((intptr_t)vec->begin + pos + n <= (intptr_t)vec->end); 508 | 509 | const intptr_t dst_begin = (intptr_t)vec->begin + pos; 510 | const intptr_t dst_end = dst_begin + n; 511 | intptr_t src_begin = (intptr_t)vec->end - n; 512 | if (src_begin < dst_end) { src_begin = dst_end; } 513 | const intptr_t m = (intptr_t)vec->end - src_begin; 514 | if (m > 0) { TPH_POISSON_MEMCPY((void *)dst_begin, (const void *)src_begin, (size_t)m); } 515 | /* else: when erasing up to the last element there is no need to copy anything, 516 | * just "pop" the end of the vector. */ 517 | 518 | /* "pop" the end of the buffer, decreasing the vector size by n. */ 519 | vec->end = (void *)((intptr_t)vec->end - n); 520 | } 521 | 522 | /** 523 | * @brief Requests the removal of unused capacity. 524 | * @param vec Vector. 525 | * @param alloc Allocator. 526 | * @return TPH_POISSON_SUCCESS, or a non-zero error code. 527 | */ 528 | static int tph_poisson_vec_shrink_to_fit(tph_poisson_vec *vec, 529 | const tph_poisson_allocator *alloc, 530 | const ptrdiff_t alignment) 531 | { 532 | TPH_POISSON_ASSERT(vec != NULL); 533 | TPH_POISSON_ASSERT(alloc != NULL); 534 | TPH_POISSON_ASSERT(alignment > 0); 535 | 536 | const ptrdiff_t size = (intptr_t)vec->end - (intptr_t)vec->begin; 537 | if (size == 0) { 538 | /* Empty vector, no elements. Wipe capacity! 539 | * This includes the case of a zero-initialized vector. */ 540 | if (vec->mem != NULL) { 541 | /* Existing vector is empty but has capacity. */ 542 | TPH_POISSON_ASSERT(vec->mem_size > 0); 543 | alloc->free(vec->mem, vec->mem_size, alloc->ctx); 544 | TPH_POISSON_MEMSET((void *)vec, 0, sizeof(tph_poisson_vec)); 545 | } 546 | TPH_POISSON_ASSERT(vec->mem == NULL); 547 | TPH_POISSON_ASSERT(vec->mem_size == 0); 548 | return TPH_POISSON_SUCCESS; 549 | } 550 | 551 | /* Check if allocating a new buffer (size + alignment) would be smaller than 552 | * the existing buffer. */ 553 | TPH_POISSON_ASSERT(vec->mem_size > alignment); 554 | const ptrdiff_t new_mem_size = size + alignment; 555 | if (vec->mem_size > new_mem_size) { 556 | /* Allocate and align a new buffer with sufficient capacity. Take into 557 | * account that the memory returned by the allocator may not be aligned to 558 | * the type of element that will be stored. */ 559 | void *const new_mem = alloc->malloc(new_mem_size, alloc->ctx); 560 | if (new_mem == NULL) { return TPH_POISSON_BAD_ALLOC; } 561 | void *const new_begin = tph_poisson_align(new_mem, (size_t)alignment); 562 | 563 | /* Copy existing data to the new buffer and destroy the old buffer. */ 564 | TPH_POISSON_MEMCPY(new_begin, vec->begin, (size_t)size); 565 | alloc->free(vec->mem, vec->mem_size, alloc->ctx); 566 | 567 | /* Configure vector to use the new buffer. */ 568 | vec->mem = new_mem; 569 | vec->mem_size = new_mem_size; 570 | vec->begin = new_begin; 571 | vec->end = (void *)((intptr_t)new_begin + size); 572 | } 573 | return TPH_POISSON_SUCCESS; 574 | } 575 | 576 | /* 577 | * STRUCTS 578 | */ 579 | 580 | struct tph_poisson_sampling_internal_ 581 | { 582 | tph_poisson_allocator alloc; 583 | void *mem; 584 | ptrdiff_t mem_size; 585 | 586 | tph_poisson_vec samples; /** ElemT = tph_poisson_real */ 587 | }; 588 | 589 | typedef struct tph_poisson_context_ 590 | { 591 | void *mem; 592 | ptrdiff_t mem_size; 593 | 594 | tph_poisson_real radius; /** No two samples are closer to each other than the radius. */ 595 | int32_t ndims; /** Number of dimensions, typically 2 or 3. */ 596 | uint32_t max_sample_attempts; /** Maximum attempts when spawning samples from existing ones. */ 597 | tph_poisson_real *bounds_min; /** Hyper-rectangle lower bound. */ 598 | tph_poisson_real *bounds_max; /** Hyper-rectangle upper bound. */ 599 | tph_poisson_xoshiro256p_state prng_state; /** Pseudo-random number generator state. */ 600 | 601 | tph_poisson_vec active_indices; /** ElemT = ptrdiff_t */ 602 | 603 | tph_poisson_real grid_dx; /** Uniform cell extent. */ 604 | tph_poisson_real grid_dx_rcp; /** 1 / dx */ 605 | ptrdiff_t grid_linear_size; /** Total number of grid cells. */ 606 | ptrdiff_t *grid_size; /** Number of grid cells in each dimension. */ 607 | ptrdiff_t *grid_stride; /** Strides in each dimension, used to compute linear index. */ 608 | uint32_t *grid_cells; /** Grid cells storing indices to points inside them. */ 609 | 610 | /* Arrays of size ndims. Pre-allocated in the context to provide 'scratch' variables that are used 611 | * during the creation of a sampling, but don't need to be stored afterwards. */ 612 | tph_poisson_real *sample; 613 | ptrdiff_t *grid_index; 614 | ptrdiff_t *min_grid_index; 615 | ptrdiff_t *max_grid_index; 616 | } tph_poisson_context; 617 | 618 | /** 619 | * @brief Returns an allocated instance of sampling internal data. If allocator is NULL, 620 | * a default allocator is used. The instance must be free'd using the free function that 621 | * it stores (see tph_poisson_destroy). 622 | * @param alloc Allocator. 623 | * @return A dynamically allocated initialized instance of sampling internal data. 624 | */ 625 | static tph_poisson_sampling_internal *tph_poisson_alloc_internal(const tph_poisson_allocator *alloc) 626 | { 627 | TPH_POISSON_ASSERT(!alloc || (alloc && ((alloc->malloc != NULL) & (alloc->free != NULL)))); 628 | 629 | const ptrdiff_t mem_size = 630 | (ptrdiff_t)(sizeof(tph_poisson_sampling_internal) + alignof(tph_poisson_sampling_internal)); 631 | tph_poisson_malloc_fn malloc_fn = 632 | alloc != NULL ? alloc->malloc : tph_poisson_default_alloc.malloc; 633 | void *alloc_ctx = alloc != NULL ? alloc->ctx : tph_poisson_default_alloc.ctx; 634 | void *mem = malloc_fn(mem_size, alloc_ctx); 635 | if (mem == NULL) { return NULL; } 636 | TPH_POISSON_MEMSET(mem, 0, (size_t)mem_size); 637 | void *aligned_mem = tph_poisson_align(mem, alignof(tph_poisson_sampling_internal)); 638 | tph_poisson_sampling_internal *internal = (tph_poisson_sampling_internal *)aligned_mem; 639 | internal->alloc.malloc = malloc_fn; 640 | internal->alloc.free = alloc != NULL ? alloc->free : tph_poisson_default_alloc.free; 641 | internal->alloc.ctx = alloc_ctx; 642 | internal->mem = mem; 643 | internal->mem_size = mem_size; 644 | return internal; 645 | } 646 | 647 | /** 648 | * @brief Initialize the context using the provided allocator and arguments. Sets up the 649 | * data structures needed to perform a single run, but that don't need to be kept alive 650 | * after the run has been completed. 651 | * @param ctx Context. 652 | * @param alloc Allocator. 653 | * @param args Arguments. 654 | * @return TPH_POISSON_SUCCESS, or a non-zero error code. 655 | */ 656 | static int tph_poisson_context_init(const tph_poisson_allocator *alloc, 657 | const tph_poisson_args *args, 658 | tph_poisson_context *ctx) 659 | { 660 | /* clang-format off */ 661 | bool valid_args = (args != NULL); 662 | if (!valid_args) { return TPH_POISSON_INVALID_ARGS; } 663 | valid_args &= (args->radius > 0); 664 | valid_args &= (args->ndims > 0); 665 | valid_args &= (args->max_sample_attempts > 0); 666 | valid_args &= (args->bounds_min != NULL); 667 | valid_args &= (args->bounds_max != NULL); 668 | if (!valid_args) { return TPH_POISSON_INVALID_ARGS; } 669 | for (int32_t i = 0; i < args->ndims; ++i) { 670 | valid_args &= (args->bounds_max[i] > args->bounds_min[i]); 671 | } 672 | if (!valid_args) { return TPH_POISSON_INVALID_ARGS; } 673 | /* clang-format on */ 674 | 675 | ctx->radius = args->radius; 676 | ctx->ndims = args->ndims; 677 | ctx->max_sample_attempts = args->max_sample_attempts; 678 | 679 | /* Use a slightly smaller radius to avoid numerical issues. */ 680 | ctx->grid_dx = 681 | ((tph_poisson_real)0.999 * ctx->radius) / TPH_POISSON_SQRT((tph_poisson_real)ctx->ndims); 682 | ctx->grid_dx_rcp = (tph_poisson_real)1 / ctx->grid_dx; 683 | 684 | /* Seed pseudo-random number generator. */ 685 | tph_poisson_xoshiro256p_init(&ctx->prng_state, args->seed); 686 | 687 | /* Compute grid linear size so that we know how much memory to allocate for grid cells. */ 688 | ctx->grid_linear_size = 1; 689 | for (int32_t i = 0; i < ctx->ndims; ++i) { 690 | /* Not checking for overflow! */ 691 | ctx->grid_linear_size *= 692 | (ptrdiff_t)TPH_POISSON_CEIL((args->bounds_max[i] - args->bounds_min[i]) * ctx->grid_dx_rcp); 693 | } 694 | 695 | /* clang-format off */ 696 | ctx->mem_size = 697 | /* bounds_min, bounds_max, sample */ 698 | (ptrdiff_t)(ctx->ndims * 3) * (ptrdiff_t)sizeof(tph_poisson_real) 699 | + (ptrdiff_t)alignof(tph_poisson_real) + 700 | /* grid_index, min_grid_index, max_grid_index, grid.size, grid.stride*/ 701 | (ptrdiff_t)(ctx->ndims * 5) * (ptrdiff_t)sizeof(ptrdiff_t) 702 | + (ptrdiff_t)alignof(ptrdiff_t) + 703 | /* grid.cells */ 704 | ctx->grid_linear_size * (ptrdiff_t)sizeof(uint32_t) + (ptrdiff_t)alignof(uint32_t); 705 | ctx->mem = alloc->malloc(ctx->mem_size, alloc->ctx); 706 | /* clang-format on */ 707 | if (ctx->mem == NULL) { return TPH_POISSON_BAD_ALLOC; } 708 | TPH_POISSON_MEMSET(ctx->mem, 0, (size_t)ctx->mem_size); 709 | 710 | /* Initialize context pointers. Make sure alignment is correct. */ 711 | void *ptr = ctx->mem; 712 | 713 | #ifdef TPH_POISSON_CTX_ALLOC 714 | #error "TPH_POISSON_CTX_ALLOC already defined!" 715 | #endif 716 | /* clang-format off */ 717 | #define TPH_POISSON_CTX_ALLOC(type, count, var) \ 718 | do { \ 719 | TPH_POISSON_ASSERT((uintptr_t)ptr < (uintptr_t)ctx->mem + (size_t)ctx->mem_size); \ 720 | ptr = ((var) = (type *)ptr) + (count); \ 721 | TPH_POISSON_ASSERT((uintptr_t)(var) + (size_t)((ptrdiff_t)sizeof(type) * (count) - 1) \ 722 | < (uintptr_t)ctx->mem + (size_t)ctx->mem_size); \ 723 | TPH_POISSON_ASSERT(((uintptr_t)(var) & (alignof(type) - 1)) == 0); \ 724 | } while (0) 725 | /* clang-format on */ 726 | 727 | ptr = tph_poisson_align(ptr, alignof(ptrdiff_t)); 728 | TPH_POISSON_CTX_ALLOC(ptrdiff_t, ctx->ndims, ctx->grid_index); 729 | TPH_POISSON_CTX_ALLOC(ptrdiff_t, ctx->ndims, ctx->min_grid_index); 730 | TPH_POISSON_CTX_ALLOC(ptrdiff_t, ctx->ndims, ctx->max_grid_index); 731 | TPH_POISSON_CTX_ALLOC(ptrdiff_t, ctx->ndims, ctx->grid_size); 732 | TPH_POISSON_CTX_ALLOC(ptrdiff_t, ctx->ndims, ctx->grid_stride); 733 | ptr = tph_poisson_align(ptr, alignof(tph_poisson_real)); 734 | TPH_POISSON_CTX_ALLOC(tph_poisson_real, ctx->ndims, ctx->bounds_min); 735 | TPH_POISSON_CTX_ALLOC(tph_poisson_real, ctx->ndims, ctx->bounds_max); 736 | TPH_POISSON_CTX_ALLOC(tph_poisson_real, ctx->ndims, ctx->sample); 737 | ptr = tph_poisson_align(ptr, alignof(uint32_t)); 738 | TPH_POISSON_CTX_ALLOC(uint32_t, ctx->grid_linear_size, ctx->grid_cells); 739 | #undef TPH_POISSON_CTX_ALLOC 740 | 741 | /* Copy bounds into context memory buffer to improve locality. */ 742 | TPH_POISSON_MEMCPY( 743 | ctx->bounds_min, args->bounds_min, (size_t)(ctx->ndims * (ptrdiff_t)sizeof(tph_poisson_real))); 744 | TPH_POISSON_MEMCPY( 745 | ctx->bounds_max, args->bounds_max, (size_t)(ctx->ndims * (ptrdiff_t)sizeof(tph_poisson_real))); 746 | 747 | /* Initialize grid size and stride. */ 748 | ctx->grid_size[0] = 749 | (ptrdiff_t)TPH_POISSON_CEIL((ctx->bounds_max[0] - ctx->bounds_min[0]) * ctx->grid_dx_rcp); 750 | ctx->grid_stride[0] = 1; 751 | for (int32_t i = 1; i < ctx->ndims; ++i) { 752 | ctx->grid_size[i] = 753 | (ptrdiff_t)TPH_POISSON_CEIL((ctx->bounds_max[i] - ctx->bounds_min[i]) * ctx->grid_dx_rcp); 754 | ctx->grid_stride[i] = ctx->grid_stride[i - 1] * ctx->grid_size[i - 1]; 755 | } 756 | 757 | /* Initialize cells with sentinel value 0xFFFFFFFF, indicating no sample there. 758 | * Cell values are later set to sample indices. */ 759 | TPH_POISSON_MEMSET( 760 | ctx->grid_cells, 0xFF, (size_t)(ctx->grid_linear_size * (ptrdiff_t)sizeof(uint32_t))); 761 | 762 | return TPH_POISSON_SUCCESS; 763 | } 764 | 765 | /** 766 | * @brief Frees all memory allocated by the context. 767 | * @param ctx Context. 768 | */ 769 | static void tph_poisson_context_destroy(tph_poisson_context *ctx, tph_poisson_allocator *alloc) 770 | { 771 | TPH_POISSON_ASSERT(ctx && alloc); 772 | tph_poisson_vec_free(&ctx->active_indices, alloc); 773 | alloc->free(ctx->mem, ctx->mem_size, alloc->ctx); 774 | } 775 | 776 | /** 777 | * @brief Returns true if p is element-wise inclusively inside b_min and b_max; otherwise false. 778 | * Assumes that b_min is element-wise less than b_max. 779 | * @param p Position to test. 780 | * @param b_min Minimum bounds. 781 | * @param b_max Maximum bounds. 782 | * @param ndims Number of values in p, b_min, and b_max. 783 | * @return Non-zero if p is element-wise inclusively inside the bounded region; otherwise zero. 784 | */ 785 | static bool tph_poisson_inside(const tph_poisson_real *p, 786 | const tph_poisson_real *b_min, 787 | const tph_poisson_real *b_max, 788 | const int32_t ndims) 789 | { 790 | /* Note returns true if ndims <= 0. */ 791 | bool inside = true; 792 | for (int32_t i = 0; i < ndims; ++i) { 793 | /* No early exit, always check all dims. */ 794 | TPH_POISSON_ASSERT(b_min[i] < b_max[i]); 795 | inside &= (p[i] >= b_min[i]); 796 | inside &= (p[i] <= b_max[i]); 797 | } 798 | return inside; 799 | } 800 | 801 | /** 802 | * @brief Add a sample, which is assumed here to fulfill all the Poisson requirements. Updates the 803 | * necessary internal data structures and the context. 804 | * @param ctx Context. 805 | * @param internal Internal data. 806 | * @param sample Sample to add. 807 | * @return TPH_POISSON_SUCCESS, or a non-zero error code. 808 | */ 809 | static int tph_poisson_add_sample(tph_poisson_context *ctx, 810 | tph_poisson_sampling_internal *internal, 811 | const tph_poisson_real *sample) 812 | { 813 | TPH_POISSON_ASSERT(tph_poisson_inside(sample, ctx->bounds_min, ctx->bounds_max, ctx->ndims)); 814 | TPH_POISSON_ASSERT( 815 | tph_poisson_vec_size(&internal->samples) % ((ptrdiff_t)sizeof(tph_poisson_real) * ctx->ndims) 816 | == 0); 817 | const ptrdiff_t sample_index = 818 | tph_poisson_vec_size(&internal->samples) / ((ptrdiff_t)sizeof(tph_poisson_real) * ctx->ndims); 819 | if ((uint32_t)sample_index == 0xFFFFFFFF) { 820 | /* The sample index cannot be the same as the sentinel value of the grid. */ 821 | return TPH_POISSON_OVERFLOW; 822 | } 823 | 824 | int ret = tph_poisson_vec_append(&internal->samples, 825 | &internal->alloc, 826 | sample, 827 | (ptrdiff_t)sizeof(tph_poisson_real) * ctx->ndims, 828 | (ptrdiff_t)alignof(tph_poisson_real)); 829 | if (ret != TPH_POISSON_SUCCESS) { return ret; } 830 | ret = tph_poisson_vec_append(&ctx->active_indices, 831 | &internal->alloc, 832 | &sample_index, 833 | (ptrdiff_t)sizeof(ptrdiff_t), 834 | (ptrdiff_t)alignof(ptrdiff_t)); 835 | if (ret != TPH_POISSON_SUCCESS) { return ret; } 836 | 837 | /* Compute linear grid index. */ 838 | TPH_POISSON_ASSERT(ctx->grid_stride[0] == 1); 839 | ptrdiff_t xi = (ptrdiff_t)TPH_POISSON_FLOOR((sample[0] - ctx->bounds_min[0]) * ctx->grid_dx_rcp); 840 | TPH_POISSON_ASSERT((0 <= xi) & (xi < ctx->grid_size[0])); 841 | ptrdiff_t k = xi; 842 | for (int32_t i = 1; i < ctx->ndims; ++i) { 843 | xi = (ptrdiff_t)TPH_POISSON_FLOOR((sample[i] - ctx->bounds_min[i]) * ctx->grid_dx_rcp); 844 | TPH_POISSON_ASSERT((0 <= xi) & (xi < ctx->grid_size[i])); 845 | /* Not checking for overflow! */ 846 | k += xi * ctx->grid_stride[i]; 847 | } 848 | 849 | /* Record sample index in grid. Each grid cell can hold up to one sample, 850 | * and once a cell has been assigned a sample it should not be updated. 851 | * It is assumed here that the cell has its sentinel value before being 852 | * assigned a sample index. */ 853 | TPH_POISSON_ASSERT(ctx->grid_cells[k] == 0xFFFFFFFF); 854 | ctx->grid_cells[k] = (uint32_t)sample_index; 855 | return TPH_POISSON_SUCCESS; 856 | } 857 | 858 | /** 859 | * @brief Generate a pseudo-random sample position that is guaranteed be at a distance 860 | * [radius, 2 * radius] from the provided center position. 861 | * @param ctx Context. 862 | * @param center Center position. 863 | * @param sample Output sample position. 864 | */ 865 | static void tph_poisson_rand_annulus_sample(tph_poisson_context *ctx, 866 | const tph_poisson_real *center, 867 | tph_poisson_real *sample) 868 | { 869 | int32_t i = 0; 870 | tph_poisson_real sqr_mag = 0; 871 | for (;;) { 872 | /* Generate a random component in the range [-2, 2] for each dimension. 873 | * Use sample storage to temporarily store components. */ 874 | sqr_mag = 0; 875 | for (i = 0; i < ctx->ndims; ++i) { 876 | /* clang-format off */ 877 | sample[i] = (tph_poisson_real)(-2 + 4 * tph_poisson_to_double( 878 | tph_poisson_xoshiro256p_next(&ctx->prng_state))); 879 | /* clang-format on */ 880 | sqr_mag += sample[i] * sample[i]; 881 | } 882 | 883 | /* The randomized offset is not guaranteed to be within the radial 884 | * distance that we need to guarantee. If we found an offset with 885 | * magnitude in the range (1, 2] we are done, otherwise generate a new 886 | * offset. Continue until a valid offset is found. */ 887 | if (((int)((tph_poisson_real)1 < sqr_mag) & (int)(sqr_mag <= (tph_poisson_real)4)) == 1) { 888 | /* Found a valid offset. 889 | * Add the offset scaled by radius to the center coordinate to 890 | * produce the final sample. */ 891 | for (i = 0; i < ctx->ndims; ++i) { sample[i] = center[i] + ctx->radius * sample[i]; } 892 | break; 893 | } 894 | } 895 | } 896 | 897 | /** 898 | * @brief Computes the grid index range in which the provided sample position needs to check for 899 | * other samples that are possible closer than the radius (given in context). 900 | * @param ctx Context. 901 | * @param sample Input sample position. 902 | * @param min_grid_index Minimum grid index. 903 | * @param max_grid_index Maximum grid index. 904 | */ 905 | static void tph_poisson_grid_index_bounds(tph_poisson_context *ctx, 906 | const tph_poisson_real *sample, 907 | ptrdiff_t *min_grid_index, 908 | ptrdiff_t *max_grid_index) 909 | { 910 | #ifdef TPH_POISSON_GRID_CLAMP 911 | #error "TPH_POISSON_GRID_CLAMP already defined!" 912 | #endif 913 | /* clang-format off */ 914 | #define TPH_POISSON_GRID_CLAMP(grid_index, grid_size) \ 915 | if ((grid_index) < 0) { \ 916 | (grid_index) = 0; \ 917 | } else if ((grid_index) >= (grid_size)) { \ 918 | (grid_index) = (grid_size) - 1; \ 919 | } 920 | /* clang-format on */ 921 | 922 | tph_poisson_real si = 0; 923 | for (int32_t i = 0; i < ctx->ndims; ++i) { 924 | TPH_POISSON_ASSERT(ctx->grid_size[i] > 0); 925 | si = sample[i] - ctx->bounds_min[i]; 926 | min_grid_index[i] = (ptrdiff_t)TPH_POISSON_FLOOR((si - ctx->radius) * ctx->grid_dx_rcp); 927 | max_grid_index[i] = (ptrdiff_t)TPH_POISSON_FLOOR((si + ctx->radius) * ctx->grid_dx_rcp); 928 | TPH_POISSON_GRID_CLAMP(min_grid_index[i], ctx->grid_size[i]); 929 | TPH_POISSON_GRID_CLAMP(max_grid_index[i], ctx->grid_size[i]); 930 | } 931 | #undef TPH_POISSON_GRID_CLAMP 932 | } 933 | 934 | /** 935 | * @brief Returns true if there exists another sample within the radius used to 936 | * construct the grid; otherwise false. 937 | * @param ctx Context. 938 | * @param sample Input sample position. 939 | * @param active_sample_index Index of the existing sample that 'spawned' the sample tested here. 940 | * @param min_grid_index Minimum grid index. 941 | * @param max_grid_index Maximum grid index. 942 | */ 943 | static bool tph_poisson_existing_sample_within_radius(tph_poisson_context *ctx, 944 | const tph_poisson_vec *samples, 945 | const tph_poisson_real *sample, 946 | const ptrdiff_t active_sample_index, 947 | const ptrdiff_t *min_grid_index, 948 | const ptrdiff_t *max_grid_index) 949 | { 950 | const tph_poisson_real r_sqr = ctx->radius * ctx->radius; 951 | tph_poisson_real di = 0; 952 | tph_poisson_real d_sqr = -1; 953 | const tph_poisson_real *cell_sample = NULL; 954 | int32_t i = -1; 955 | ptrdiff_t k = -1; 956 | bool test_cell = false; 957 | const int32_t ndims = ctx->ndims; 958 | TPH_POISSON_MEMCPY( 959 | ctx->grid_index, min_grid_index, (size_t)(ndims * (ptrdiff_t)sizeof(ptrdiff_t))); 960 | do { 961 | /* Compute linear grid index. */ 962 | TPH_POISSON_ASSERT((0 <= ctx->grid_index[0]) & (ctx->grid_index[0] < ctx->grid_size[0])); 963 | k = ctx->grid_index[0]; 964 | for (i = 1; i < ndims; ++i) { 965 | /* Not checking for overflow! */ 966 | TPH_POISSON_ASSERT((0 <= ctx->grid_index[i]) & (ctx->grid_index[i] < ctx->grid_size[i])); 967 | k += ctx->grid_index[i] * ctx->grid_stride[i]; 968 | } 969 | 970 | test_cell = (ctx->grid_cells[k] != 0xFFFFFFFF); 971 | test_cell &= (ctx->grid_cells[k] != (uint32_t)active_sample_index); 972 | if (test_cell) { 973 | /* Compute (squared) distance to the existing sample and then check if the existing sample is 974 | * closer than (squared) radius to the provided sample. */ 975 | cell_sample = 976 | (const tph_poisson_real *)samples->begin + (ptrdiff_t)ctx->grid_cells[k] * ndims; 977 | di = sample[0] - cell_sample[0]; 978 | d_sqr = di * di; 979 | for (i = 1; i < ndims; ++i) { 980 | di = sample[i] - cell_sample[i]; 981 | d_sqr += di * di; 982 | } 983 | if (d_sqr < r_sqr) { return true; } 984 | } 985 | 986 | /* Iterate over grid index range. Enumerate every grid index between min_grid_index and 987 | * max_grid_index (inclusive) exactly once. Assumes that min_index is element-wise less than or 988 | * equal to max_index. */ 989 | for (i = 0; i < ndims; ++i) { 990 | TPH_POISSON_ASSERT(min_grid_index[i] <= max_grid_index[i]); 991 | ctx->grid_index[i]++; 992 | if (ctx->grid_index[i] <= max_grid_index[i]) { break; } 993 | ctx->grid_index[i] = min_grid_index[i]; 994 | } 995 | /* If the above loop ran to completion, without triggering the break, the grid_index has been 996 | * set to its original value (min_grid_index). Since this was the starting value for grid_index 997 | * we exit the outer loop when this happens. */ 998 | } while (i != ndims); 999 | 1000 | /* No existing sample was found to be closer to the provided sample than the radius. */ 1001 | return false; 1002 | } 1003 | 1004 | /** 1005 | * @brief Generate a random sample within the given bounds (in context). 1006 | * @param ctx Context. 1007 | * @param sample Output sample. 1008 | */ 1009 | static void tph_poisson_rand_sample(tph_poisson_context *ctx, tph_poisson_real *sample) 1010 | { 1011 | int32_t i = 0; 1012 | for (; i < ctx->ndims; ++i) { 1013 | TPH_POISSON_ASSERT(ctx->bounds_max[i] > ctx->bounds_min[i]); 1014 | sample[i] = 1015 | ctx->bounds_min[i] 1016 | + (tph_poisson_real)(tph_poisson_to_double(tph_poisson_xoshiro256p_next(&ctx->prng_state))) 1017 | * (ctx->bounds_max[i] - ctx->bounds_min[i]); 1018 | /* Clamp to avoid numerical issues. */ 1019 | /* clang-format off */ 1020 | sample[i] = sample[i] < ctx->bounds_min[i] ? ctx->bounds_min[i] 1021 | : (ctx->bounds_max[i] < sample[i] ? ctx->bounds_max[i] : sample[i]); 1022 | /* clang-format on */ 1023 | } 1024 | } 1025 | 1026 | int tph_poisson_create(const tph_poisson_args *args, 1027 | const tph_poisson_allocator *alloc, 1028 | tph_poisson_sampling *sampling) 1029 | { 1030 | /* Allocator must provide all functions, allocator context is optional (may be null). */ 1031 | if (sampling == NULL) { return TPH_POISSON_INVALID_ARGS; } 1032 | if (alloc != NULL && ((int)(alloc->malloc == NULL) | (int)(alloc->free == NULL)) == 1) { 1033 | return TPH_POISSON_INVALID_ARGS; 1034 | } 1035 | 1036 | /* Allocate internal data. */ 1037 | if (sampling->internal != NULL) { tph_poisson_destroy(sampling); } 1038 | sampling->internal = tph_poisson_alloc_internal(alloc); 1039 | if (sampling->internal == NULL) { return TPH_POISSON_BAD_ALLOC; } 1040 | tph_poisson_sampling_internal *internal = sampling->internal; 1041 | 1042 | /* Initialize context. Validates arguments and allocates buffers. */ 1043 | tph_poisson_context ctx; 1044 | TPH_POISSON_MEMSET(&ctx, 0, sizeof(tph_poisson_context)); 1045 | int ret = tph_poisson_context_init(&internal->alloc, args, &ctx); 1046 | if (ret != TPH_POISSON_SUCCESS) { 1047 | /* No need to destroy context here. */ 1048 | tph_poisson_destroy(sampling); 1049 | return ret; 1050 | } 1051 | 1052 | /* Heuristically reserve some memory for samples to avoid reallocations while 1053 | * growing the buffer. Estimate that 25% of the grid cells will end up 1054 | * containing a sample, which is a fairly conservative guess. Prefering not 1055 | * to over-allocate up front here, at the cost of having to reallocate later. */ 1056 | ret = tph_poisson_vec_reserve(&internal->samples, 1057 | &internal->alloc, 1058 | (ctx.grid_linear_size / 4) * ((ptrdiff_t)sizeof(tph_poisson_real) * ctx.ndims), 1059 | (ptrdiff_t)alignof(tph_poisson_real)); 1060 | if (ret != TPH_POISSON_SUCCESS) { 1061 | tph_poisson_context_destroy(&ctx, &internal->alloc); 1062 | tph_poisson_destroy(sampling); 1063 | return ret; 1064 | } 1065 | 1066 | /* Reserve memory for active indices, could use some analysis to find a 1067 | * better estimate here... */ 1068 | ret = tph_poisson_vec_reserve(&ctx.active_indices, 1069 | &internal->alloc, 1070 | 100 * (ptrdiff_t)sizeof(ptrdiff_t), 1071 | (ptrdiff_t)alignof(ptrdiff_t)); 1072 | if (ret != TPH_POISSON_SUCCESS) { 1073 | tph_poisson_context_destroy(&ctx, &internal->alloc); 1074 | tph_poisson_destroy(sampling); 1075 | return ret; 1076 | } 1077 | 1078 | /* Add first sample randomly within bounds. No need to check (non-existing) neighbors. */ 1079 | tph_poisson_rand_sample(&ctx, ctx.sample); 1080 | ret = tph_poisson_add_sample(&ctx, internal, ctx.sample); 1081 | TPH_POISSON_ASSERT(ret == TPH_POISSON_SUCCESS); 1082 | (void)ret; 1083 | 1084 | TPH_POISSON_ASSERT(tph_poisson_vec_size(&ctx.active_indices) / (ptrdiff_t)sizeof(ptrdiff_t) == 1); 1085 | ptrdiff_t active_index_count = 1; 1086 | ptrdiff_t rand_index = -1; 1087 | ptrdiff_t active_sample_index = -1; 1088 | const tph_poisson_real *active_sample = NULL; 1089 | uint32_t attempt_count = 0; 1090 | while (active_index_count > 0) { 1091 | /* Randomly choose an active sample. A sample is considered active until failed attempts 1092 | * have been made to generate a new sample within its annulus. */ 1093 | rand_index = 1094 | (ptrdiff_t)(tph_poisson_xoshiro256p_next(&ctx.prng_state) % (uint64_t)active_index_count); 1095 | active_sample_index = *((const ptrdiff_t *)ctx.active_indices.begin + rand_index); 1096 | active_sample = 1097 | (const tph_poisson_real *)internal->samples.begin + active_sample_index * ctx.ndims; 1098 | attempt_count = 0; 1099 | while (attempt_count < ctx.max_sample_attempts) { 1100 | /* Randomly create a candidate sample inside the active sample's annulus. */ 1101 | tph_poisson_rand_annulus_sample(&ctx, active_sample, ctx.sample); 1102 | /* Check if candidate sample is within bounds. */ 1103 | if (tph_poisson_inside(ctx.sample, ctx.bounds_min, ctx.bounds_max, ctx.ndims)) { 1104 | tph_poisson_grid_index_bounds(&ctx, ctx.sample, ctx.min_grid_index, ctx.max_grid_index); 1105 | if (!tph_poisson_existing_sample_within_radius(&ctx, 1106 | &internal->samples, 1107 | ctx.sample, 1108 | active_sample_index, 1109 | ctx.min_grid_index, 1110 | ctx.max_grid_index)) { 1111 | /* No existing samples where found to be too close to the 1112 | * candidate sample, no further attempts necessary. */ 1113 | ret = tph_poisson_add_sample(&ctx, internal, ctx.sample); 1114 | if (ret != TPH_POISSON_SUCCESS) { 1115 | tph_poisson_context_destroy(&ctx, &internal->alloc); 1116 | tph_poisson_destroy(sampling); 1117 | return ret; 1118 | } 1119 | break; 1120 | } 1121 | /* else: The candidate sample is too close to an existing sample. */ 1122 | } 1123 | /* else: The candidate sample is out-of-bounds. */ 1124 | ++attempt_count; 1125 | } 1126 | 1127 | if (attempt_count == args->max_sample_attempts) { 1128 | /* No valid sample was found on the disk of the active sample after 1129 | * maximum number of attempts, remove it from the active list. */ 1130 | tph_poisson_vec_erase_swap(&ctx.active_indices, 1131 | rand_index * (ptrdiff_t)sizeof(ptrdiff_t), 1132 | (ptrdiff_t)sizeof(ptrdiff_t)); 1133 | } 1134 | active_index_count = tph_poisson_vec_size(&ctx.active_indices) / (ptrdiff_t)sizeof(ptrdiff_t); 1135 | } 1136 | 1137 | ret = tph_poisson_vec_shrink_to_fit( 1138 | &internal->samples, &internal->alloc, (ptrdiff_t)alignof(tph_poisson_real)); 1139 | if (ret != TPH_POISSON_SUCCESS) { 1140 | tph_poisson_context_destroy(&ctx, &internal->alloc); 1141 | tph_poisson_destroy(sampling); 1142 | return ret; 1143 | } 1144 | 1145 | const ptrdiff_t sample_size = (ptrdiff_t)sizeof(tph_poisson_real) * ctx.ndims; 1146 | TPH_POISSON_ASSERT(tph_poisson_vec_size(&internal->samples) % sample_size == 0); 1147 | sampling->ndims = ctx.ndims; 1148 | sampling->nsamples = tph_poisson_vec_size(&internal->samples) / sample_size; 1149 | 1150 | tph_poisson_context_destroy(&ctx, &internal->alloc); 1151 | 1152 | return TPH_POISSON_SUCCESS; 1153 | } 1154 | 1155 | void tph_poisson_destroy(tph_poisson_sampling *sampling) 1156 | { 1157 | if (sampling != NULL) { 1158 | tph_poisson_sampling_internal *internal = sampling->internal; 1159 | if (internal != NULL) { 1160 | tph_poisson_vec_free(&internal->samples, &internal->alloc); 1161 | tph_poisson_free_fn free_fn = internal->alloc.free; 1162 | void *alloc_ctx = internal->alloc.ctx; 1163 | free_fn(internal->mem, internal->mem_size, alloc_ctx); 1164 | } 1165 | /* Protects from destroy being called more than once causing a double-free error. */ 1166 | TPH_POISSON_MEMSET(sampling, 0, sizeof(tph_poisson_sampling)); 1167 | } 1168 | } 1169 | 1170 | const tph_poisson_real *tph_poisson_get_samples(const tph_poisson_sampling *sampling) 1171 | { 1172 | /* Make sure that a 'destroyed' sampling does not return any samples. */ 1173 | if (sampling != NULL && sampling->internal != NULL) { 1174 | return (const tph_poisson_real *)sampling->internal->samples.begin; 1175 | } 1176 | return NULL; 1177 | } 1178 | 1179 | /* Clean up internal macros. */ 1180 | #undef TPH_POISSON_INLINE 1181 | #undef TPH_POISSON_ASSERT 1182 | #undef TPH_POISSON_MEMCPY 1183 | #undef TPH_POISSON_MEMSET 1184 | #undef TPH_POISSON_MALLOC 1185 | #undef TPH_POISSON_FREE 1186 | 1187 | #endif /* TPH_POISSON_IMPLEMENTATION */ 1188 | 1189 | /* 1190 | 1191 | ABOUT: 1192 | 1193 | A single file implementation of Poisson disk sampling in arbitrary dimensions. 1194 | 1195 | HISTORY: 1196 | 1197 | 0.4.0 2024-11-13 - C interface and implementation, new build system. 1198 | v0.3 2020-06-30 - C++ interface and implementation. 1199 | 1200 | LICENSE: 1201 | 1202 | The MIT License (MIT) 1203 | 1204 | Copyright (c) 2024 Tommy Hinks 1205 | 1206 | Permission is hereby granted, free of charge, to any person obtaining a copy 1207 | of this software and associated documentation files (the "Software"), to deal 1208 | in the Software without restriction, including without limitation the rights 1209 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 1210 | copies of the Software, and to permit persons to whom the Software is 1211 | furnished to do so, subject to the following conditions: 1212 | 1213 | The above copyright notice and this permission notice shall be included in all 1214 | copies or substantial portions of the Software. 1215 | 1216 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1217 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1218 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1219 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1220 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1221 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1222 | SOFTWARE. 1223 | 1224 | DISCLAIMER: 1225 | 1226 | This software is supplied "AS IS" without any warranties and support 1227 | 1228 | USAGE: 1229 | 1230 | Generates a list of samples with the guarantees: 1231 | (1) No two samples are closer to each other than some radius; 1232 | (2) No sample is outside the region bounds. 1233 | 1234 | The algorithm tries to fit as many samples as possible into the region without violating the 1235 | above requirements. 1236 | 1237 | The API consists of these functions: 1238 | 1239 | int tph_poisson_create(const tph_poisson_args *args, 1240 | const tph_poisson_allocator *alloc, 1241 | tph_poisson_sampling *sampling); 1242 | 1243 | void tph_poisson_destroy(tph_poisson_sampling *sampling); 1244 | 1245 | const tph_poisson_real *tph_poisson_get_samples(const tph_poisson_sampling *sampling); 1246 | 1247 | Example usage: 1248 | 1249 | #include 1250 | #include // ptrdiff_t 1251 | #include // UINT64_C, etc 1252 | #include // printf 1253 | #include // EXIT_FAILURE, etc 1254 | #include // memset 1255 | 1256 | #define TPH_POISSON_IMPLEMENTATION 1257 | #include "thinks/tph_poisson.h" 1258 | 1259 | int main(int argc, char *argv[]) 1260 | { 1261 | (void)argc; 1262 | (void)argv; 1263 | 1264 | const tph_poisson_real bounds_min[2] = { (tph_poisson_real)-100, (tph_poisson_real)-100 }; 1265 | const tph_poisson_real bounds_max[2] = { (tph_poisson_real)100, (tph_poisson_real)100 }; 1266 | const tph_poisson_args args = { .bounds_min = bounds_min, 1267 | .bounds_max = bounds_max, 1268 | .radius = (tph_poisson_real)3, 1269 | .ndims = INT32_C(2), 1270 | .max_sample_attempts = UINT32_C(30), 1271 | .seed = UINT64_C(1981) }; 1272 | 1273 | tph_poisson_allocator *alloc = NULL; 1274 | 1275 | tph_poisson_sampling sampling; 1276 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 1277 | 1278 | int ret = tph_poisson_create(&args, alloc, &sampling); 1279 | if (ret != TPH_POISSON_SUCCESS) { 1280 | // No need to destroy sampling here! 1281 | printf("Failed creating Poisson sampling! Error code: %d", ret); 1282 | return EXIT_FAILURE; 1283 | } 1284 | 1285 | const tph_poisson_real *samples = tph_poisson_get_samples(&sampling); 1286 | if (samples == NULL) { 1287 | // Shouldn't happen since we check the return value from tph_poisson_create! 1288 | printf("Bad samples!\n"); 1289 | tph_poisson_destroy(&sampling); 1290 | return EXIT_FAILURE; 1291 | } 1292 | 1293 | // Print sample positions. 1294 | for (ptrdiff_t i = 0; i < sampling.nsamples; ++i) { 1295 | printf("sample[%td] = ( %.3f, %.3f )\n", 1296 | i, 1297 | samples[i * sampling.ndims], 1298 | samples[i * sampling.ndims + 1]); 1299 | } 1300 | 1301 | // Free memory. 1302 | tph_poisson_destroy(&sampling); 1303 | 1304 | return EXIT_SUCCESS; 1305 | } 1306 | 1307 | */ 1308 | -------------------------------------------------------------------------------- /python/poisson_plot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import json 3 | import argparse 4 | 5 | 6 | def draw_sample(ax, xy, radius, draw_circles): 7 | dot = plt.Circle(xy, 0.05, color='black', clip_on=False) 8 | ax.add_artist(dot) 9 | if draw_circles: 10 | circle = plt.Circle(xy, radius, color='r', clip_on=False, fill=False) 11 | ax.add_artist(circle) 12 | 13 | 14 | def draw_samples(ax, xy_list, radius, draw_circles): 15 | for xy in xy_list: 16 | draw_sample(ax, xy, radius, draw_circles) 17 | 18 | 19 | def main(json_input_filename, image_output_filename, draw_circles): 20 | with open(json_input_filename, "r") as read_file: 21 | data = json.load(read_file) 22 | 23 | fig, ax = plt.subplots() # note we must use plt.subplots, not plt.subplot 24 | 25 | ax.set_xlim((data["bounds_min"][0], data["bounds_max"][0])) 26 | ax.set_ylim((data["bounds_min"][1], data["bounds_max"][1])) 27 | ax.set_aspect('equal') 28 | 29 | xy_list = data["points"] 30 | radius = data["radius"] 31 | 32 | draw_samples(ax, xy_list, radius, draw_circles) 33 | 34 | fig.savefig(image_output_filename, dpi=300) 35 | 36 | 37 | if __name__ == "__main__": 38 | parser = argparse.ArgumentParser(description='Plot Poisson distribution.') 39 | parser.add_argument("--json", help="input json filename") 40 | parser.add_argument("--image", help="output image filename") 41 | parser.add_argument("--draw_circles", help="draw sample radii", default=False, action="store_true") 42 | args = parser.parse_args() 43 | 44 | main(args.json, args.image, args.draw_circles) 45 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(tph_poissonTests LANGUAGES C CXX) 4 | 5 | include(../cmake/project-is-top-level.cmake) 6 | include(../cmake/folders.cmake) 7 | 8 | # ---- Dependencies ---- 9 | 10 | if(PROJECT_IS_TOP_LEVEL) 11 | find_package(tph_poisson REQUIRED) 12 | enable_testing() 13 | endif() 14 | 15 | # Build and versions as static libraries used by the tests. 16 | # The C compiler is used so that linters and other tools recognize that our 17 | # code is in C and can give appropriate warnings. 18 | 19 | add_library(tph_poisson_f32 STATIC "src/tph_poisson_f32.c") 20 | add_library(thinks::tph_poisson_f32 ALIAS tph_poisson_f32) 21 | target_link_libraries(tph_poisson_f32 PUBLIC thinks::tph_poisson) 22 | target_compile_features(tph_poisson_f32 PRIVATE c_std_11) 23 | 24 | add_library(tph_poisson_f64 STATIC "src/tph_poisson_f64.c") 25 | add_library(thinks::tph_poisson_f64 ALIAS tph_poisson_f64) 26 | target_link_libraries(tph_poisson_f64 PUBLIC thinks::tph_poisson) 27 | target_compile_features(tph_poisson_f64 PRIVATE c_std_11) 28 | 29 | # ---- Tests ---- 30 | 31 | # 32 | add_executable(tph_poisson_f32_test "src/tph_poisson_test.cpp") 33 | target_link_libraries(tph_poisson_f32_test PRIVATE thinks::tph_poisson_f32) 34 | target_compile_features(tph_poisson_f32_test PRIVATE cxx_std_17) 35 | add_test(NAME tph_poisson_f32_test COMMAND tph_poisson_f32_test) 36 | 37 | # 38 | add_executable(tph_poisson_f64_test "src/tph_poisson_test.cpp") 39 | target_link_libraries(tph_poisson_f64_test PRIVATE thinks::tph_poisson_f64) 40 | target_compile_definitions(tph_poisson_f64_test PRIVATE TPH_POISSON_TEST_USE_F64) 41 | target_compile_features(tph_poisson_f64_test PRIVATE cxx_std_17) 42 | add_test(NAME tph_poisson_f64_test COMMAND tph_poisson_f64_test) 43 | 44 | add_executable(tph_poisson_vec_test "src/tph_poisson_vec_test.c") 45 | target_link_libraries(tph_poisson_vec_test PRIVATE thinks::tph_poisson) 46 | target_compile_features(tph_poisson_vec_test PRIVATE c_std_11) 47 | add_test(NAME tph_poisson_vec_test COMMAND tph_poisson_vec_test) 48 | if(NOT MSVC) 49 | target_link_libraries(tph_poisson_vec_test PRIVATE m) 50 | endif() 51 | 52 | add_executable(tph_poisson_alloc_test "src/tph_poisson_alloc_test.c") 53 | target_link_libraries(tph_poisson_alloc_test PRIVATE thinks::tph_poisson) 54 | target_compile_features(tph_poisson_alloc_test PRIVATE c_std_11) 55 | add_test(NAME tph_poisson_alloc_test COMMAND tph_poisson_alloc_test) 56 | if(NOT MSVC) 57 | target_link_libraries(tph_poisson_alloc_test PRIVATE m) 58 | endif() 59 | 60 | add_executable(tph_poisson_libc_test "src/tph_poisson_libc_test.c") 61 | target_link_libraries(tph_poisson_libc_test PRIVATE thinks::tph_poisson) 62 | target_compile_features(tph_poisson_libc_test PRIVATE c_std_11) 63 | add_test(NAME tph_poisson_libc_test COMMAND tph_poisson_libc_test) 64 | if(NOT MSVC) 65 | target_link_libraries(tph_poisson_libc_test PRIVATE m) 66 | endif() 67 | 68 | # ---- End-of-file commands ---- 69 | 70 | add_folders(Test) 71 | -------------------------------------------------------------------------------- /test/src/require.h: -------------------------------------------------------------------------------- 1 | #include /* assert */ 2 | #include /* printf */ 3 | #include /* abort */ 4 | 5 | static inline void 6 | require_fail(const char *expr, const char *file, unsigned int line, const char *function) 7 | { 8 | printf( 9 | "Requirement failed: '%s' on line %u in file %s in function %s\n", expr, line, file, function); 10 | abort(); 11 | } 12 | 13 | /* clang-format off */ 14 | #ifdef _MSC_VER 15 | #define TPH_PRETTY_FUNCTION __FUNCSIG__ 16 | #else 17 | #define TPH_PRETTY_FUNCTION __func__ 18 | #endif 19 | #define REQUIRE(expr) \ 20 | ((bool)(expr) ? (void)0 : require_fail(#expr, __FILE__, __LINE__, TPH_PRETTY_FUNCTION)) 21 | #define REQUIRE_F(expr, func) \ 22 | ((bool)(expr) ? (void)0 : require_fail(#expr, __FILE__, __LINE__, func)) 23 | /* clang-format on */ 24 | 25 | /* Dummy malloc/free functions. Used to set up incomplete allocators, these functions should never 26 | * actually be called. Having the function definitions here means that these functions don't show up 27 | * as missing code coverage. */ 28 | 29 | static inline void *dummy_malloc(ptrdiff_t size, void *ctx) 30 | { 31 | (void)size; 32 | (void)ctx; 33 | 34 | assert(false && "called dummy_malloc"); 35 | static int a[2]; 36 | return (void *)a; 37 | } 38 | 39 | static inline void dummy_free(void *ptr, ptrdiff_t size, void *ctx) 40 | { 41 | (void)ptr; 42 | (void)size; 43 | (void)ctx; 44 | 45 | // Do nothing! 46 | assert(false && "called dummy_free"); 47 | } 48 | -------------------------------------------------------------------------------- /test/src/tph_poisson_alloc_test.c: -------------------------------------------------------------------------------- 1 | #include /* UINT64_C, etc */ 2 | #include /* printf */ 3 | #include /* malloc, free, EXIT_SUCCESS */ 4 | #include /* memset */ 5 | 6 | #define TPH_POISSON_IMPLEMENTATION 7 | #include "thinks/tph_poisson.h" 8 | 9 | #include "require.h" 10 | 11 | typedef struct bad_alloc_ctx_ 12 | { 13 | int num_mallocs; 14 | int max_mallocs; 15 | } bad_alloc_ctx; 16 | 17 | static void *bad_alloc_malloc(ptrdiff_t size, void *ctx) 18 | { 19 | bad_alloc_ctx *a_ctx = (bad_alloc_ctx *)ctx; 20 | if ((size == 0) | (a_ctx->num_mallocs >= a_ctx->max_mallocs)) { return NULL; } 21 | void *ptr = malloc((size_t)(size)); 22 | ++a_ctx->num_mallocs; 23 | return ptr; 24 | } 25 | 26 | static void bad_alloc_free(void *ptr, ptrdiff_t size, void *ctx) 27 | { 28 | (void)size; 29 | (void)ctx; 30 | if (ptr == NULL) { return; } 31 | free(ptr); 32 | } 33 | 34 | static void test_bad_alloc(void) 35 | { 36 | /* The idea here is to use a custom allocator that fails (i.e. malloc returns null) 37 | * after a controllable number of allocations. This way it becomes possible 38 | * to verify that bad allocations are gracefully handled internally. However, 39 | * we don't know exactly how many allocations will be made (nor would we want 40 | * to rely on such knowledge for testing, since it may change). Therefore, our 41 | * custom allocator is configured to fail after 1, 2, 3, ... allocations, until 42 | * tph_poisson_create succeeds. This way all possible memory allocation failures 43 | * will be hit. 44 | * 45 | * Note that without this kind of exhaustive test it would be very difficult to 46 | * achieve full test code coverage. */ 47 | 48 | /* Configure arguments. */ 49 | const tph_poisson_real bounds_min[2] = { (tph_poisson_real)-10, (tph_poisson_real)-10 }; 50 | const tph_poisson_real bounds_max[2] = { (tph_poisson_real)10, (tph_poisson_real)10 }; 51 | const tph_poisson_args args = { .bounds_min = bounds_min, 52 | .bounds_max = bounds_max, 53 | .radius = (tph_poisson_real)1, 54 | .ndims = INT32_C(2), 55 | .max_sample_attempts = UINT32_C(30), 56 | .seed = UINT64_C(1981) }; 57 | 58 | /* Initialize empty sampling. */ 59 | tph_poisson_sampling sampling; 60 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 61 | 62 | /* Verify arguments using default allocator. Reset sampling. */ 63 | REQUIRE(tph_poisson_create(&args, /*alloc=*/NULL, &sampling) == TPH_POISSON_SUCCESS); 64 | tph_poisson_destroy(&sampling); 65 | 66 | int ret = TPH_POISSON_BAD_ALLOC; 67 | int i = 0; 68 | while (ret != TPH_POISSON_SUCCESS) { 69 | /* Use a custom allocator that will fail after 'i' allocations. Note that 70 | * when 'i' == 0 the first allocation fails. */ 71 | bad_alloc_ctx alloc_ctx = { .num_mallocs = 0, .max_mallocs = i }; 72 | tph_poisson_allocator alloc = { 73 | .malloc = bad_alloc_malloc, .free = bad_alloc_free, .ctx = &alloc_ctx 74 | }; 75 | 76 | /* Try to populate sampling with points. */ 77 | ret = tph_poisson_create(&args, &alloc, &sampling); 78 | REQUIRE(ret == TPH_POISSON_BAD_ALLOC || ret == TPH_POISSON_SUCCESS); 79 | ++i; 80 | } 81 | 82 | /* Free memory associated with sampling. */ 83 | tph_poisson_destroy(&sampling); 84 | } 85 | 86 | typedef struct destroyed_alloc_ctx_ 87 | { 88 | int num_mallocs; 89 | int num_frees; 90 | } destroyed_alloc_ctx; 91 | 92 | static void *destroyed_alloc_malloc(ptrdiff_t size, void *ctx) 93 | { 94 | if (size == 0) { return NULL; } 95 | void *ptr = malloc((size_t)(size)); 96 | destroyed_alloc_ctx *a_ctx = (destroyed_alloc_ctx *)ctx; 97 | ++a_ctx->num_mallocs; 98 | return ptr; 99 | } 100 | 101 | static void destroyed_alloc_free(void *ptr, ptrdiff_t size, void *ctx) 102 | { 103 | (void)size; 104 | if (ptr == NULL) { return; } 105 | destroyed_alloc_ctx *a_ctx = (destroyed_alloc_ctx *)ctx; 106 | ++a_ctx->num_frees; 107 | free(ptr); 108 | } 109 | 110 | static void test_destroyed_alloc(void) 111 | { 112 | /* Initialize empty sampling. */ 113 | tph_poisson_sampling sampling; 114 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 115 | 116 | /* Configure arguments. */ 117 | const tph_poisson_real bounds_min[2] = { (tph_poisson_real)-10, (tph_poisson_real)-10 }; 118 | const tph_poisson_real bounds_max[2] = { (tph_poisson_real)10, (tph_poisson_real)10 }; 119 | const tph_poisson_args args = { .bounds_min = bounds_min, 120 | .bounds_max = bounds_max, 121 | .radius = (tph_poisson_real)1, 122 | .ndims = INT32_C(2), 123 | .max_sample_attempts = UINT32_C(30), 124 | .seed = UINT64_C(1981) }; 125 | 126 | /* Set up a simple allocator that just counts number of allocations/deallocations. */ 127 | destroyed_alloc_ctx alloc_ctx = { .num_mallocs = 0, .num_frees = 0 }; 128 | tph_poisson_allocator *alloc = (tph_poisson_allocator *)malloc(sizeof(tph_poisson_allocator)); 129 | alloc->malloc = destroyed_alloc_malloc; 130 | alloc->free = destroyed_alloc_free; 131 | alloc->ctx = &alloc_ctx; 132 | 133 | const int ret = tph_poisson_create(&args, alloc, &sampling); 134 | REQUIRE(ret == TPH_POISSON_SUCCESS); 135 | 136 | /* Destroy the allocator before deallocating the sampling memory. The sampling 137 | * should have stored the malloc/free function pointers and context internally, 138 | * so there should be no errors. Note that the allocator context is not destroyed, 139 | * only the allocator. 140 | * 141 | * This prevent the implementation from storing a pointer directly to the allocator. 142 | * Instead the implementation should copy the malloc/free function 143 | * pointers (and context) so that it does not depend on the lifetime of the 144 | * allocator instance. */ 145 | free(alloc); 146 | 147 | /* Free memory associated with sampling. */ 148 | tph_poisson_destroy(&sampling); 149 | 150 | /* Note that we have not freed the allocator context memory, so we should 151 | * still be able to read from it. */ 152 | REQUIRE(alloc_ctx.num_mallocs > 0); 153 | REQUIRE(alloc_ctx.num_frees > 0); 154 | } 155 | 156 | int main(int argc, char *argv[]) 157 | { 158 | (void)argc; 159 | (void)argv; 160 | 161 | printf("test_bad_alloc...\n"); 162 | test_bad_alloc(); 163 | 164 | printf("test_destroyed_alloc...\n"); 165 | test_destroyed_alloc(); 166 | 167 | return EXIT_SUCCESS; 168 | } 169 | -------------------------------------------------------------------------------- /test/src/tph_poisson_f32.c: -------------------------------------------------------------------------------- 1 | #define TPH_POISSON_IMPLEMENTATION 2 | #include "tph_poisson_f32.h" 3 | 4 | /* Silence clang-tidy warning [misc-include-cleaner] */ 5 | typedef void *(*dummy)(tph_poisson_real); 6 | -------------------------------------------------------------------------------- /test/src/tph_poisson_f32.h: -------------------------------------------------------------------------------- 1 | #ifndef TPH_POISSON_F32_H 2 | #define TPH_POISSON_F32_H 3 | 4 | /* #define TPH_POISSON_REAL_TYPE float */ 5 | #include "thinks/tph_poisson.h" 6 | 7 | #endif /* TPH_POISSON_F32_H */ 8 | -------------------------------------------------------------------------------- /test/src/tph_poisson_f64.c: -------------------------------------------------------------------------------- 1 | #define TPH_POISSON_IMPLEMENTATION 2 | #include "tph_poisson_f64.h" 3 | 4 | /* Silence clang-tidy warning [misc-include-cleaner] */ 5 | typedef void *(*dummy)(tph_poisson_real); 6 | -------------------------------------------------------------------------------- /test/src/tph_poisson_f64.h: -------------------------------------------------------------------------------- 1 | #ifndef TPH_POISSON_F64_H 2 | #define TPH_POISSON_F64_H 3 | 4 | #include 5 | #define TPH_POISSON_REAL_TYPE double 6 | #define TPH_POISSON_SQRT sqrt 7 | #define TPH_POISSON_CEIL ceil 8 | #define TPH_POISSON_FLOOR floor 9 | 10 | #include "thinks/tph_poisson.h" 11 | 12 | #endif /* TPH_POISSON_F64_H */ 13 | -------------------------------------------------------------------------------- /test/src/tph_poisson_libc_test.c: -------------------------------------------------------------------------------- 1 | #include /* UINT64_C, etc */ 2 | #include /* printf */ 3 | #include /* malloc, free, EXIT_SUCCESS */ 4 | #include /* memset, memcpy */ 5 | 6 | static void *my_malloc(size_t size); 7 | static void my_free(void *ptr); 8 | static void *my_memcpy(void *dest, const void *src, size_t count); 9 | static void *my_memset(void *dest, int ch, size_t count); 10 | 11 | /* Provide custom libc functions for malloc, free, memcpy, and memset. */ 12 | #define TPH_POISSON_MALLOC my_malloc 13 | #define TPH_POISSON_FREE my_free 14 | #define TPH_POISSON_MEMCPY my_memcpy 15 | #define TPH_POISSON_MEMSET my_memset 16 | #define TPH_POISSON_IMPLEMENTATION 17 | #include "thinks/tph_poisson.h" 18 | 19 | #include "require.h" 20 | 21 | /* Global variables to count the number of calls to our custom libc functions. */ 22 | static int libc_malloc_calls = 0; 23 | static int libc_free_calls = 0; 24 | static int memcpy_calls = 0; 25 | static int memset_calls = 0; 26 | 27 | static void *my_malloc(size_t size) 28 | { 29 | ++libc_malloc_calls; 30 | return malloc(size); 31 | } 32 | 33 | static void my_free(void *ptr) 34 | { 35 | ++libc_free_calls; 36 | free(ptr); 37 | } 38 | 39 | static void *my_memcpy(void *dest, const void *src, size_t count) 40 | { 41 | ++memcpy_calls; 42 | return memcpy(dest, src, count); 43 | } 44 | 45 | static void *my_memset(void *dest, int ch, size_t count) 46 | { 47 | ++memset_calls; 48 | return memset(dest, ch, count); 49 | } 50 | 51 | /* Custom allocator used to ensure that no libc functions are called 52 | * when custom allocation is active. */ 53 | typedef struct my_tph_alloc_ctx_ 54 | { 55 | int malloc_calls; 56 | int free_calls; 57 | } my_tph_alloc_ctx; 58 | 59 | static void *my_tph_malloc(ptrdiff_t size, void *ctx) 60 | { 61 | my_tph_alloc_ctx *a_ctx = (my_tph_alloc_ctx *)ctx; 62 | ++a_ctx->malloc_calls; 63 | return malloc((size_t)size); 64 | } 65 | 66 | static void my_tph_free(void *ptr, ptrdiff_t size, void *ctx) 67 | { 68 | (void)size; 69 | my_tph_alloc_ctx *a_ctx = (my_tph_alloc_ctx *)ctx; 70 | ++a_ctx->free_calls; 71 | free(ptr); 72 | } 73 | 74 | /* ---------- */ 75 | 76 | static void test_custom_libc(void) 77 | { 78 | /* Configure arguments. */ 79 | const tph_poisson_real bounds_min[2] = { (tph_poisson_real)-5, (tph_poisson_real)-5 }; 80 | const tph_poisson_real bounds_max[2] = { (tph_poisson_real)5, (tph_poisson_real)5 }; 81 | const tph_poisson_args args = { .bounds_min = bounds_min, 82 | .bounds_max = bounds_max, 83 | .radius = (tph_poisson_real)0.37, 84 | .ndims = INT32_C(2), 85 | .max_sample_attempts = UINT32_C(23), 86 | .seed = UINT64_C(2015) }; 87 | 88 | /* Initialize empty sampling. Use libc memset here. */ 89 | tph_poisson_sampling sampling; 90 | memset(&sampling, 0, sizeof(tph_poisson_sampling)); 91 | 92 | /* Populate sampling with points using default allocator. This should call 93 | * our custom libc functions. */ 94 | libc_malloc_calls = 0; 95 | libc_free_calls = 0; 96 | memcpy_calls = 0; 97 | memset_calls = 0; 98 | int ret = tph_poisson_create(&args, /*alloc=*/NULL, &sampling); 99 | REQUIRE(ret == TPH_POISSON_SUCCESS); 100 | REQUIRE(tph_poisson_get_samples(&sampling) != NULL); 101 | tph_poisson_destroy(&sampling); 102 | REQUIRE(libc_malloc_calls > 0); 103 | REQUIRE(libc_free_calls > 0); 104 | REQUIRE(memcpy_calls > 0); 105 | REQUIRE(memset_calls > 0); 106 | const int num_default_malloc = libc_malloc_calls; 107 | const int num_default_free = libc_free_calls; 108 | const int num_default_memcpy = memcpy_calls; 109 | const int num_default_memset = memset_calls; 110 | 111 | /* Populate sampling again with points using a custom allocator. This should NOT 112 | * call our custom libc functions. */ 113 | my_tph_alloc_ctx my_alloc_ctx = { .malloc_calls = 0, .free_calls = 0 }; 114 | tph_poisson_allocator my_alloc = { 115 | .malloc = my_tph_malloc, .free = my_tph_free, .ctx = &my_alloc_ctx 116 | }; 117 | ret = tph_poisson_create(&args, &my_alloc, &sampling); 118 | REQUIRE(ret == TPH_POISSON_SUCCESS); 119 | REQUIRE(tph_poisson_get_samples(&sampling) != NULL); 120 | tph_poisson_destroy(&sampling); 121 | 122 | /* Unchanged. */ 123 | REQUIRE(libc_malloc_calls == num_default_malloc); 124 | REQUIRE(libc_free_calls == num_default_free); 125 | 126 | /* Same number of calls. */ 127 | REQUIRE(num_default_malloc == my_alloc_ctx.malloc_calls); 128 | REQUIRE(num_default_free == my_alloc_ctx.free_calls); 129 | 130 | /* Custom allocator still uses overriden memcpy/memset. */ 131 | REQUIRE(memcpy_calls == 2 * num_default_memcpy); 132 | REQUIRE(memset_calls == 2 * num_default_memset); 133 | } 134 | 135 | int main(int argc, char *argv[]) 136 | { 137 | (void)argc; 138 | (void)argv; 139 | 140 | printf("test_custom_libc...\n"); 141 | test_custom_libc(); 142 | 143 | return EXIT_SUCCESS; 144 | } 145 | -------------------------------------------------------------------------------- /test/src/tph_poisson_test.cpp: -------------------------------------------------------------------------------- 1 | #include // std::all_of 2 | #include 3 | #include // PRIXPTR 4 | #include 5 | #include // int32_t, etc 6 | #include // std::printf 7 | #include // EXIT_SUCCESS 8 | #include // std::memcmp 9 | #include // std::function 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef TPH_POISSON_TEST_USE_F64 17 | #include "tph_poisson_f64.h" 18 | static_assert(std::is_same_v); 19 | #else 20 | #include "tph_poisson_f32.h" 21 | static_assert(std::is_same_v); 22 | #endif 23 | 24 | #include "require.h" 25 | 26 | using Real = TPH_POISSON_REAL_TYPE; 27 | 28 | using unique_poisson_ptr = 29 | std::unique_ptr>; 30 | static auto make_unique_poisson() -> unique_poisson_ptr 31 | { 32 | return unique_poisson_ptr(new tph_poisson_sampling{}, [](tph_poisson_sampling *s) { 33 | tph_poisson_destroy(s); 34 | delete s; 35 | }); 36 | } 37 | 38 | // Brute-force (with some tricks) verification that the distance between each possible 39 | // sample pair meets the Poisson requirement, i.e. is greater than some radius. 40 | static void TestRadius() 41 | { 42 | const auto valid_radius = [](const std::vector bounds_min, 43 | const std::vector bounds_max, 44 | tph_poisson_allocator *alloc) { 45 | if (bounds_min.size() != bounds_max.size()) { return false; } 46 | tph_poisson_args args = {}; 47 | args.ndims = static_cast(bounds_min.size()); 48 | args.bounds_min = bounds_min.data(); 49 | args.bounds_max = bounds_max.data(); 50 | args.radius = 2; 51 | args.seed = UINT64_C(1981); 52 | args.max_sample_attempts = UINT32_C(30); 53 | unique_poisson_ptr sampling = make_unique_poisson(); 54 | if (tph_poisson_create(&args, alloc, sampling.get()) != TPH_POISSON_SUCCESS) { return false; } 55 | const tph_poisson_real *samples = tph_poisson_get_samples(sampling.get()); 56 | if (samples == nullptr) { return false; } 57 | 58 | // Setup threading. 59 | // Avoid spawning more threads than there are samples (although very unlikely). 60 | const ptrdiff_t thread_count = 61 | std::thread::hardware_concurrency() > 0 62 | ? std::min(static_cast(std::thread::hardware_concurrency()), sampling->nsamples) 63 | : static_cast(1); 64 | 65 | // Launch threads. 66 | std::vector> futures; 67 | for (ptrdiff_t i = 0; i < thread_count; ++i) { 68 | futures.emplace_back(std::async(std::launch::async, 69 | [i, 70 | samples, 71 | thread_count, 72 | ndims = sampling->ndims, 73 | nsamples = sampling->nsamples, 74 | r = args.radius]() { 75 | // We know that distance is symmetrical, such that 76 | // dist(s[j], s[k]) == dist(s[k], s[j]). Therefore 77 | // we need only compute the upper half of the matrix (excluding the diagonal). 78 | // 79 | // Load balance threads such that "short" (small j) and "long" (large j) 80 | // columns are divided evenly among threads. 81 | const Real r_sqr = r * r; 82 | for (ptrdiff_t j = i; j < nsamples; j += thread_count) { 83 | const Real *sj = &samples[j * ndims]; 84 | const ptrdiff_t k_max = j; 85 | for (ptrdiff_t k = 0; k < k_max; ++k) { 86 | const Real *sk = &samples[k * ndims]; 87 | Real dist_sqr = 0; 88 | for (int32_t m = 0; m < ndims; ++m) { dist_sqr += (sj[m] - sk[m]) * (sj[m] - sk[m]); } 89 | if (!(dist_sqr > r_sqr)) { return false; } 90 | } 91 | } 92 | return true; 93 | })); 94 | } 95 | 96 | // Check results. 97 | for (auto &&f : futures) { f.wait(); } 98 | return std::all_of( 99 | std::begin(futures), std::end(futures), [](std::future &f) { return f.get(); }); 100 | }; 101 | 102 | constexpr tph_poisson_allocator *alloc = nullptr; 103 | REQUIRE(valid_radius(/*bounds_min=*/{ -100, -100 }, /*bounds_max=*/{ 100, 100 }, alloc)); 104 | REQUIRE(valid_radius({ -20, -20, -20 }, { 20, 20, 20 }, alloc)); 105 | REQUIRE(valid_radius({ -10, -10, -10, -10 }, { 10, 10, 10, 10 }, alloc)); 106 | } 107 | 108 | // Verify that all samples are within the specified bounds. 109 | static void TestBounds() 110 | { 111 | const auto valid_bounds = [](const std::vector bounds_min, 112 | const std::vector bounds_max, 113 | tph_poisson_allocator *alloc) { 114 | if (bounds_min.size() != bounds_max.size()) { return false; } 115 | tph_poisson_args args = {}; 116 | args.ndims = static_cast(bounds_min.size()); 117 | args.bounds_min = bounds_min.data(); 118 | args.bounds_max = bounds_max.data(); 119 | args.radius = 2; 120 | args.seed = UINT64_C(1981); 121 | args.max_sample_attempts = UINT32_C(30); 122 | unique_poisson_ptr sampling = make_unique_poisson(); 123 | if (tph_poisson_create(&args, alloc, sampling.get()) != TPH_POISSON_SUCCESS) { return false; } 124 | const tph_poisson_real *samples = tph_poisson_get_samples(sampling.get()); 125 | if (samples == nullptr) { return false; } 126 | for (ptrdiff_t i = 0; i < sampling->nsamples; ++i) { 127 | const Real *p = &samples[i * sampling->ndims]; 128 | for (int32_t j = 0; j < sampling->ndims; ++j) { 129 | const size_t jj = static_cast(j); 130 | // clang-format off 131 | if ((static_cast(p[jj] >= bounds_min[jj]) & 132 | static_cast(p[jj] <= bounds_max[jj])) == 0) { 133 | return false; 134 | } 135 | // clang-format on 136 | } 137 | } 138 | return true; 139 | }; 140 | 141 | constexpr tph_poisson_allocator *alloc = nullptr; 142 | REQUIRE(valid_bounds(/*bounds_min=*/{ -100, -100 }, /*bounds_max=*/{ 100, 100 }, alloc)); 143 | REQUIRE(valid_bounds({ -20, -20, -20 }, { 20, 20, 20 }, alloc)); 144 | REQUIRE(valid_bounds({ -10, -10, -10, -10 }, { 10, 10, 10, 10 }, alloc)); 145 | } 146 | 147 | // Verify that we get a denser sampling, i.e. more samples, 148 | // when we increase the max sample attempts parameter (with 149 | // all other parameters constant). 150 | static void TestVaryingMaxSampleAttempts() 151 | { 152 | constexpr int32_t ndims = INT32_C(2); 153 | constexpr std::array bounds_min{ -10, -10 }; 154 | constexpr std::array bounds_max{ 10, 10 }; 155 | constexpr tph_poisson_allocator *alloc = nullptr; 156 | 157 | tph_poisson_args args_10 = {}; 158 | args_10.ndims = ndims; 159 | args_10.radius = static_cast(0.5); 160 | args_10.bounds_min = bounds_min.data(); 161 | args_10.bounds_max = bounds_max.data(); 162 | args_10.seed = UINT64_C(1981); 163 | args_10.max_sample_attempts = UINT32_C(10); 164 | tph_poisson_args args_40 = args_10; 165 | args_40.max_sample_attempts = UINT32_C(40); 166 | 167 | unique_poisson_ptr sampling_10 = make_unique_poisson(); 168 | unique_poisson_ptr sampling_40 = make_unique_poisson(); 169 | REQUIRE(TPH_POISSON_SUCCESS == tph_poisson_create(&args_10, alloc, sampling_10.get())); 170 | REQUIRE(TPH_POISSON_SUCCESS == tph_poisson_create(&args_40, alloc, sampling_40.get())); 171 | 172 | REQUIRE(sampling_10->nsamples < sampling_40->nsamples); 173 | } 174 | 175 | // Verify that different seeds give different sample distributions (with 176 | // all other parameters constant). 177 | static void TestVaryingSeed() 178 | { 179 | constexpr int32_t ndims = INT32_C(2); 180 | constexpr std::array bounds_min{ -10, -10 }; 181 | constexpr std::array bounds_max{ 10, 10 }; 182 | constexpr tph_poisson_allocator *alloc = nullptr; 183 | 184 | tph_poisson_args args_1981 = {}; 185 | args_1981.ndims = ndims; 186 | args_1981.radius = static_cast(0.5); 187 | args_1981.bounds_min = bounds_min.data(); 188 | args_1981.bounds_max = bounds_max.data(); 189 | args_1981.max_sample_attempts = UINT32_C(20); 190 | args_1981.seed = UINT64_C(1981); 191 | tph_poisson_args args_1337 = args_1981; 192 | args_1337.seed = UINT64_C(1337); 193 | 194 | unique_poisson_ptr sampling_1981 = make_unique_poisson(); 195 | unique_poisson_ptr sampling_1337 = make_unique_poisson(); 196 | REQUIRE(TPH_POISSON_SUCCESS == tph_poisson_create(&args_1981, alloc, sampling_1981.get())); 197 | REQUIRE(TPH_POISSON_SUCCESS == tph_poisson_create(&args_1337, alloc, sampling_1337.get())); 198 | 199 | const tph_poisson_real *samples_1981 = tph_poisson_get_samples(sampling_1981.get()); 200 | const tph_poisson_real *samples_1337 = tph_poisson_get_samples(sampling_1337.get()); 201 | REQUIRE(samples_1981 != nullptr); 202 | REQUIRE(samples_1337 != nullptr); 203 | 204 | // For each sample in the first point set compute the smallest 205 | // distance checking every sample in the second point set. 206 | // Then, if the smallest distance is larger than some threshold 207 | // we say that the sample from the first point set is distinct 208 | // from every sample in the second point set. Thus the two 209 | // distributions must be different. 210 | REQUIRE([&]() { 211 | for (ptrdiff_t i = 0; i < sampling_1981->nsamples; ++i) { 212 | const Real *p = &samples_1981[i * sampling_1981->ndims]; 213 | Real min_sqr_dist = std::numeric_limits::max(); 214 | for (ptrdiff_t j = 0; j < sampling_1337->nsamples; ++j) { 215 | const Real *q = &samples_1337[j * sampling_1337->ndims]; 216 | Real sqr_dist = 0; 217 | for (int32_t k = 0; k < ndims; ++k) { sqr_dist += (p[k] - q[k]) * (p[k] - q[k]); } 218 | min_sqr_dist = std::min(min_sqr_dist, sqr_dist); 219 | } 220 | if (std::sqrt(min_sqr_dist) > static_cast(0.1)) { return true; } 221 | } 222 | return false; 223 | }()); 224 | } 225 | 226 | static void TestInvalidArgs() 227 | { 228 | // Work-around for MSVC compiler not being able to handle constexpr 229 | // capture in lambdas. 230 | // constexpr int32_t ndims = 2; 231 | #define NDIMS INT32_C(2) 232 | 233 | constexpr std::array bounds_min{ -10, -10 }; 234 | constexpr std::array bounds_max{ 10, 10 }; 235 | constexpr tph_poisson_allocator *alloc = nullptr; 236 | 237 | tph_poisson_args valid_args = {}; 238 | valid_args.radius = 1; 239 | valid_args.ndims = NDIMS; 240 | valid_args.bounds_min = bounds_min.data(); 241 | valid_args.bounds_max = bounds_max.data(); 242 | valid_args.max_sample_attempts = UINT32_C(30); 243 | valid_args.seed = UINT64_C(333); 244 | // Verify valid arguments. 245 | unique_poisson_ptr sampling = make_unique_poisson(); 246 | REQUIRE(TPH_POISSON_SUCCESS == tph_poisson_create(&valid_args, alloc, sampling.get())); 247 | REQUIRE(sampling->ndims == NDIMS); 248 | REQUIRE(sampling->nsamples > 0); 249 | REQUIRE(tph_poisson_get_samples(sampling.get()) != nullptr); 250 | // Reset sampling. 251 | sampling = make_unique_poisson(); 252 | REQUIRE(sampling->ndims == 0); 253 | REQUIRE(sampling->nsamples == 0); 254 | 255 | const char *func = TPH_PRETTY_FUNCTION; 256 | 257 | // sampling == NULL 258 | [&] { 259 | tph_poisson_args args = valid_args; 260 | REQUIRE_F( 261 | TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, /*sampling=*/nullptr), func); 262 | }(); 263 | 264 | // Incomplete (custom) allocator. 265 | [&] { 266 | tph_poisson_args args = valid_args; 267 | tph_poisson_allocator incomplete_alloc = {}; 268 | REQUIRE_F(incomplete_alloc.malloc == nullptr, func); 269 | REQUIRE_F(incomplete_alloc.free == nullptr, func); 270 | REQUIRE_F( 271 | TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, &incomplete_alloc, sampling.get()), 272 | func); 273 | }(); 274 | [&] { 275 | tph_poisson_args args = valid_args; 276 | tph_poisson_allocator incomplete_alloc = {}; 277 | incomplete_alloc.malloc = dummy_malloc; 278 | REQUIRE_F(incomplete_alloc.free == nullptr, func); 279 | REQUIRE_F( 280 | TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, &incomplete_alloc, sampling.get()), 281 | func); 282 | }(); 283 | [&] { 284 | tph_poisson_args args = valid_args; 285 | tph_poisson_allocator incomplete_alloc = {}; 286 | incomplete_alloc.free = dummy_free; 287 | REQUIRE_F(incomplete_alloc.malloc == nullptr, func); 288 | REQUIRE_F( 289 | TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, &incomplete_alloc, sampling.get()), 290 | func); 291 | }(); 292 | 293 | // args == NULL 294 | [&] { 295 | REQUIRE_F( 296 | TPH_POISSON_INVALID_ARGS == tph_poisson_create(/*args=*/nullptr, alloc, sampling.get()), 297 | func); 298 | }(); 299 | 300 | // radius <= 0 301 | [&] { 302 | tph_poisson_args args = valid_args; 303 | args.radius = 0; 304 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 305 | 306 | args.radius = -1; 307 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 308 | }(); 309 | 310 | // ndims <= 0 311 | [&] { 312 | tph_poisson_args args = valid_args; 313 | args.ndims = 0; 314 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 315 | 316 | args.ndims = -1; 317 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 318 | }(); 319 | 320 | // max_sample_attempts == 0 321 | [&] { 322 | tph_poisson_args args = valid_args; 323 | args.max_sample_attempts = 0; 324 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 325 | }(); 326 | 327 | // bounds_min >= bounds_max 328 | [&] { 329 | tph_poisson_args args = valid_args; 330 | args.bounds_min = nullptr; 331 | args.bounds_max = nullptr; 332 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 333 | 334 | constexpr std::array invalid_bounds_min0{ 10, 10 }; 335 | constexpr std::array invalid_bounds_max0{ 10, 10 }; 336 | args.bounds_min = invalid_bounds_min0.data(); 337 | args.bounds_max = invalid_bounds_max0.data(); 338 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 339 | 340 | constexpr std::array invalid_bounds_min1{ 10, -10 }; 341 | constexpr std::array invalid_bounds_max1{ 10, 10 }; 342 | args.bounds_min = invalid_bounds_min1.data(); 343 | args.bounds_max = invalid_bounds_max1.data(); 344 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 345 | 346 | constexpr std::array invalid_bounds_min2{ -10, 10 }; 347 | constexpr std::array invalid_bounds_max2{ 10, 10 }; 348 | args.bounds_min = invalid_bounds_min2.data(); 349 | args.bounds_max = invalid_bounds_max2.data(); 350 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 351 | 352 | constexpr std::array invalid_bounds_min3{ 10, 10 }; 353 | constexpr std::array invalid_bounds_max3{ -10, -10 }; 354 | args.bounds_min = invalid_bounds_min3.data(); 355 | args.bounds_max = invalid_bounds_max3.data(); 356 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 357 | 358 | constexpr std::array invalid_bounds_min4{ 10, -10 }; 359 | constexpr std::array invalid_bounds_max4{ -10, 10 }; 360 | args.bounds_min = invalid_bounds_min4.data(); 361 | args.bounds_max = invalid_bounds_max4.data(); 362 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 363 | 364 | constexpr std::array invalid_bounds_min5{ -10, 10 }; 365 | constexpr std::array invalid_bounds_max5{ 10, -10 }; 366 | args.bounds_min = invalid_bounds_min5.data(); 367 | args.bounds_max = invalid_bounds_max5.data(); 368 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 369 | 370 | // clang-format off 371 | constexpr std::array invalid_bounds_min6{ 372 | -10, std::numeric_limits::quiet_NaN() }; 373 | constexpr std::array invalid_bounds_max6{ 10, 10 }; 374 | args.bounds_min = invalid_bounds_min6.data(); 375 | args.bounds_max = invalid_bounds_max6.data(); 376 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 377 | 378 | constexpr std::array invalid_bounds_min7{ 379 | std::numeric_limits::quiet_NaN(), -10 }; 380 | constexpr std::array invalid_bounds_max7{ 10, 10 }; 381 | args.bounds_min = invalid_bounds_min7.data(); 382 | args.bounds_max = invalid_bounds_max7.data(); 383 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 384 | 385 | constexpr std::array invalid_bounds_min8{ -10, -10 }; 386 | constexpr std::array invalid_bounds_max8{ 387 | std::numeric_limits::quiet_NaN(), 10 }; 388 | args.bounds_min = invalid_bounds_min8.data(); 389 | args.bounds_max = invalid_bounds_max8.data(); 390 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 391 | 392 | constexpr std::array invalid_bounds_min9{ -10, -10 }; 393 | constexpr std::array invalid_bounds_max9{ 394 | 10, std::numeric_limits::quiet_NaN()}; 395 | args.bounds_min = invalid_bounds_min9.data(); 396 | args.bounds_max = invalid_bounds_max9.data(); 397 | REQUIRE_F(TPH_POISSON_INVALID_ARGS == tph_poisson_create(&args, alloc, sampling.get()), func); 398 | // clang-format on 399 | }(); 400 | 401 | // Verify that no output was written by failed attempts. 402 | REQUIRE(sampling->ndims == 0); 403 | REQUIRE(sampling->nsamples == 0); 404 | REQUIRE(tph_poisson_get_samples(sampling.get()) == nullptr); 405 | #undef NDIMS 406 | } 407 | 408 | static void TestDestroy() 409 | { 410 | // NOTE: Also verifies correct behaviour of tph_poisson_get_samples(). 411 | 412 | constexpr int32_t ndims = 2; 413 | constexpr std::array bounds_min{ -10, -10 }; 414 | constexpr std::array bounds_max{ 10, 10 }; 415 | constexpr tph_poisson_allocator *alloc = nullptr; 416 | 417 | tph_poisson_args valid_args = {}; 418 | valid_args.radius = 1; 419 | valid_args.ndims = ndims; 420 | valid_args.bounds_min = bounds_min.data(); 421 | valid_args.bounds_max = bounds_max.data(); 422 | valid_args.max_sample_attempts = UINT32_C(30); 423 | valid_args.seed = UINT64_C(333); 424 | 425 | // Zero-initialized sampling has no samples. 426 | tph_poisson_sampling sampling = {}; 427 | constexpr uint8_t zeros[sizeof(tph_poisson_sampling)] = { 0 }; 428 | REQUIRE(std::memcmp(&sampling, zeros, sizeof(tph_poisson_sampling)) == 0); 429 | REQUIRE(tph_poisson_get_samples(&sampling) == nullptr); 430 | 431 | // Successfully created sampling has samples. 432 | REQUIRE(TPH_POISSON_SUCCESS == tph_poisson_create(&valid_args, alloc, &sampling)); 433 | REQUIRE(tph_poisson_get_samples(&sampling) != nullptr); 434 | 435 | // Destroyed sampling has no samples. 436 | tph_poisson_destroy(&sampling); 437 | REQUIRE(tph_poisson_get_samples(&sampling) == nullptr); 438 | 439 | // "No" sampling has no samples... 440 | REQUIRE(tph_poisson_get_samples(/*sampling=*/nullptr) == nullptr); 441 | 442 | // Double create without intermediate destroy. 443 | REQUIRE(TPH_POISSON_SUCCESS == tph_poisson_create(&valid_args, alloc, &sampling)); 444 | REQUIRE(tph_poisson_get_samples(&sampling) != nullptr); 445 | REQUIRE(TPH_POISSON_SUCCESS == tph_poisson_create(&valid_args, alloc, &sampling)); 446 | REQUIRE(tph_poisson_get_samples(&sampling) != nullptr); 447 | 448 | // Double destroy (no crash). 449 | tph_poisson_destroy(&sampling); 450 | REQUIRE(tph_poisson_get_samples(&sampling) == nullptr); 451 | tph_poisson_destroy(&sampling); 452 | REQUIRE(tph_poisson_get_samples(&sampling) == nullptr); 453 | } 454 | 455 | int main(int /*argc*/, char * /*argv*/[]) 456 | { 457 | std::printf("TestInvalidArgs...\n"); 458 | TestInvalidArgs(); 459 | 460 | std::printf("TestRadius...\n"); 461 | TestRadius(); 462 | 463 | std::printf("TestBounds...\n"); 464 | TestBounds(); 465 | 466 | std::printf("TestVaryingMaxSampleAttempts...\n"); 467 | TestVaryingMaxSampleAttempts(); 468 | 469 | std::printf("TestVaryingSeed...\n"); 470 | TestVaryingSeed(); 471 | 472 | std::printf("TestDestroy...\n"); 473 | TestDestroy(); 474 | 475 | return EXIT_SUCCESS; 476 | } 477 | -------------------------------------------------------------------------------- /test/src/tph_poisson_vec_test.c: -------------------------------------------------------------------------------- 1 | #include /* PRIXPTR, etc */ 2 | #include /* bool, true, false */ 3 | #include /* printf */ 4 | #include /* malloc, free, EXIT_SUCCESS */ 5 | 6 | #define TPH_POISSON_IMPLEMENTATION 7 | #include "thinks/tph_poisson.h" 8 | 9 | #include "require.h" 10 | 11 | typedef struct vec_test_alloc_ctx_ 12 | { 13 | ptrdiff_t align_offset; /** [bytes] */ 14 | int fail; /** When non-zero vec_test_malloc returns NULL. */ 15 | } vec_test_alloc_ctx; 16 | 17 | static void *vec_test_malloc(ptrdiff_t size, void *ctx) 18 | { 19 | vec_test_alloc_ctx *a_ctx = (vec_test_alloc_ctx *)ctx; 20 | if ((size == 0) | (a_ctx->fail != 0)) { return NULL; } 21 | void *ptr = malloc((size_t)(size + a_ctx->align_offset)); 22 | 23 | /* cppcheck-suppress memleak */ 24 | return (void *)((intptr_t)ptr + a_ctx->align_offset); 25 | } 26 | 27 | static void vec_test_free(void *ptr, ptrdiff_t size, void *ctx) 28 | { 29 | (void)size; 30 | if (ptr == NULL) { return; } 31 | vec_test_alloc_ctx *a_ctx = (vec_test_alloc_ctx *)ctx; 32 | free((void *)((intptr_t)ptr - a_ctx->align_offset)); 33 | } 34 | 35 | #define VEC_TEST_SIZEOF(_X_) (ptrdiff_t)sizeof(_X_) 36 | #define VEC_TEST_ALIGNOF(_X_) (ptrdiff_t)alignof(_X_) 37 | 38 | static bool flt_eq(const float *a, const float *b) 39 | { 40 | return memcmp((const void *)a, (const void *)b, sizeof(float)) == 0 ? true : false; 41 | } 42 | 43 | static bool is_zeros(const void *const mem, const ptrdiff_t n) 44 | { 45 | static const uint8_t test_block[128] = { 0 }; 46 | assert(n > 0); 47 | assert(n <= 128); 48 | return memcmp((const void *)test_block, mem, (size_t)n) == 0; 49 | } 50 | 51 | /* ------------------------- */ 52 | 53 | /* This function is reasonable for a vector, but is not required by the implementation. 54 | * It is provided here since it is useful for testing purposes. */ 55 | static inline ptrdiff_t tph_poisson_vec_capacity(const tph_poisson_vec *vec) 56 | { 57 | assert(vec != NULL); 58 | assert(vec->mem_size >= 0); 59 | assert((intptr_t)vec->begin >= (intptr_t)vec->mem); 60 | 61 | /* NOTE: Account for possible alignment correction when computing capacity! */ 62 | return (ptrdiff_t)(vec->mem_size - ((intptr_t)vec->begin - (intptr_t)vec->mem)); 63 | } 64 | 65 | #if 0 66 | static void print_my_vec_f(const tph_poisson_vec *vec) 67 | { 68 | /* clang-format off */ 69 | printf("mem = 0x%" PRIXPTR "\n" 70 | "mem_size = %td [bytes]\n" 71 | "begin = 0x%" PRIXPTR "\n" 72 | "end = 0x%" PRIXPTR "\n" 73 | "size = %td, %td [bytes]\n" 74 | "cap = %td, %td [bytes]\n", 75 | (uintptr_t)vec->mem, 76 | vec->mem_size, 77 | (uintptr_t)vec->begin, 78 | (uintptr_t)vec->end, 79 | tph_poisson_vec_size(vec) / VEC_TEST_SIZEOF(float), tph_poisson_vec_size(vec), 80 | tph_poisson_vec_capacity(vec) / VEC_TEST_SIZEOF(float), tph_poisson_vec_capacity(vec)); 81 | /* clang-format on */ 82 | printf("data = [ "); 83 | const float *iter = (const float *)vec->begin; 84 | const float *const iend = (const float *)vec->end; 85 | for (; iter != iend; ++iter) { printf("%.3f%s", (double)*iter, iter + 1 == iend ? "" : ", "); } 86 | printf(" ]\n\n"); 87 | } 88 | #endif 89 | 90 | static bool valid_invariants(tph_poisson_vec *vec, const ptrdiff_t alignment) 91 | { 92 | if (vec == NULL) { return false; } 93 | 94 | /* Zero-initialized. */ 95 | if (is_zeros(vec, VEC_TEST_SIZEOF(tph_poisson_vec))) { return true; } 96 | 97 | /* clang-format off */ 98 | if (!(vec->mem != NULL && 99 | vec->mem_size > 0 && 100 | (intptr_t)vec->begin >= (intptr_t)vec->mem && 101 | (intptr_t)vec->end >= (intptr_t)vec->begin && 102 | (intptr_t)vec->begin % alignment == 0)) { 103 | return false; 104 | } 105 | /* clang-format on */ 106 | 107 | if (!(tph_poisson_vec_capacity(vec) >= tph_poisson_vec_size(vec))) { return false; } 108 | 109 | return true; 110 | } 111 | 112 | static void test_reserve(void) 113 | { 114 | for (size_t i = 0; i < sizeof(float); ++i) { 115 | /* Create an allocator that returns misaligned memory (except when i == 0). */ 116 | vec_test_alloc_ctx alloc_ctx = { .align_offset = (ptrdiff_t)i }; 117 | tph_poisson_allocator alloc = { 118 | .malloc = vec_test_malloc, .free = vec_test_free, .ctx = &alloc_ctx 119 | }; 120 | const float values[] = { 0.F, 1.F, 13.F, 42.F, 33.F, 18.F, -34.F }; 121 | const ptrdiff_t extra_cap = 122 | alloc_ctx.align_offset == 0 ? VEC_TEST_ALIGNOF(float) : alloc_ctx.align_offset; 123 | 124 | { 125 | /* Reserve on zero-initialized vector. No existing capacity. */ 126 | tph_poisson_vec vec; 127 | memset(&vec, 0, sizeof(tph_poisson_vec)); 128 | 129 | REQUIRE(tph_poisson_vec_reserve( 130 | &vec, &alloc, /*new_cap=*/VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 131 | == TPH_POISSON_SUCCESS); 132 | 133 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 134 | REQUIRE(tph_poisson_vec_size(&vec) == 0); 135 | REQUIRE(tph_poisson_vec_capacity(&vec) == VEC_TEST_SIZEOF(values) + extra_cap); 136 | 137 | tph_poisson_vec_free(&vec, &alloc); 138 | } 139 | 140 | { 141 | /* Reserve less than existing capacity is a no-op. */ 142 | tph_poisson_vec vec; 143 | memset(&vec, 0, sizeof(tph_poisson_vec)); 144 | 145 | REQUIRE(tph_poisson_vec_reserve( 146 | &vec, &alloc, /*new_cap=*/VEC_TEST_SIZEOF(float), VEC_TEST_ALIGNOF(float)) 147 | == TPH_POISSON_SUCCESS); 148 | uint8_t vec0[sizeof(tph_poisson_vec)]; 149 | memcpy(vec0, &vec, sizeof(tph_poisson_vec)); 150 | 151 | REQUIRE(tph_poisson_vec_reserve(&vec, 152 | &alloc, 153 | /*new_cap=*/VEC_TEST_SIZEOF(float) / 2, 154 | VEC_TEST_ALIGNOF(float)) 155 | == TPH_POISSON_SUCCESS); 156 | 157 | REQUIRE(memcmp(vec0, &vec, sizeof(tph_poisson_vec)) == 0); 158 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 159 | 160 | tph_poisson_vec_free(&vec, &alloc); 161 | } 162 | 163 | { 164 | /* Append some values and then reserve a larger buffer. Check that existing 165 | values are copied to new buffer. */ 166 | tph_poisson_vec vec; 167 | memset(&vec, 0, sizeof(tph_poisson_vec)); 168 | 169 | REQUIRE(tph_poisson_vec_append( 170 | &vec, &alloc, values, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 171 | == TPH_POISSON_SUCCESS); 172 | 173 | REQUIRE(tph_poisson_vec_reserve(&vec, 174 | &alloc, 175 | /*new_cap=*/VEC_TEST_SIZEOF(values) + 3 * VEC_TEST_SIZEOF(values), 176 | VEC_TEST_ALIGNOF(float)) 177 | == TPH_POISSON_SUCCESS); 178 | 179 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 180 | REQUIRE(tph_poisson_vec_capacity(&vec) 181 | == VEC_TEST_SIZEOF(values) + 3 * VEC_TEST_SIZEOF(values) + extra_cap); 182 | REQUIRE(memcmp((const void *)vec.begin, (const void *)values, VEC_TEST_SIZEOF(values)) == 0); 183 | 184 | tph_poisson_vec_free(&vec, &alloc); 185 | } 186 | 187 | { 188 | /* Reserve additional capacity for a vector with existing capacity but no values. */ 189 | tph_poisson_vec vec; 190 | memset(&vec, 0, sizeof(tph_poisson_vec)); 191 | 192 | tph_poisson_vec_append( 193 | &vec, &alloc, values, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)); 194 | tph_poisson_vec_erase_swap(&vec, 0, VEC_TEST_SIZEOF(values)); 195 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 196 | REQUIRE(tph_poisson_vec_size(&vec) == 0); 197 | REQUIRE(tph_poisson_vec_capacity(&vec) == VEC_TEST_SIZEOF(values) + extra_cap); 198 | 199 | REQUIRE(tph_poisson_vec_reserve(&vec, 200 | &alloc, 201 | /*new_cap=*/2 * VEC_TEST_SIZEOF(values), 202 | VEC_TEST_ALIGNOF(float)) 203 | == TPH_POISSON_SUCCESS); 204 | 205 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 206 | tph_poisson_vec_free(&vec, &alloc); 207 | } 208 | 209 | { 210 | /* Check that bad allocation propagates to caller. */ 211 | tph_poisson_vec vec; 212 | memset(&vec, 0, sizeof(tph_poisson_vec)); 213 | 214 | alloc_ctx.fail = 1; 215 | REQUIRE(tph_poisson_vec_reserve(&vec, 216 | &alloc, 217 | /*new_cap=*/VEC_TEST_SIZEOF(float), 218 | VEC_TEST_ALIGNOF(float)) 219 | == TPH_POISSON_BAD_ALLOC); 220 | alloc_ctx.fail = 0; 221 | 222 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 223 | REQUIRE(is_zeros((const void *)&vec, sizeof(tph_poisson_vec))); 224 | /* No need to free the vector! */ 225 | } 226 | } 227 | } 228 | 229 | static void test_append(void) 230 | { 231 | for (size_t i = 0; i < sizeof(float); ++i) { 232 | /* Create an allocator that returns misaligned memory (except when i == 0). */ 233 | vec_test_alloc_ctx alloc_ctx = { .align_offset = (ptrdiff_t)i }; 234 | tph_poisson_allocator alloc = { 235 | .malloc = vec_test_malloc, .free = vec_test_free, .ctx = &alloc_ctx 236 | }; 237 | const float values[] = { 0.F, 1.F, 13.F, 42.F, 33.F, 18.F, -34.F }; 238 | 239 | { 240 | /* Append to zero-initialized vector. No existing capacity. */ 241 | tph_poisson_vec vec; 242 | memset(&vec, 0, sizeof(tph_poisson_vec)); 243 | 244 | REQUIRE(tph_poisson_vec_append( 245 | &vec, &alloc, values, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 246 | == TPH_POISSON_SUCCESS); 247 | 248 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 249 | REQUIRE(tph_poisson_vec_size(&vec) == VEC_TEST_SIZEOF(values)); 250 | REQUIRE(memcmp((const void *)vec.begin, (const void *)values, sizeof(values)) == 0); 251 | 252 | tph_poisson_vec_free(&vec, &alloc); 253 | } 254 | 255 | { 256 | /* Append to empty vector with existing capacity. */ 257 | tph_poisson_vec vec; 258 | memset(&vec, 0, sizeof(tph_poisson_vec)); 259 | 260 | REQUIRE( 261 | tph_poisson_vec_reserve(&vec, &alloc, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 262 | == TPH_POISSON_SUCCESS); 263 | 264 | REQUIRE(tph_poisson_vec_append( 265 | &vec, &alloc, values, 2 * VEC_TEST_SIZEOF(float), VEC_TEST_ALIGNOF(float)) 266 | == TPH_POISSON_SUCCESS); 267 | 268 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 269 | REQUIRE(tph_poisson_vec_size(&vec) == 2 * VEC_TEST_SIZEOF(float)); 270 | REQUIRE(memcmp((const void *)vec.begin, (const void *)values, 2 * sizeof(float)) == 0); 271 | 272 | tph_poisson_vec_free(&vec, &alloc); 273 | } 274 | 275 | { 276 | /* Append to vector with elements. */ 277 | tph_poisson_vec vec; 278 | memset(&vec, 0, sizeof(tph_poisson_vec)); 279 | 280 | REQUIRE(tph_poisson_vec_append( 281 | &vec, &alloc, values, 2 * VEC_TEST_SIZEOF(float), VEC_TEST_ALIGNOF(float)) 282 | == TPH_POISSON_SUCCESS); 283 | 284 | REQUIRE(tph_poisson_vec_append( 285 | &vec, &alloc, &values[2], 2 * VEC_TEST_SIZEOF(float), VEC_TEST_ALIGNOF(float)) 286 | == TPH_POISSON_SUCCESS); 287 | 288 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 289 | REQUIRE(tph_poisson_vec_size(&vec) == 4 * VEC_TEST_SIZEOF(float)); 290 | REQUIRE(memcmp((const void *)vec.begin, (const void *)values, 4 * sizeof(float)) == 0); 291 | 292 | tph_poisson_vec_free(&vec, &alloc); 293 | } 294 | 295 | { 296 | /* Append to vector with existing capacity but no values. */ 297 | tph_poisson_vec vec; 298 | memset(&vec, 0, sizeof(tph_poisson_vec)); 299 | 300 | tph_poisson_vec_append( 301 | &vec, &alloc, values, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)); 302 | tph_poisson_vec_erase_swap(&vec, 0, VEC_TEST_SIZEOF(values)); 303 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 304 | REQUIRE(tph_poisson_vec_size(&vec) == 0); 305 | 306 | /* clang-format off */ 307 | const float values2[] = { 308 | 0.F, 1.F, 13.F, 42.F, 33.F, 18.F, -34.F, 309 | 0.F, 1.F, 13.F, 42.F, 33.F, 18.F, -34.F, 310 | }; 311 | /* clang-format on */ 312 | 313 | REQUIRE(tph_poisson_vec_append( 314 | &vec, &alloc, values2, VEC_TEST_SIZEOF(values2), VEC_TEST_ALIGNOF(float)) 315 | == TPH_POISSON_SUCCESS); 316 | 317 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 318 | tph_poisson_vec_free(&vec, &alloc); 319 | } 320 | 321 | { 322 | /* Append that causes reallocation and element copy. */ 323 | tph_poisson_vec vec; 324 | memset(&vec, 0, sizeof(tph_poisson_vec)); 325 | 326 | REQUIRE(tph_poisson_vec_append( 327 | &vec, &alloc, values, 2 * VEC_TEST_SIZEOF(float), VEC_TEST_ALIGNOF(float)) 328 | == TPH_POISSON_SUCCESS); 329 | 330 | REQUIRE(tph_poisson_vec_append( 331 | &vec, &alloc, values, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 332 | == TPH_POISSON_SUCCESS); 333 | 334 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 335 | REQUIRE(tph_poisson_vec_size(&vec) == 2 * VEC_TEST_SIZEOF(float) + VEC_TEST_SIZEOF(values)); 336 | REQUIRE(memcmp((const void *)vec.begin, (const void *)values, 2 * sizeof(float)) == 0); 337 | REQUIRE(memcmp((const void *)((intptr_t)vec.begin + 2 * VEC_TEST_SIZEOF(float)), 338 | (const void *)values, 339 | VEC_TEST_SIZEOF(values)) 340 | == 0); 341 | 342 | tph_poisson_vec_free(&vec, &alloc); 343 | } 344 | 345 | { 346 | /* Check that bad allocation propagates to caller. */ 347 | tph_poisson_vec vec; 348 | memset(&vec, 0, sizeof(tph_poisson_vec)); 349 | 350 | alloc_ctx.fail = 1; 351 | REQUIRE(tph_poisson_vec_append( 352 | &vec, &alloc, values, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 353 | == TPH_POISSON_BAD_ALLOC); 354 | alloc_ctx.fail = 0; 355 | 356 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 357 | REQUIRE(is_zeros((const void *)&vec, sizeof(tph_poisson_vec))); 358 | /* No need to free the vector! */ 359 | } 360 | } 361 | } 362 | 363 | static void test_erase_swap(void) 364 | { 365 | for (size_t i = 0; i < sizeof(float); ++i) { 366 | /* Create an allocator that returns misaligned memory (except when i == 0). */ 367 | vec_test_alloc_ctx alloc_ctx = { .align_offset = (ptrdiff_t)i }; 368 | tph_poisson_allocator alloc = { 369 | .malloc = vec_test_malloc, .free = vec_test_free, .ctx = &alloc_ctx 370 | }; 371 | const float values[] = { 0.F, 1.F, 13.F, 42.F, 33.F, 18.F, -34.F }; 372 | 373 | /* Append some values. */ 374 | tph_poisson_vec vec; 375 | memset(&vec, 0, sizeof(tph_poisson_vec)); 376 | REQUIRE( 377 | tph_poisson_vec_append(&vec, &alloc, values, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 378 | == TPH_POISSON_SUCCESS); 379 | 380 | const ptrdiff_t mem_size0 = vec.mem_size; 381 | void *const mem0 = vec.mem; 382 | void *const begin0 = vec.begin; 383 | 384 | /* Remove first of several elements. */ 385 | tph_poisson_vec_erase_swap( 386 | &vec, /*pos=*/0 * VEC_TEST_SIZEOF(float), /*n=*/VEC_TEST_SIZEOF(float)); 387 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 388 | REQUIRE(tph_poisson_vec_size(&vec) == 6 * VEC_TEST_SIZEOF(float)); 389 | REQUIRE(flt_eq((const float *)vec.begin + 0, &values[6])); 390 | REQUIRE(flt_eq((const float *)vec.begin + 1, &values[1])); 391 | REQUIRE(flt_eq((const float *)vec.begin + 2, &values[2])); 392 | REQUIRE(flt_eq((const float *)vec.begin + 3, &values[3])); 393 | REQUIRE(flt_eq((const float *)vec.begin + 4, &values[4])); 394 | REQUIRE(flt_eq((const float *)vec.begin + 5, &values[5])); 395 | 396 | /* Remove last of several elements. */ 397 | tph_poisson_vec_erase_swap( 398 | &vec, /*pos=*/5 * VEC_TEST_SIZEOF(float), /*n=*/VEC_TEST_SIZEOF(float)); 399 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 400 | REQUIRE(tph_poisson_vec_size(&vec) == 5 * VEC_TEST_SIZEOF(float)); 401 | REQUIRE(flt_eq((const float *)vec.begin + 0, &values[6])); 402 | REQUIRE(flt_eq((const float *)vec.begin + 1, &values[1])); 403 | REQUIRE(flt_eq((const float *)vec.begin + 2, &values[2])); 404 | REQUIRE(flt_eq((const float *)vec.begin + 3, &values[3])); 405 | REQUIRE(flt_eq((const float *)vec.begin + 4, &values[4])); 406 | 407 | /* Remove third and fourth element. */ 408 | tph_poisson_vec_erase_swap( 409 | &vec, /*pos=*/2 * VEC_TEST_SIZEOF(float), /*n=*/2 * VEC_TEST_SIZEOF(float)); 410 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 411 | REQUIRE(tph_poisson_vec_size(&vec) == 3 * VEC_TEST_SIZEOF(float)); 412 | REQUIRE(flt_eq((const float *)vec.begin + 0, &values[6])); 413 | REQUIRE(flt_eq((const float *)vec.begin + 1, &values[1])); 414 | REQUIRE(flt_eq((const float *)vec.begin + 2, &values[4])); 415 | 416 | /* Remove second element. */ 417 | tph_poisson_vec_erase_swap( 418 | &vec, /*pos=*/1 * VEC_TEST_SIZEOF(float), /*n=*/VEC_TEST_SIZEOF(float)); 419 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 420 | REQUIRE(tph_poisson_vec_size(&vec) == 2 * VEC_TEST_SIZEOF(float)); 421 | REQUIRE(flt_eq((const float *)vec.begin + 0, &values[6])); 422 | REQUIRE(flt_eq((const float *)vec.begin + 1, &values[4])); 423 | 424 | /* Remove second element. */ 425 | tph_poisson_vec_erase_swap( 426 | &vec, /*pos=*/1 * VEC_TEST_SIZEOF(float), /*n=*/VEC_TEST_SIZEOF(float)); 427 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 428 | REQUIRE(tph_poisson_vec_size(&vec) == 1 * VEC_TEST_SIZEOF(float)); 429 | REQUIRE(flt_eq((const float *)vec.begin + 0, &values[6])); 430 | 431 | /* Remove only remaining element. */ 432 | tph_poisson_vec_erase_swap( 433 | &vec, /*pos=*/0 * VEC_TEST_SIZEOF(float), /*n=*/VEC_TEST_SIZEOF(float)); 434 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 435 | REQUIRE(tph_poisson_vec_size(&vec) == 0); 436 | 437 | /* Capacity unchanged, no reallocation, vector is empty. */ 438 | REQUIRE(vec.mem_size == mem_size0); 439 | REQUIRE((intptr_t)vec.mem == (intptr_t)mem0); 440 | REQUIRE((intptr_t)vec.begin == (intptr_t)begin0); 441 | REQUIRE((intptr_t)vec.begin == (intptr_t)vec.end); 442 | 443 | tph_poisson_vec_free(&vec, &alloc); 444 | } 445 | } 446 | 447 | static void test_shrink_to_fit(void) 448 | { 449 | for (size_t i = 0; i < sizeof(float); ++i) { 450 | /* Create an allocator that returns misaligned memory (except when i == 0). */ 451 | vec_test_alloc_ctx alloc_ctx = { .align_offset = (ptrdiff_t)i }; 452 | tph_poisson_allocator alloc = { 453 | .malloc = vec_test_malloc, .free = vec_test_free, .ctx = &alloc_ctx 454 | }; 455 | const float values[] = { 0.F, 1.F, 13.F, 42.F, 33.F, 18.F, -34.F }; 456 | const ptrdiff_t extra_cap = 457 | alloc_ctx.align_offset == 0 ? VEC_TEST_ALIGNOF(float) : alloc_ctx.align_offset; 458 | 459 | { 460 | /* No-op on zero-initialized vector. */ 461 | tph_poisson_vec vec; 462 | memset(&vec, 0, sizeof(tph_poisson_vec)); 463 | 464 | REQUIRE(tph_poisson_vec_shrink_to_fit(&vec, &alloc, VEC_TEST_ALIGNOF(float)) 465 | == TPH_POISSON_SUCCESS); 466 | 467 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 468 | REQUIRE(is_zeros(&vec, VEC_TEST_SIZEOF(tph_poisson_vec))); 469 | /* No need to free the vector! */ 470 | } 471 | 472 | { 473 | /* Capacity but no elements. */ 474 | tph_poisson_vec vec; 475 | memset(&vec, 0, sizeof(tph_poisson_vec)); 476 | 477 | REQUIRE( 478 | tph_poisson_vec_reserve(&vec, &alloc, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 479 | == TPH_POISSON_SUCCESS); 480 | 481 | REQUIRE(tph_poisson_vec_shrink_to_fit(&vec, &alloc, VEC_TEST_ALIGNOF(float)) 482 | == TPH_POISSON_SUCCESS); 483 | 484 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 485 | REQUIRE(is_zeros(&vec, VEC_TEST_SIZEOF(tph_poisson_vec))); 486 | /* No need to free the vector! */ 487 | } 488 | 489 | { 490 | /* size == capacity means that shrink_to_fit is a no-op. */ 491 | tph_poisson_vec vec; 492 | memset(&vec, 0, sizeof(tph_poisson_vec)); 493 | 494 | /* Append some values to an empty vector. */ 495 | REQUIRE(tph_poisson_vec_append( 496 | &vec, &alloc, values, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 497 | == TPH_POISSON_SUCCESS); 498 | if (extra_cap == VEC_TEST_SIZEOF(float)) { 499 | REQUIRE(tph_poisson_vec_append( 500 | &vec, &alloc, values, VEC_TEST_SIZEOF(float), VEC_TEST_ALIGNOF(float)) 501 | == TPH_POISSON_SUCCESS); 502 | } 503 | 504 | /* Shrink and verify that vector is bit-wise intact. */ 505 | tph_poisson_vec vec0; 506 | memcpy(&vec0, &vec, sizeof(tph_poisson_vec)); 507 | 508 | REQUIRE(tph_poisson_vec_shrink_to_fit(&vec, &alloc, VEC_TEST_ALIGNOF(float)) 509 | == TPH_POISSON_SUCCESS); 510 | 511 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 512 | REQUIRE(memcmp(&vec, &vec0, sizeof(tph_poisson_vec)) == 0); 513 | REQUIRE(memcmp((const void *)vec.begin, (const void *)values, VEC_TEST_SIZEOF(values)) == 0); 514 | 515 | tph_poisson_vec_free(&vec, &alloc); 516 | } 517 | 518 | { 519 | /* Verify that shrinking removes extraneous capacity. This requires a reallocation and copying 520 | * elements to the smaller buffer. */ 521 | tph_poisson_vec vec; 522 | memset(&vec, 0, sizeof(tph_poisson_vec)); 523 | 524 | /* Reserve a large capacity and append only a few values so that shrinking removes 525 | * extraneous capacity (which triggers reallocation). */ 526 | REQUIRE( 527 | tph_poisson_vec_reserve(&vec, &alloc, 20 * VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 528 | == TPH_POISSON_SUCCESS); 529 | REQUIRE(tph_poisson_vec_append( 530 | &vec, &alloc, values, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 531 | == TPH_POISSON_SUCCESS); 532 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 533 | REQUIRE(tph_poisson_vec_size(&vec) == VEC_TEST_SIZEOF(values)); 534 | REQUIRE(tph_poisson_vec_capacity(&vec) == 20 * VEC_TEST_SIZEOF(values) + extra_cap); 535 | 536 | /* Shrink capacity. */ 537 | REQUIRE(tph_poisson_vec_shrink_to_fit(&vec, &alloc, VEC_TEST_ALIGNOF(float)) 538 | == TPH_POISSON_SUCCESS); 539 | 540 | REQUIRE(valid_invariants(&vec, VEC_TEST_ALIGNOF(float))); 541 | REQUIRE(tph_poisson_vec_size(&vec) == VEC_TEST_SIZEOF(values)); 542 | REQUIRE(tph_poisson_vec_capacity(&vec) == VEC_TEST_SIZEOF(values) + extra_cap); 543 | REQUIRE(memcmp((const void *)vec.begin, (const void *)values, sizeof(values)) == 0); 544 | tph_poisson_vec_free(&vec, &alloc); 545 | } 546 | 547 | { 548 | /* Check that bad allocation propagates to caller. */ 549 | tph_poisson_vec vec; 550 | memset(&vec, 0, sizeof(tph_poisson_vec)); 551 | 552 | /* Reserve a large capacity and append only a few values so that shrinking removes 553 | * extraneous capacity (which triggers reallocation). */ 554 | REQUIRE( 555 | tph_poisson_vec_reserve(&vec, &alloc, VEC_TEST_SIZEOF(values), VEC_TEST_ALIGNOF(float)) 556 | == TPH_POISSON_SUCCESS); 557 | REQUIRE(tph_poisson_vec_append( 558 | &vec, &alloc, values, VEC_TEST_SIZEOF(values) / 2, VEC_TEST_ALIGNOF(float)) 559 | == TPH_POISSON_SUCCESS); 560 | 561 | /* Check that caller is notified when reallocation fails. */ 562 | alloc_ctx.fail = 1; 563 | REQUIRE(tph_poisson_vec_shrink_to_fit(&vec, &alloc, VEC_TEST_ALIGNOF(float)) 564 | == TPH_POISSON_BAD_ALLOC); 565 | alloc_ctx.fail = 0; 566 | 567 | tph_poisson_vec_free(&vec, &alloc); 568 | } 569 | } 570 | } 571 | 572 | int main(int argc, char *argv[]) 573 | { 574 | (void)argc; 575 | (void)argv; 576 | 577 | printf("test_reserve...\n"); 578 | test_reserve(); 579 | 580 | printf("test_append...\n"); 581 | test_append(); 582 | 583 | printf("test_erase_swap...\n"); 584 | test_erase_swap(); 585 | 586 | printf("test_shrink_to_fit...\n"); 587 | test_shrink_to_fit(); 588 | 589 | return EXIT_SUCCESS; 590 | } 591 | --------------------------------------------------------------------------------