[
  {
    "path": ".clang-format",
    "content": "---\nAccessModifierOffset: -1\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignEscapedNewlines:  Left\n# AlingOperands: true # Unsupported\nAlignTrailingComments: false\nAllowAllParametersOfDeclarationOnNextLine: false\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: Empty\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterReturnType: None\nAlwaysBreakBeforeMultilineStrings: true\nAlwaysBreakTemplateDeclarations: true\nBinPackArguments: false\nBinPackParameters: false\nBreakBeforeBinaryOperators: false\nBreakBeforeBraces: Attach\nBreakBeforeInheritanceComma: false\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializers: AfterColon\nBreakStringLiterals: true\nColumnLimit: 140\nCompactNamespaces: false\nConstructorInitializerAllOnOneLineOrOnePerLine: true\nConstructorInitializerIndentWidth: 2\nContinuationIndentWidth: 4\nCpp11BracedListStyle: true\nDerivePointerAlignment: false\nFixNamespaceComments: true\nIncludeBlocks: Preserve\nIndentCaseLabels: false\nIndentPPDirectives: None\nIndentWidth: 2\nIndentWrappedFunctionNames: false\nKeepEmptyLinesAtTheStartOfBlocks: false\n# LanguageKind: Cpp # Unsupported\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: None\n# ObjCBinPackProtocolList: Never # Unsupported\nObjCBlockIndentWidth: 2\nObjCSpaceAfterProperty: true\nObjCSpaceBeforeProtocolList: true\nPenaltyBreakAssignment: 5\nPenaltyBreakBeforeFirstCallParameter: 10\nPenaltyBreakComment: 60\nPenaltyBreakFirstLessLess: 20\nPenaltyBreakString: 1000\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 200\nPointerAlignment: Left\nReflowComments: true\nSortIncludes: true\nSortUsingDeclarations: true\nSpaceAfterCStyleCast: false\nSpaceAfterTemplateKeyword: false\nSpaceBeforeAssignmentOperators: true\n# SpaceBeforeCtorInitializerColon: false # Unsupported\n# SpaceBeforeInheritanceColon: false # Unsupported\nSpaceBeforeParens: ControlStatements\n# SpaceBeforeRangeBasedForLoopColon: true # Unsupported\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles: false\nSpacesInContainerLiterals: true\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard: Cpp11\nTabWidth: 2\nUseTab: Never\n...\n"
  },
  {
    "path": ".github/workflows/c-cpp.yml",
    "content": "name: C/C++ CI\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\njobs:\n  cmake-build:\n      strategy:\n        fail-fast: false\n        matrix:\n          os: [ubuntu-latest, windows-latest, macos-latest]\n          generator: [\"Default Generator\", \"Unix Makefiles\"]\n      env:\n        CMAKE_GENERATOR: >-\n          ${{format(matrix.generator != 'Default Generator' && '-G \"{0}\"' || '', matrix.generator)}}\n      runs-on: ${{ matrix.os }}\n      steps:\n        - uses: actions/checkout@v6\n          with:\n            submodules: recursive\n\n        - name: Get number of CPU cores\n          uses: SimenB/github-actions-cpu-cores@v2\n\n        - name: Build\n          shell: bash\n          run: |\n            cmake ${{ env.CMAKE_GENERATOR }} -S \"${{ github.workspace }}\" -B build\n            cd build\n            cmake --build . --parallel ${{ steps.cpu-cores.outputs.count }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n\n# VisualStudio files\n*.suo\n*.user\n*.sdf\n*.opensdf\n\nDebug/\nRelease/\nipch/\nbuild/\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.22)\n\nproject(\"poisson-disk-generator\")\n\nadd_library(PoissonGenerator INTERFACE PoissonGenerator.h)\n\nadd_executable(Poisson Poisson.cpp)\n\ntarget_link_libraries(Poisson PRIVATE PoissonGenerator)\n\nif(MSVC)\n  target_compile_definitions(Poisson PRIVATE _CRT_SECURE_NO_WARNINGS)\nendif()\n\nset_property(TARGET PoissonGenerator PROPERTY CXX_STANDARD 17)\nset_property(TARGET PoissonGenerator PROPERTY CXX_STANDARD_REQUIRED ON)\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014-2026 Sergey Kosarevsky\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "Poisson.cpp",
    "content": "/**\n * \\file Poisson.cpp\n * \\brief\n *\n * Poisson Disk Points Generator example\n *\n * \\version 1.7.0\n * \\date 21/01/2026\n * \\author Sergey Kosarevsky, 2014-2026\n * \\author support@linderdaum.com   http://www.linderdaum.com   http://blog.linderdaum.com\n */\n\n/*\n   To compile:\n      gcc Poisson.cpp -std=c++17 -lstdc++\n*/\n\n#include <math.h>\n#include <memory.h>\n#include <string.h>\n\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <memory>\n#include <vector>\n\n#define POISSON_PROGRESS_INDICATOR 1\n#include \"PoissonGenerator.h\"\n\n///////////////// User selectable parameters ///////////////////////////////\n\nconst int kNumPointsDefaultPoisson = 20000; // default number of points to generate for Poisson disk\nconst int kNumPointsDefaultVogel = 2000; // default number of points to generate for Vogel disk\nconst int kNumPointsDefaultJittered = 2500; // default number of points to generate for jittered grid\nconst int kImageSize = 512; // generate RGB image [ImageSize x ImageSize]\n\n////////////////////////////////////////////////////////////////////////////\n\nfloat* g_DensityMap = nullptr;\n\n#if defined(__GNUC__)\n#define GCC_PACK(n) __attribute__((packed, aligned(n)))\n#else\n#define GCC_PACK(n) __declspec(align(n))\n#endif // __GNUC__\n\n#pragma pack(push, 1)\nstruct GCC_PACK(1) sBMPHeader {\n  // BITMAPFILEHEADER\n  unsigned short bfType;\n  uint32_t bfSize;\n  unsigned short bfReserved1;\n  unsigned short bfReserved2;\n  uint32_t bfOffBits;\n  // BITMAPINFOHEADER\n  uint32_t biSize;\n  uint32_t biWidth;\n  uint32_t biHeight;\n  unsigned short biPlanes;\n  unsigned short biBitCount;\n  uint32_t biCompression;\n  uint32_t biSizeImage;\n  uint32_t biXPelsPerMeter;\n  uint32_t biYPelsPerMeter;\n  uint32_t biClrUsed;\n  uint32_t biClrImportant;\n};\n#pragma pack(pop)\n\n///////////////// Uncompressed AVI Writer //////////////////////////////////\n\n// Simple uncompressed AVI writer (grayscale 8-bit, no audio)\n// AVI format reference: https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference\nclass AVIWriter {\n public:\n  AVIWriter(const char* fileName, int width, int height, int skipFrames) {\n    m_width = width;\n    m_height = height;\n    m_skipFrames = skipFrames;\n    m_frameDataSize = width * height; // grayscale: 1 byte per pixel\n    m_rowPadding = (4 - (width) % 4) % 4; // pad each row to 4-byte boundary (BMP/AVI requirement)\n    m_paddedRowSize = width + m_rowPadding;\n    m_paddedFrameSize = m_paddedRowSize * height;\n\n    std::cout << \"\\nSaving video to `\" << fileName << \"`\" << std::endl;\n\n    m_file.open(fileName, std::ios::out | std::ios::binary);\n\n    // write placeholder header (will be updated on close)\n    writeHeader();\n    m_moviStart = static_cast<uint32_t>(m_file.tellp());\n\n    // start 'movi' LIST\n    writeChunkHeader(\"LIST\", 0); // size placeholder\n    writeFourCC(\"movi\");\n  }\n  ~AVIWriter() {\n    const uint32_t moviEnd = static_cast<uint32_t>(m_file.tellp());\n    const uint32_t moviSize = moviEnd - m_moviStart - 8; // exclude LIST header\n\n    writeIndex(); // write index chunk 'idx1'\n\n    const uint32_t fileEnd = static_cast<uint32_t>(m_file.tellp());\n\n    // update movi LIST size\n    m_file.seekp(m_moviStart + 4);\n    writeUInt32(moviSize + 4); // +4 for 'movi' fourcc\n\n    // update RIFF size\n    m_file.seekp(4);\n    writeUInt32(fileEnd - 8);\n\n    // update frame count in header\n    m_file.seekp(48); // dwTotalFrames in main AVI header\n    writeUInt32(m_frameCount);\n\n    m_file.seekp(140); // dwLength in stream header\n    writeUInt32(m_frameCount);\n\n    m_file.close();\n\n    std::cout << \"\\nSaved AVI with \" << m_frameCount << \" frames\" << std::endl;\n  }\n\n  bool addFrame(const void* bgrData, bool isLastFrame) {\n    if (!isLastFrame && (m_inputFrameCount++ % m_skipFrames) != 0)\n      return false;\n\n    writeChunkHeader(\"00db\", m_paddedFrameSize); // write frame chunk: '00db' for uncompressed DIB\n\n    // convert BGR to grayscale and write with row padding\n    const unsigned char* src = static_cast<const unsigned char*>(bgrData);\n    std::vector<unsigned char> rowBuffer(m_paddedRowSize, 0);\n\n    for (int y = 0; y < m_height; y++) {\n      for (int x = 0; x < m_width; x++) {\n        rowBuffer[x] = src[(y * m_width + x) * 3]; // in our grayscale case, just take the first channel\n      }\n      m_file.write(reinterpret_cast<const char*>(rowBuffer.data()), m_paddedRowSize);\n    }\n\n    m_frameCount++;\n\n    return true;\n  }\n\n private:\n  std::ofstream m_file;\n  int m_width = 0;\n  int m_height = 0;\n  int m_fps = 60;\n  int m_skipFrames = 16;\n  uint32_t m_inputFrameCount = 0;\n  uint32_t m_frameCount = 0;\n  uint32_t m_frameDataSize = 0;\n  uint32_t m_moviStart = 0;\n  int m_rowPadding = 0;\n  int m_paddedRowSize = 0;\n  uint32_t m_paddedFrameSize = 0;\n\n  void writeFourCC(const char* fourcc) {\n    m_file.write(fourcc, 4);\n  }\n\n  void writeUInt32(uint32_t value) {\n    m_file.write(reinterpret_cast<const char*>(&value), 4);\n  }\n\n  void writeUInt16(uint16_t value) {\n    m_file.write(reinterpret_cast<const char*>(&value), 2);\n  }\n\n  void writeChunkHeader(const char* fourcc, uint32_t size) {\n    writeFourCC(fourcc);\n    writeUInt32(size);\n  }\n\n  void writeHeader() {\n    writeFourCC(\"RIFF\");\n    writeUInt32(0); // file size placeholder\n    writeFourCC(\"AVI \");\n    writeFourCC(\"LIST\"); // hdrl LIST\n    uint32_t hdrlSizePos = static_cast<uint32_t>(m_file.tellp());\n    writeUInt32(0); // size placeholder\n    writeFourCC(\"hdrl\");\n    // main AVI header (avih)\n    writeFourCC(\"avih\");\n    writeUInt32(56); // header size\n    writeUInt32(1000000 / m_fps); // dwMicroSecPerFrame\n    writeUInt32(m_paddedFrameSize * m_fps); // dwMaxBytesPerSec\n    writeUInt32(0); // dwPaddingGranularity\n    writeUInt32(0x10); // dwFlags (AVIF_HASINDEX)\n    writeUInt32(0); // dwTotalFrames (placeholder)\n    writeUInt32(0); // dwInitialFrames\n    writeUInt32(1); // dwStreams\n    writeUInt32(m_paddedFrameSize); // dwSuggestedBufferSize\n    writeUInt32(m_width); // dwWidth\n    writeUInt32(m_height); // dwHeight\n    writeUInt32(0); // dwReserved[4]\n    writeUInt32(0);\n    writeUInt32(0);\n    writeUInt32(0);\n    // stream LIST\n    writeFourCC(\"LIST\");\n    uint32_t strlSizePos = static_cast<uint32_t>(m_file.tellp());\n    writeUInt32(0); // size placeholder\n    writeFourCC(\"strl\");\n    // stream header (strh)\n    writeFourCC(\"strh\");\n    writeUInt32(56); // header size\n    writeFourCC(\"vids\"); // fccType\n    writeFourCC(\"DIB \"); // fccHandler (uncompressed)\n    writeUInt32(0); // dwFlags\n    writeUInt16(0); // wPriority\n    writeUInt16(0); // wLanguage\n    writeUInt32(0); // dwInitialFrames\n    writeUInt32(1); // dwScale\n    writeUInt32(m_fps); // dwRate\n    writeUInt32(0); // dwStart\n    writeUInt32(0); // dwLength (placeholder)\n    writeUInt32(m_paddedFrameSize); // dwSuggestedBufferSize\n    writeUInt32(0xFFFFFFFF); // dwQuality\n    writeUInt32(0); // dwSampleSize\n    writeUInt16(0); // rcFrame left\n    writeUInt16(0); // rcFrame top\n    writeUInt16(static_cast<uint16_t>(m_width)); // rcFrame right\n    writeUInt16(static_cast<uint16_t>(m_height)); // rcFrame bottom\n    // stream format (strf) - BITMAPINFOHEADER + palette for 8-bit grayscale\n    writeFourCC(\"strf\");\n    writeUInt32(40 + 256 * 4); // header size + palette size\n    writeUInt32(40); // biSize\n    writeUInt32(m_width); // biWidth\n    writeUInt32(m_height); // biHeight\n    writeUInt16(1); // biPlanes\n    writeUInt16(8); // biBitCount (8-bit grayscale)\n    writeUInt32(0); // biCompression (BI_RGB)\n    writeUInt32(m_paddedFrameSize); // biSizeImage\n    writeUInt32(0); // biXPelsPerMeter\n    writeUInt32(0); // biYPelsPerMeter\n    writeUInt32(256); // biClrUsed\n    writeUInt32(256); // biClrImportant\n\n    // write grayscale palette (256 entries, BGRA format)\n    for (int i = 0; i < 256; i++) {\n      const uint8_t gray = static_cast<uint8_t>(i);\n      m_file.put(gray); // B\n      m_file.put(gray); // G\n      m_file.put(gray); // R\n      m_file.put(0); // A (reserved)\n    }\n\n    // update strl LIST size\n    const uint32_t strlEnd = static_cast<uint32_t>(m_file.tellp());\n    m_file.seekp(strlSizePos);\n    writeUInt32(strlEnd - strlSizePos - 4);\n    m_file.seekp(strlEnd);\n\n    // Update hdrl LIST size\n    const uint32_t hdrlEnd = static_cast<uint32_t>(m_file.tellp());\n    m_file.seekp(hdrlSizePos);\n    writeUInt32(hdrlEnd - hdrlSizePos - 4);\n    m_file.seekp(hdrlEnd);\n  }\n\n  void writeIndex() {\n    writeFourCC(\"idx1\");\n    writeUInt32(m_frameCount * 16); // index size\n\n    uint32_t offset = 4; // offset from 'movi' to first frame data\n\n    for (uint32_t i = 0; i < m_frameCount; i++) {\n      writeFourCC(\"00db\"); // chunk ID\n      writeUInt32(0x10); // flags (AVIIF_KEYFRAME)\n      writeUInt32(offset); // offset\n      writeUInt32(m_paddedFrameSize); // size\n\n      offset += m_paddedFrameSize + 8; // +8 for chunk header\n    }\n  }\n};\n\n///////////////// BMP Functions ////////////////////////////////////////////\n\nvoid SaveBMP(const char* FileName, const void* RawBGRImage, int Width, int Height) {\n  sBMPHeader Header;\n\n  int ImageSize = Width * Height * 3;\n\n  Header.bfType = 0x4D * 256 + 0x42;\n  Header.bfSize = ImageSize + sizeof(sBMPHeader);\n  Header.bfReserved1 = 0;\n  Header.bfReserved2 = 0;\n  Header.bfOffBits = 0x36;\n  Header.biSize = 40;\n  Header.biWidth = Width;\n  Header.biHeight = Height;\n  Header.biPlanes = 1;\n  Header.biBitCount = 24;\n  Header.biCompression = 0;\n  Header.biSizeImage = ImageSize;\n  Header.biXPelsPerMeter = 6000;\n  Header.biYPelsPerMeter = 6000;\n  Header.biClrUsed = 0;\n  Header.biClrImportant = 0;\n\n  std::ofstream File(FileName, std::ios::out | std::ios::binary);\n\n  File.write((const char*)&Header, sizeof(Header));\n  File.write((const char*)RawBGRImage, ImageSize);\n\n  std::cout << \"Saved \" << FileName << std::endl;\n}\n\nunsigned char* LoadBMP(const char* FileName, int* OutWidth, int* OutHeight) {\n  sBMPHeader Header;\n\n  std::ifstream File(FileName, std::ifstream::binary);\n\n  File.read((char*)&Header, sizeof(Header));\n\n  *OutWidth = Header.biWidth;\n  *OutHeight = Header.biHeight;\n\n  const size_t DataSize = 3 * Header.biWidth * Header.biHeight;\n\n  unsigned char* Img = new unsigned char[DataSize];\n\n  File.read((char*)Img, DataSize);\n\n  return Img;\n}\n\nvoid LoadDensityMap(const char* FileName) {\n  std::cout << \"Loading density map \" << FileName << std::endl;\n\n  int W, H;\n  unsigned char* Data = LoadBMP(FileName, &W, &H);\n\n  std::cout << \"Loaded ( \" << W << \" x \" << H << \" ) \" << std::endl;\n\n  if (W != kImageSize || H != kImageSize) {\n    std::cout << \"ERROR: density map should be \" << kImageSize << \" x \" << kImageSize << std::endl;\n\n    exit(255);\n  }\n\n  g_DensityMap = new float[W * H];\n\n  for (int y = 0; y != H; y++) {\n    for (int x = 0; x != W; x++) {\n      g_DensityMap[x + y * W] = float(Data[3 * (x + y * W)]) / 255.0f;\n    }\n  }\n\n  delete[] (Data);\n}\n\nvoid PrintBanner() {\n  std::cout << \"Poisson disk points generator\" << std::endl;\n  std::cout << \"Version \" << PoissonGenerator::Version << std::endl;\n  std::cout << \"Sergey Kosarevsky, 2014-2026\" << std::endl;\n  std::cout << \"support@linderdaum.com http://www.linderdaum.com http://blog.linderdaum.com\" << std::endl;\n  std::cout << std::endl;\n  std::cout << \"Usage: Poisson [density-map-rgb24.bmp] [--raw-points] [--num-points=<value>] [--square] [--vogel-disk | --jittered-grid | \"\n               \"--hammersley] [--shuffle] [--save-frames] [--save-video[=<skip-frames>]]\"\n            << std::endl;\n  std::cout << std::endl;\n}\n\nint main(int argc, char** argv) {\n  PrintBanner();\n\n  if (argc > 1 && !strstr(argv[1], \"--\")) {\n    LoadDensityMap(argv[1]);\n  }\n\n  auto hasCmdLineArg = [argc, argv](const char* arg) -> bool {\n    for (int i = 1; i < argc; i++) {\n      if (!strcmp(argv[i], arg)) {\n        return true;\n      }\n    }\n    return false;\n  };\n\n  auto getCmdLineValue = [argc, argv](const char* arg, unsigned int defaultValue) -> unsigned int {\n    for (int i = 1; i < argc; i++) {\n      if (strstr(argv[i], arg)) {\n        unsigned int v = defaultValue;\n        return sscanf(argv[i], \"--num-points=%u\", &v) == 1 ? v : defaultValue;\n      }\n    }\n    return defaultValue;\n  };\n\n  auto getCmdLineValueSkipFrames = [argc, argv](const char* arg, unsigned int defaultValue) -> unsigned int {\n    for (int i = 1; i < argc; i++) {\n      if (strstr(argv[i], arg)) {\n        unsigned int v = defaultValue;\n        if (sscanf(argv[i], \"--save-video=%u\", &v) == 1) {\n          return v;\n        }\n        return defaultValue;\n      }\n    }\n    return defaultValue;\n  };\n\n  auto hasCmdLineArgPrefix = [argc, argv](const char* prefix) -> bool {\n    for (int i = 1; i < argc; i++) {\n      if (strstr(argv[i], prefix) == argv[i]) {\n        return true;\n      }\n    }\n    return false;\n  };\n\n  const bool cmdRawPointsOutput = hasCmdLineArg(\"--raw-points\");\n  const bool cmdSquare = hasCmdLineArg(\"--square\");\n  const bool cmdVogelDisk = hasCmdLineArg(\"--vogel-disk\");\n  const bool cmdJitteredGrid = hasCmdLineArg(\"--jittered-grid\");\n\n  const bool cmdHammersley = hasCmdLineArg(\"--hammersley\");\n\n  const bool cmdShuffle = hasCmdLineArg(\"--shuffle\");\n  const bool cmdSaveFrames = hasCmdLineArg(\"--save-frames\");\n  const bool cmdSaveVideo = hasCmdLineArgPrefix(\"--save-video\");\n  const unsigned int videoSkipFrames = getCmdLineValueSkipFrames(\"--save-video\", 16);\n\n  const unsigned int numPoints = getCmdLineValue(\n      \"--num-points=\", cmdVogelDisk ? kNumPointsDefaultVogel : (cmdJitteredGrid ? kNumPointsDefaultJittered : kNumPointsDefaultPoisson));\n\n  std::cout << \"NumPoints = \" << numPoints << std::endl;\n\n  PoissonGenerator::DefaultPRNG PRNG;\n\n  auto Points = cmdVogelDisk      ? PoissonGenerator::generateVogelPoints(numPoints)\n                : cmdJitteredGrid ? PoissonGenerator::generateJitteredGridPoints(numPoints, PRNG, !cmdSquare)\n                : cmdHammersley   ? PoissonGenerator::generateHammersleyPoints(numPoints)\n                                  : PoissonGenerator::generatePoissonPoints(numPoints, PRNG, !cmdSquare);\n\n  // prepare BGR image\n  const size_t DataSize = 3 * kImageSize * kImageSize;\n\n  unsigned char* Img = new unsigned char[DataSize];\n\n  memset(Img, 0, DataSize);\n\n  if (cmdShuffle) {\n    std::cout << \"Shuffling points...\" << std::endl;\n    PoissonGenerator::shuffle(Points, PRNG);\n  }\n\n  std::unique_ptr<AVIWriter> aviWriter = cmdSaveVideo ? std::make_unique<AVIWriter>(\"Points.avi\", kImageSize, kImageSize, videoSkipFrames)\n                                                      : nullptr;\n\n  int frame = 0;\n  size_t currentPoint = 0;\n  const size_t totalPoints = Points.size();\n\n  for (const auto& i : Points) {\n    currentPoint++;\n    const int x = int(i.x * kImageSize);\n    const int y = int(i.y * kImageSize);\n    if (x < 0 || y < 0 || x >= kImageSize || y >= kImageSize)\n      continue;\n    if (g_DensityMap) {\n      // dice\n      float R = PRNG.randomFloat();\n      float P = g_DensityMap[x + y * kImageSize];\n      if (R > P)\n        continue;\n    }\n    const int Base = 3 * (x + y * kImageSize);\n    Img[Base + 0] = Img[Base + 1] = Img[Base + 2] = 255;\n\n    if (cmdSaveFrames) {\n      char fileName[64] = {};\n      snprintf(fileName, sizeof(fileName), \"pnt%05i.bmp\", frame++);\n      SaveBMP(fileName, Img, kImageSize, kImageSize);\n    }\n\n    if (aviWriter && aviWriter->addFrame(Img, currentPoint == totalPoints)) {\n      std::cout << \"\\rRendering points to video: \" << currentPoint << \"/\" << totalPoints << std::flush;\n    }\n  }\n\n  // always flush the final frame to video\n  if (aviWriter && aviWriter->addFrame(Img, true)) {\n    std::cout << \"\\rRendering points to video: \" << currentPoint << \"/\" << totalPoints << std::flush;\n  }\n\n  aviWriter = nullptr;\n\n  SaveBMP(\"Points.bmp\", Img, kImageSize, kImageSize);\n\n  delete[] (Img);\n\n  // dump points to a text file\n  std::ofstream File(\"points.txt\", std::ios::out);\n\n  if (cmdRawPointsOutput) {\n    File << \"NumPoints = \" << Points.size() << std::endl;\n\n    for (const auto& p : Points) {\n      File << p.x << \" \" << p.y << std::endl;\n    }\n  } else {\n    File << \"const vec2 points[\" << Points.size() << \"]\" << std::endl;\n    File << \"{\" << std::endl;\n    File << std::fixed << std::setprecision(6);\n    for (const auto& p : Points) {\n      File << \"\\tvec2(\" << p.x << \"f, \" << p.y << \"f),\" << std::endl;\n    }\n    File << \"};\" << std::endl;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "PoissonGenerator.h",
    "content": "﻿/**\n * \\file PoissonGenerator.h\n * \\brief\n *\n * Poisson Disk Points Generator\n *\n * \\version 1.7.0\n * \\date 21/01/2026\n * \\author Sergey Kosarevsky, 2014-2026\n * \\author support@linderdaum.com   http://www.linderdaum.com   http://blog.linderdaum.com\n *\n * https://github.com/corporateshark/poisson-disk-generator\n */\n\n/*\n   Usage example:\n\n      #define POISSON_PROGRESS_INDICATOR 1\n      #include \"PoissonGenerator.h\"\n      ...\n      PoissonGenerator::DefaultPRNG PRNG;\n      const auto Points = PoissonGenerator::generatePoissonPoints( NumPoints, PRNG );\n      ...\n      const auto Points = PoissonGenerator::generateVogelPoints( NumPoints );\n*/\n\n// Fast Poisson Disk Sampling in Arbitrary Dimensions\n// http://people.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf\n\n// Implementation based on http://devmag.org.za/2009/05/03/poisson-disk-sampling/\n\n/* Versions history:\n *    1.7     Jan 21, 2026    New arguments: `--shuffle`, `--save-frames`, and `--save-video`; fixed warnings\n *    1.6.2   Aug  8, 2025    Dropped the `argh` library dependency\n *    1.6.1   Feb 16, 2024    Reformatted using .clang-format\n *    1.6     May 29, 2023    Added generateHammersleyPoints() to generate Hammersley points\n *    1.5     Mar 26, 2022    Added generateJitteredGridPoints() to generate jittered grid points\n *    1.4.1   Dec 12, 2021    Replaced default Mersenne Twister and <random> with fast and lightweight LCG\n *    1.4     Dec  5, 2021    Added generateVogelPoints() to generate Vogel disk points\n *    1.3     Mar 14, 2021    Bugfixes: number of points in the !isCircle mode, incorrect loop boundaries\n *    1.2     Dec 28, 2019    Bugfixes; more consistent progress indicator; new command line options in demo app\n *    1.1.6   Dec  7, 2019    Removed duplicate seed initialization; fixed warnings\n *    1.1.5   Jun 16, 2019    In-class initializers; default ctors; naming, shorter code\n *    1.1.4   Oct 19, 2016    POISSON_PROGRESS_INDICATOR can be defined outside of the header file, disabled by default\n *    1.1.3a  Jun  9, 2016    Update constructor for DefaultPRNG\n *    1.1.3   Mar 10, 2016    Header-only library, no global mutable state\n *    1.1.2   Apr  9, 2015    Output a text file with XY coordinates\n *    1.1.1   May 23, 2014    Initialize PRNG seed, fixed uninitialized fields\n *    1.1     May  7, 2014    Support of density maps\n *    1.0     May  6, 2014\n */\n\n#include <stdint.h>\n#include <vector>\n\nnamespace PoissonGenerator {\n\nconst char* Version = \"1.7.0 (21/01/2026)\";\n\nclass DefaultPRNG {\n public:\n  DefaultPRNG() = default;\n  explicit DefaultPRNG(unsigned int seed) : seed_(seed) {}\n  inline float randomFloat() {\n    seed_ *= 521167;\n    uint32_t a = (seed_ & 0x007fffff) | 0x40000000;\n    // remap to 0..1\n    return 0.5f * (*((float*)&a) - 2.0f);\n  }\n  inline uint32_t randomInt(uint32_t maxInt) {\n    return uint32_t(randomFloat() * maxInt);\n  }\n  inline uint32_t getSeed() const {\n    return seed_;\n  }\n\n private:\n  uint32_t seed_ = 7133167;\n};\n\nstruct Point {\n  Point() = default;\n  Point(float X, float Y) : x(X), y(Y), valid_(true) {}\n  float x = 0.0f;\n  float y = 0.0f;\n  bool valid_ = false;\n  //\n  bool isInRectangle() const {\n    return x >= 0 && y >= 0 && x <= 1 && y <= 1;\n  }\n  //\n  bool isInCircle() const {\n    const float fx = x - 0.5f;\n    const float fy = y - 0.5f;\n    return (fx * fx + fy * fy) <= 0.25f;\n  }\n  Point& operator+(const Point& p) {\n    x += p.x;\n    y += p.y;\n    return *this;\n  }\n  Point& operator-(const Point& p) {\n    x -= p.x;\n    y -= p.y;\n    return *this;\n  }\n};\n\nstruct GridPoint {\n  GridPoint() = delete;\n  GridPoint(int X, int Y) : x(X), y(Y) {}\n  int x;\n  int y;\n};\n\nfloat getDistance(const Point& P1, const Point& P2) {\n  return sqrt((P1.x - P2.x) * (P1.x - P2.x) + (P1.y - P2.y) * (P1.y - P2.y));\n}\n\nGridPoint imageToGrid(const Point& P, float cellSize) {\n  return GridPoint((int)(P.x / cellSize), (int)(P.y / cellSize));\n}\n\nstruct Grid {\n  Grid(int w, int h, float cellSize) : w_(w), h_(h), cellSize_(cellSize) {\n    grid_.resize(h_);\n    for (auto i = grid_.begin(); i != grid_.end(); i++) {\n      i->resize(w);\n    }\n  }\n  void insert(const Point& p) {\n    const GridPoint g = imageToGrid(p, cellSize_);\n    grid_[g.x][g.y] = p;\n  }\n  bool isInNeighbourhood(const Point& point, float minDist, float cellSize) {\n    const GridPoint g = imageToGrid(point, cellSize);\n\n    // number of adjacent cells to look for neighbour points\n    const int D = 5;\n\n    // scan the neighbourhood of the point in the grid\n    for (int i = g.x - D; i <= g.x + D; i++) {\n      for (int j = g.y - D; j <= g.y + D; j++) {\n        if (i >= 0 && i < w_ && j >= 0 && j < h_) {\n          const Point P = grid_[i][j];\n\n          if (P.valid_ && getDistance(P, point) < minDist)\n            return true;\n        }\n      }\n    }\n\n    return false;\n  }\n\n private:\n  int w_;\n  int h_;\n  float cellSize_;\n  std::vector<std::vector<Point>> grid_;\n};\n\ntemplate<typename PRNG>\nPoint popRandom(std::vector<Point>& points, PRNG& generator) {\n  const int idx = generator.randomInt(static_cast<int>(points.size()) - 1);\n  const Point p = points[idx];\n  points.erase(points.begin() + idx);\n  return p;\n}\n\ntemplate<typename PRNG>\nPoint generateRandomPointAround(const Point& p, float minDist, PRNG& generator) {\n  // start with non-uniform distribution\n  const float R1 = generator.randomFloat();\n  const float R2 = generator.randomFloat();\n\n  // radius should be between MinDist and 2 * MinDist\n  const float radius = minDist * (R1 + 1.0f);\n\n  // random angle\n  const float angle = 2 * 3.141592653589f * R2;\n\n  // the new point is generated around the point (x, y)\n  const float x = p.x + radius * cos(angle);\n  const float y = p.y + radius * sin(angle);\n\n  return Point(x, y);\n}\n\n/**\n   Return a vector of generated points\n\n   NewPointsCount - refer to bridson-siggraph07-poissondisk.pdf for details (the value 'k')\n   Circle  - 'true' to fill a circle, 'false' to fill a rectangle\n   MinDist - minimal distance estimator, use negative value for default\n**/\ntemplate<typename PRNG = DefaultPRNG>\nstd::vector<Point> generatePoissonPoints(uint32_t numPoints,\n                                         PRNG& generator,\n                                         bool isCircle = true,\n                                         uint32_t newPointsCount = 30,\n                                         float minDist = -1.0f) {\n  numPoints *= 2;\n\n  // if we want to generate a Poisson square shape, multiply the estimate number of points by PI/4 due to reduced shape area\n  if (!isCircle) {\n    const double Pi_4 = 0.785398163397448309616; // PI/4\n    numPoints = static_cast<int>(Pi_4 * numPoints);\n  }\n\n  if (minDist < 0.0f) {\n    minDist = sqrt(float(numPoints)) / float(numPoints);\n  }\n\n  std::vector<Point> samplePoints;\n  std::vector<Point> processList;\n\n  if (!numPoints)\n    return samplePoints;\n\n  // create the grid\n  const float cellSize = minDist / sqrt(2.0f);\n\n  const int gridW = (int)ceil(1.0f / cellSize);\n  const int gridH = (int)ceil(1.0f / cellSize);\n\n  Grid grid(gridW, gridH, cellSize);\n\n  Point firstPoint;\n  do {\n    firstPoint = Point(generator.randomFloat(), generator.randomFloat());\n  } while (!(isCircle ? firstPoint.isInCircle() : firstPoint.isInRectangle()));\n\n  // update containers\n  processList.push_back(firstPoint);\n  samplePoints.push_back(firstPoint);\n  grid.insert(firstPoint);\n\n#if POISSON_PROGRESS_INDICATOR\n  size_t progress = 0;\n#endif\n\n  // generate new points for each point in the queue\n  while (!processList.empty() && samplePoints.size() <= numPoints) {\n#if POISSON_PROGRESS_INDICATOR\n    // a progress indicator, kind of\n    if ((samplePoints.size()) % 1000 == 0) {\n      const size_t newProgress = 200 * (samplePoints.size() + processList.size()) / numPoints;\n      if (newProgress != progress) {\n        progress = newProgress;\n        std::cout << \".\";\n      }\n    }\n#endif // POISSON_PROGRESS_INDICATOR\n\n    const Point point = popRandom<PRNG>(processList, generator);\n\n    for (uint32_t i = 0; i < newPointsCount; i++) {\n      const Point newPoint = generateRandomPointAround(point, minDist, generator);\n      const bool canFitPoint = isCircle ? newPoint.isInCircle() : newPoint.isInRectangle();\n\n      if (canFitPoint && !grid.isInNeighbourhood(newPoint, minDist, cellSize)) {\n        processList.push_back(newPoint);\n        samplePoints.push_back(newPoint);\n        grid.insert(newPoint);\n        continue;\n      }\n    }\n  }\n\n#if POISSON_PROGRESS_INDICATOR\n  std::cout << std::endl << std::endl;\n#endif // POISSON_PROGRESS_INDICATOR\n\n  return samplePoints;\n}\n\nPoint sampleVogelDisk(uint32_t idx, uint32_t numPoints, float phi) {\n  const float kGoldenAngle = 2.4f;\n\n  const float r = sqrtf(float(idx) + 0.5f) / sqrtf(float(numPoints));\n  const float theta = idx * kGoldenAngle + phi;\n\n  return Point(r * cosf(theta), r * sinf(theta));\n}\n\n/**\n   Return a vector of generated points\n**/\nstd::vector<Point> generateVogelPoints(uint32_t numPoints, bool isCircle = true, float phi = 0.0f, Point center = Point(0.5f, 0.5f)) {\n  std::vector<Point> samplePoints;\n\n  samplePoints.reserve(numPoints);\n\n  const uint32_t numSamples = isCircle ? 4 * numPoints : numPoints;\n\n  for (uint32_t i = 0; i != numPoints; i++) {\n    const Point p = sampleVogelDisk(i, numSamples, phi * 3.141592653f / 180.0f) + center;\n    samplePoints.push_back(p);\n  }\n\n  return samplePoints;\n}\n\n/**\n   Return a vector of generated points\n**/\ntemplate<typename PRNG = DefaultPRNG>\nstd::vector<Point> generateJitteredGridPoints(uint32_t numPoints,\n                                              PRNG& generator,\n                                              bool isCircle = false,\n                                              float jitterRadius = 0.004f,\n                                              Point center = Point(0.5f, 0.5f)) {\n  std::vector<Point> samplePoints;\n\n  samplePoints.reserve(numPoints);\n\n  const uint32_t gridSize = uint32_t(sqrt(numPoints));\n\n  for (uint32_t x = 0; x != gridSize; x++) {\n    for (uint32_t y = 0; y != gridSize; y++) {\n      Point p;\n      do {\n        const Point offs = generateRandomPointAround(Point(0, 0), jitterRadius, generator) - center + Point(0.5f, 0.5f);\n        p = Point(float(x) / gridSize, float(y) / gridSize) + offs;\n        // generate a new point until it is within the boundaries\n      } while (!p.isInRectangle());\n\n      if (isCircle)\n        if (!p.isInCircle())\n          continue;\n\n      samplePoints.push_back(p);\n    }\n  }\n\n  return samplePoints;\n}\n\nnamespace {\n\n// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html\nfloat radicalInverse_VdC(uint32_t bits) {\n  bits = (bits << 16u) | (bits >> 16u);\n  bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);\n  bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);\n  bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);\n  bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);\n  return float(float(bits) * 2.3283064365386963e-10); // / 0x100000000\n}\n\nPoint hammersley2d(uint32_t i, uint32_t N) {\n  return Point(float(i) / float(N), radicalInverse_VdC(i));\n}\n\n} // namespace\n\n/**\n   Return a vector of generated points\n**/\nstd::vector<Point> generateHammersleyPoints(uint32_t numPoints) {\n  std::vector<Point> samplePoints;\n\n  samplePoints.reserve(numPoints);\n\n  const uint32_t gridSize = uint32_t(sqrt(numPoints));\n\n  for (uint32_t i = 0; i != numPoints; i++) {\n    Point p = hammersley2d(i, numPoints);\n\n    samplePoints.push_back(p);\n  }\n  return samplePoints;\n}\n\ntemplate<typename PRNG = DefaultPRNG>\nvoid shuffle(std::vector<Point>& points, PRNG& generator) {\n  const int length = (int)points.size();\n  if (!length)\n    return;\n  // Fisher-Yates shuffle\n  for (int i = length - 1; i-- > 0;) {\n    std::swap(points[i], points[generator.randomInt(i)]);\n  }\n}\n\n} // namespace PoissonGenerator\n"
  },
  {
    "path": "README.md",
    "content": "**Poisson Disk Points Generator**\n\n(C) Sergey Kosarevsky, 2014-2026\n\n@corporateshark sk@linderdaum.com\n\nhttp://www.linderdaum.com\n\nhttp://blog.linderdaum.com\n\n=============================\n\nPoisson disk & Vogel disk points generator in a single file header-only C++11 library.\n\nUsage example:\n--------------\n```\n#define POISSON_PROGRESS_INDICATOR 1\n#include \"PoissonGenerator.h\"\n...\nPoissonGenerator::DefaultPRNG PRNG;\nconst auto Points = PoissonGenerator::generatePoissonPoints( numPoints, PRNG );\n...\nconst auto Points = PoissonGenerator::generateVogelPoints( numPoints );\n...\nconst auto Points = PoissonGenerator::generateJitteredGridPoints( numPoints, PRNG );\n...\nconst auto Points = PoissonGenerator::generateHammersleyPoints( numPoints );\n```\n\nBuild instructions:\n-----------\n\nLinux/OSX: ```gcc Poisson.cpp -std=c++17 -lstdc++```\n\nWindows: ```cmake -G \"Visual Studio 17 2022\" -A x64```\n\nDemo app usage:\n---------------\n\tPoisson [density-map-rgb24.bmp] [--raw-points] [--num-points=<value>] [--shuffle] [--save-frames] [--save-video[=<skip-frames>]] [--save-frames] [--square] [--vogel-disk | --jittered-grid]\n\nAlgorithm description can be found in \"Fast Poisson Disk Sampling in Arbitrary Dimensions\"\nhttp://people.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf\n\nImplementation is based on http://devmag.org.za/2009/05/03/poisson-disk-sampling/\n\n=============================\n\nPoisson disk\n\n![Poisson disk](.github/1_Poisson_disk.png)\n\nPoisson rectangle\n\n![Poisson rectange](.github/2_Poisson_rect.png)\n\nPoisson rectangle with custom density map\n\n![Poisson rectangle with custom density map](.github/3_Poisson_density.png)\n\nVogel disk\n\n![Vogel disk](.github/4_Vogel_disk.png)\n\nJittered grid\n\n![image](.github/5_Jittered_grid.png)\n\nHammersley points\n\n![Points](.github/6_Hammersley.png)\n"
  }
]