master 5339f69a0661 cached
8 files
34.1 KB
10.3k tokens
24 symbols
1 requests
Download .txt
Repository: corporateshark/poisson-disk-generator
Branch: master
Commit: 5339f69a0661
Files: 8
Total size: 34.1 KB

Directory structure:
gitextract_pvtu5rlt/

├── .clang-format
├── .github/
│   └── workflows/
│       └── c-cpp.yml
├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── Poisson.cpp
├── PoissonGenerator.h
└── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .clang-format
================================================
---
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines:  Left
# AlingOperands: true # Unsupported
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: false
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakStringLiterals: true
ColumnLimit: 140
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
FixNamespaceComments: true
IncludeBlocks: Preserve
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
# LanguageKind: Cpp # Unsupported
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
# ObjCBinPackProtocolList: Never # Unsupported
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 5
PenaltyBreakBeforeFirstCallParameter: 10
PenaltyBreakComment: 60
PenaltyBreakFirstLessLess: 20
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
# SpaceBeforeCtorInitializerColon: false # Unsupported
# SpaceBeforeInheritanceColon: false # Unsupported
SpaceBeforeParens: ControlStatements
# SpaceBeforeRangeBasedForLoopColon: true # Unsupported
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 2
UseTab: Never
...


================================================
FILE: .github/workflows/c-cpp.yml
================================================
name: C/C++ CI

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

jobs:
  cmake-build:
      strategy:
        fail-fast: false
        matrix:
          os: [ubuntu-latest, windows-latest, macos-latest]
          generator: ["Default Generator", "Unix Makefiles"]
      env:
        CMAKE_GENERATOR: >-
          ${{format(matrix.generator != 'Default Generator' && '-G "{0}"' || '', matrix.generator)}}
      runs-on: ${{ matrix.os }}
      steps:
        - uses: actions/checkout@v6
          with:
            submodules: recursive

        - name: Get number of CPU cores
          uses: SimenB/github-actions-cpu-cores@v2

        - name: Build
          shell: bash
          run: |
            cmake ${{ env.CMAKE_GENERATOR }} -S "${{ github.workspace }}" -B build
            cd build
            cmake --build . --parallel ${{ steps.cpu-cores.outputs.count }}


================================================
FILE: .gitignore
================================================
# Compiled Object files
*.slo
*.lo
*.o
*.obj

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Compiled Static libraries
*.lai
*.la
*.a
*.lib

# Executables
*.exe
*.out
*.app

# VisualStudio files
*.suo
*.user
*.sdf
*.opensdf

Debug/
Release/
ipch/
build/


================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.22)

project("poisson-disk-generator")

add_library(PoissonGenerator INTERFACE PoissonGenerator.h)

add_executable(Poisson Poisson.cpp)

target_link_libraries(Poisson PRIVATE PoissonGenerator)

if(MSVC)
  target_compile_definitions(Poisson PRIVATE _CRT_SECURE_NO_WARNINGS)
endif()

set_property(TARGET PoissonGenerator PROPERTY CXX_STANDARD 17)
set_property(TARGET PoissonGenerator PROPERTY CXX_STANDARD_REQUIRED ON)


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014-2026 Sergey Kosarevsky

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: Poisson.cpp
================================================
/**
 * \file Poisson.cpp
 * \brief
 *
 * Poisson Disk Points Generator example
 *
 * \version 1.7.0
 * \date 21/01/2026
 * \author Sergey Kosarevsky, 2014-2026
 * \author support@linderdaum.com   http://www.linderdaum.com   http://blog.linderdaum.com
 */

/*
   To compile:
      gcc Poisson.cpp -std=c++17 -lstdc++
*/

#include <math.h>
#include <memory.h>
#include <string.h>

#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <vector>

#define POISSON_PROGRESS_INDICATOR 1
#include "PoissonGenerator.h"

///////////////// User selectable parameters ///////////////////////////////

const int kNumPointsDefaultPoisson = 20000; // default number of points to generate for Poisson disk
const int kNumPointsDefaultVogel = 2000; // default number of points to generate for Vogel disk
const int kNumPointsDefaultJittered = 2500; // default number of points to generate for jittered grid
const int kImageSize = 512; // generate RGB image [ImageSize x ImageSize]

////////////////////////////////////////////////////////////////////////////

float* g_DensityMap = nullptr;

#if defined(__GNUC__)
#define GCC_PACK(n) __attribute__((packed, aligned(n)))
#else
#define GCC_PACK(n) __declspec(align(n))
#endif // __GNUC__

#pragma pack(push, 1)
struct GCC_PACK(1) sBMPHeader {
  // BITMAPFILEHEADER
  unsigned short bfType;
  uint32_t bfSize;
  unsigned short bfReserved1;
  unsigned short bfReserved2;
  uint32_t bfOffBits;
  // BITMAPINFOHEADER
  uint32_t biSize;
  uint32_t biWidth;
  uint32_t biHeight;
  unsigned short biPlanes;
  unsigned short biBitCount;
  uint32_t biCompression;
  uint32_t biSizeImage;
  uint32_t biXPelsPerMeter;
  uint32_t biYPelsPerMeter;
  uint32_t biClrUsed;
  uint32_t biClrImportant;
};
#pragma pack(pop)

///////////////// Uncompressed AVI Writer //////////////////////////////////

// Simple uncompressed AVI writer (grayscale 8-bit, no audio)
// AVI format reference: https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference
class AVIWriter {
 public:
  AVIWriter(const char* fileName, int width, int height, int skipFrames) {
    m_width = width;
    m_height = height;
    m_skipFrames = skipFrames;
    m_frameDataSize = width * height; // grayscale: 1 byte per pixel
    m_rowPadding = (4 - (width) % 4) % 4; // pad each row to 4-byte boundary (BMP/AVI requirement)
    m_paddedRowSize = width + m_rowPadding;
    m_paddedFrameSize = m_paddedRowSize * height;

    std::cout << "\nSaving video to `" << fileName << "`" << std::endl;

    m_file.open(fileName, std::ios::out | std::ios::binary);

    // write placeholder header (will be updated on close)
    writeHeader();
    m_moviStart = static_cast<uint32_t>(m_file.tellp());

    // start 'movi' LIST
    writeChunkHeader("LIST", 0); // size placeholder
    writeFourCC("movi");
  }
  ~AVIWriter() {
    const uint32_t moviEnd = static_cast<uint32_t>(m_file.tellp());
    const uint32_t moviSize = moviEnd - m_moviStart - 8; // exclude LIST header

    writeIndex(); // write index chunk 'idx1'

    const uint32_t fileEnd = static_cast<uint32_t>(m_file.tellp());

    // update movi LIST size
    m_file.seekp(m_moviStart + 4);
    writeUInt32(moviSize + 4); // +4 for 'movi' fourcc

    // update RIFF size
    m_file.seekp(4);
    writeUInt32(fileEnd - 8);

    // update frame count in header
    m_file.seekp(48); // dwTotalFrames in main AVI header
    writeUInt32(m_frameCount);

    m_file.seekp(140); // dwLength in stream header
    writeUInt32(m_frameCount);

    m_file.close();

    std::cout << "\nSaved AVI with " << m_frameCount << " frames" << std::endl;
  }

  bool addFrame(const void* bgrData, bool isLastFrame) {
    if (!isLastFrame && (m_inputFrameCount++ % m_skipFrames) != 0)
      return false;

    writeChunkHeader("00db", m_paddedFrameSize); // write frame chunk: '00db' for uncompressed DIB

    // convert BGR to grayscale and write with row padding
    const unsigned char* src = static_cast<const unsigned char*>(bgrData);
    std::vector<unsigned char> rowBuffer(m_paddedRowSize, 0);

    for (int y = 0; y < m_height; y++) {
      for (int x = 0; x < m_width; x++) {
        rowBuffer[x] = src[(y * m_width + x) * 3]; // in our grayscale case, just take the first channel
      }
      m_file.write(reinterpret_cast<const char*>(rowBuffer.data()), m_paddedRowSize);
    }

    m_frameCount++;

    return true;
  }

 private:
  std::ofstream m_file;
  int m_width = 0;
  int m_height = 0;
  int m_fps = 60;
  int m_skipFrames = 16;
  uint32_t m_inputFrameCount = 0;
  uint32_t m_frameCount = 0;
  uint32_t m_frameDataSize = 0;
  uint32_t m_moviStart = 0;
  int m_rowPadding = 0;
  int m_paddedRowSize = 0;
  uint32_t m_paddedFrameSize = 0;

  void writeFourCC(const char* fourcc) {
    m_file.write(fourcc, 4);
  }

  void writeUInt32(uint32_t value) {
    m_file.write(reinterpret_cast<const char*>(&value), 4);
  }

  void writeUInt16(uint16_t value) {
    m_file.write(reinterpret_cast<const char*>(&value), 2);
  }

  void writeChunkHeader(const char* fourcc, uint32_t size) {
    writeFourCC(fourcc);
    writeUInt32(size);
  }

  void writeHeader() {
    writeFourCC("RIFF");
    writeUInt32(0); // file size placeholder
    writeFourCC("AVI ");
    writeFourCC("LIST"); // hdrl LIST
    uint32_t hdrlSizePos = static_cast<uint32_t>(m_file.tellp());
    writeUInt32(0); // size placeholder
    writeFourCC("hdrl");
    // main AVI header (avih)
    writeFourCC("avih");
    writeUInt32(56); // header size
    writeUInt32(1000000 / m_fps); // dwMicroSecPerFrame
    writeUInt32(m_paddedFrameSize * m_fps); // dwMaxBytesPerSec
    writeUInt32(0); // dwPaddingGranularity
    writeUInt32(0x10); // dwFlags (AVIF_HASINDEX)
    writeUInt32(0); // dwTotalFrames (placeholder)
    writeUInt32(0); // dwInitialFrames
    writeUInt32(1); // dwStreams
    writeUInt32(m_paddedFrameSize); // dwSuggestedBufferSize
    writeUInt32(m_width); // dwWidth
    writeUInt32(m_height); // dwHeight
    writeUInt32(0); // dwReserved[4]
    writeUInt32(0);
    writeUInt32(0);
    writeUInt32(0);
    // stream LIST
    writeFourCC("LIST");
    uint32_t strlSizePos = static_cast<uint32_t>(m_file.tellp());
    writeUInt32(0); // size placeholder
    writeFourCC("strl");
    // stream header (strh)
    writeFourCC("strh");
    writeUInt32(56); // header size
    writeFourCC("vids"); // fccType
    writeFourCC("DIB "); // fccHandler (uncompressed)
    writeUInt32(0); // dwFlags
    writeUInt16(0); // wPriority
    writeUInt16(0); // wLanguage
    writeUInt32(0); // dwInitialFrames
    writeUInt32(1); // dwScale
    writeUInt32(m_fps); // dwRate
    writeUInt32(0); // dwStart
    writeUInt32(0); // dwLength (placeholder)
    writeUInt32(m_paddedFrameSize); // dwSuggestedBufferSize
    writeUInt32(0xFFFFFFFF); // dwQuality
    writeUInt32(0); // dwSampleSize
    writeUInt16(0); // rcFrame left
    writeUInt16(0); // rcFrame top
    writeUInt16(static_cast<uint16_t>(m_width)); // rcFrame right
    writeUInt16(static_cast<uint16_t>(m_height)); // rcFrame bottom
    // stream format (strf) - BITMAPINFOHEADER + palette for 8-bit grayscale
    writeFourCC("strf");
    writeUInt32(40 + 256 * 4); // header size + palette size
    writeUInt32(40); // biSize
    writeUInt32(m_width); // biWidth
    writeUInt32(m_height); // biHeight
    writeUInt16(1); // biPlanes
    writeUInt16(8); // biBitCount (8-bit grayscale)
    writeUInt32(0); // biCompression (BI_RGB)
    writeUInt32(m_paddedFrameSize); // biSizeImage
    writeUInt32(0); // biXPelsPerMeter
    writeUInt32(0); // biYPelsPerMeter
    writeUInt32(256); // biClrUsed
    writeUInt32(256); // biClrImportant

    // write grayscale palette (256 entries, BGRA format)
    for (int i = 0; i < 256; i++) {
      const uint8_t gray = static_cast<uint8_t>(i);
      m_file.put(gray); // B
      m_file.put(gray); // G
      m_file.put(gray); // R
      m_file.put(0); // A (reserved)
    }

    // update strl LIST size
    const uint32_t strlEnd = static_cast<uint32_t>(m_file.tellp());
    m_file.seekp(strlSizePos);
    writeUInt32(strlEnd - strlSizePos - 4);
    m_file.seekp(strlEnd);

    // Update hdrl LIST size
    const uint32_t hdrlEnd = static_cast<uint32_t>(m_file.tellp());
    m_file.seekp(hdrlSizePos);
    writeUInt32(hdrlEnd - hdrlSizePos - 4);
    m_file.seekp(hdrlEnd);
  }

  void writeIndex() {
    writeFourCC("idx1");
    writeUInt32(m_frameCount * 16); // index size

    uint32_t offset = 4; // offset from 'movi' to first frame data

    for (uint32_t i = 0; i < m_frameCount; i++) {
      writeFourCC("00db"); // chunk ID
      writeUInt32(0x10); // flags (AVIIF_KEYFRAME)
      writeUInt32(offset); // offset
      writeUInt32(m_paddedFrameSize); // size

      offset += m_paddedFrameSize + 8; // +8 for chunk header
    }
  }
};

///////////////// BMP Functions ////////////////////////////////////////////

void SaveBMP(const char* FileName, const void* RawBGRImage, int Width, int Height) {
  sBMPHeader Header;

  int ImageSize = Width * Height * 3;

  Header.bfType = 0x4D * 256 + 0x42;
  Header.bfSize = ImageSize + sizeof(sBMPHeader);
  Header.bfReserved1 = 0;
  Header.bfReserved2 = 0;
  Header.bfOffBits = 0x36;
  Header.biSize = 40;
  Header.biWidth = Width;
  Header.biHeight = Height;
  Header.biPlanes = 1;
  Header.biBitCount = 24;
  Header.biCompression = 0;
  Header.biSizeImage = ImageSize;
  Header.biXPelsPerMeter = 6000;
  Header.biYPelsPerMeter = 6000;
  Header.biClrUsed = 0;
  Header.biClrImportant = 0;

  std::ofstream File(FileName, std::ios::out | std::ios::binary);

  File.write((const char*)&Header, sizeof(Header));
  File.write((const char*)RawBGRImage, ImageSize);

  std::cout << "Saved " << FileName << std::endl;
}

unsigned char* LoadBMP(const char* FileName, int* OutWidth, int* OutHeight) {
  sBMPHeader Header;

  std::ifstream File(FileName, std::ifstream::binary);

  File.read((char*)&Header, sizeof(Header));

  *OutWidth = Header.biWidth;
  *OutHeight = Header.biHeight;

  const size_t DataSize = 3 * Header.biWidth * Header.biHeight;

  unsigned char* Img = new unsigned char[DataSize];

  File.read((char*)Img, DataSize);

  return Img;
}

void LoadDensityMap(const char* FileName) {
  std::cout << "Loading density map " << FileName << std::endl;

  int W, H;
  unsigned char* Data = LoadBMP(FileName, &W, &H);

  std::cout << "Loaded ( " << W << " x " << H << " ) " << std::endl;

  if (W != kImageSize || H != kImageSize) {
    std::cout << "ERROR: density map should be " << kImageSize << " x " << kImageSize << std::endl;

    exit(255);
  }

  g_DensityMap = new float[W * H];

  for (int y = 0; y != H; y++) {
    for (int x = 0; x != W; x++) {
      g_DensityMap[x + y * W] = float(Data[3 * (x + y * W)]) / 255.0f;
    }
  }

  delete[] (Data);
}

void PrintBanner() {
  std::cout << "Poisson disk points generator" << std::endl;
  std::cout << "Version " << PoissonGenerator::Version << std::endl;
  std::cout << "Sergey Kosarevsky, 2014-2026" << std::endl;
  std::cout << "support@linderdaum.com http://www.linderdaum.com http://blog.linderdaum.com" << std::endl;
  std::cout << std::endl;
  std::cout << "Usage: Poisson [density-map-rgb24.bmp] [--raw-points] [--num-points=<value>] [--square] [--vogel-disk | --jittered-grid | "
               "--hammersley] [--shuffle] [--save-frames] [--save-video[=<skip-frames>]]"
            << std::endl;
  std::cout << std::endl;
}

int main(int argc, char** argv) {
  PrintBanner();

  if (argc > 1 && !strstr(argv[1], "--")) {
    LoadDensityMap(argv[1]);
  }

  auto hasCmdLineArg = [argc, argv](const char* arg) -> bool {
    for (int i = 1; i < argc; i++) {
      if (!strcmp(argv[i], arg)) {
        return true;
      }
    }
    return false;
  };

  auto getCmdLineValue = [argc, argv](const char* arg, unsigned int defaultValue) -> unsigned int {
    for (int i = 1; i < argc; i++) {
      if (strstr(argv[i], arg)) {
        unsigned int v = defaultValue;
        return sscanf(argv[i], "--num-points=%u", &v) == 1 ? v : defaultValue;
      }
    }
    return defaultValue;
  };

  auto getCmdLineValueSkipFrames = [argc, argv](const char* arg, unsigned int defaultValue) -> unsigned int {
    for (int i = 1; i < argc; i++) {
      if (strstr(argv[i], arg)) {
        unsigned int v = defaultValue;
        if (sscanf(argv[i], "--save-video=%u", &v) == 1) {
          return v;
        }
        return defaultValue;
      }
    }
    return defaultValue;
  };

  auto hasCmdLineArgPrefix = [argc, argv](const char* prefix) -> bool {
    for (int i = 1; i < argc; i++) {
      if (strstr(argv[i], prefix) == argv[i]) {
        return true;
      }
    }
    return false;
  };

  const bool cmdRawPointsOutput = hasCmdLineArg("--raw-points");
  const bool cmdSquare = hasCmdLineArg("--square");
  const bool cmdVogelDisk = hasCmdLineArg("--vogel-disk");
  const bool cmdJitteredGrid = hasCmdLineArg("--jittered-grid");

  const bool cmdHammersley = hasCmdLineArg("--hammersley");

  const bool cmdShuffle = hasCmdLineArg("--shuffle");
  const bool cmdSaveFrames = hasCmdLineArg("--save-frames");
  const bool cmdSaveVideo = hasCmdLineArgPrefix("--save-video");
  const unsigned int videoSkipFrames = getCmdLineValueSkipFrames("--save-video", 16);

  const unsigned int numPoints = getCmdLineValue(
      "--num-points=", cmdVogelDisk ? kNumPointsDefaultVogel : (cmdJitteredGrid ? kNumPointsDefaultJittered : kNumPointsDefaultPoisson));

  std::cout << "NumPoints = " << numPoints << std::endl;

  PoissonGenerator::DefaultPRNG PRNG;

  auto Points = cmdVogelDisk      ? PoissonGenerator::generateVogelPoints(numPoints)
                : cmdJitteredGrid ? PoissonGenerator::generateJitteredGridPoints(numPoints, PRNG, !cmdSquare)
                : cmdHammersley   ? PoissonGenerator::generateHammersleyPoints(numPoints)
                                  : PoissonGenerator::generatePoissonPoints(numPoints, PRNG, !cmdSquare);

  // prepare BGR image
  const size_t DataSize = 3 * kImageSize * kImageSize;

  unsigned char* Img = new unsigned char[DataSize];

  memset(Img, 0, DataSize);

  if (cmdShuffle) {
    std::cout << "Shuffling points..." << std::endl;
    PoissonGenerator::shuffle(Points, PRNG);
  }

  std::unique_ptr<AVIWriter> aviWriter = cmdSaveVideo ? std::make_unique<AVIWriter>("Points.avi", kImageSize, kImageSize, videoSkipFrames)
                                                      : nullptr;

  int frame = 0;
  size_t currentPoint = 0;
  const size_t totalPoints = Points.size();

  for (const auto& i : Points) {
    currentPoint++;
    const int x = int(i.x * kImageSize);
    const int y = int(i.y * kImageSize);
    if (x < 0 || y < 0 || x >= kImageSize || y >= kImageSize)
      continue;
    if (g_DensityMap) {
      // dice
      float R = PRNG.randomFloat();
      float P = g_DensityMap[x + y * kImageSize];
      if (R > P)
        continue;
    }
    const int Base = 3 * (x + y * kImageSize);
    Img[Base + 0] = Img[Base + 1] = Img[Base + 2] = 255;

    if (cmdSaveFrames) {
      char fileName[64] = {};
      snprintf(fileName, sizeof(fileName), "pnt%05i.bmp", frame++);
      SaveBMP(fileName, Img, kImageSize, kImageSize);
    }

    if (aviWriter && aviWriter->addFrame(Img, currentPoint == totalPoints)) {
      std::cout << "\rRendering points to video: " << currentPoint << "/" << totalPoints << std::flush;
    }
  }

  // always flush the final frame to video
  if (aviWriter && aviWriter->addFrame(Img, true)) {
    std::cout << "\rRendering points to video: " << currentPoint << "/" << totalPoints << std::flush;
  }

  aviWriter = nullptr;

  SaveBMP("Points.bmp", Img, kImageSize, kImageSize);

  delete[] (Img);

  // dump points to a text file
  std::ofstream File("points.txt", std::ios::out);

  if (cmdRawPointsOutput) {
    File << "NumPoints = " << Points.size() << std::endl;

    for (const auto& p : Points) {
      File << p.x << " " << p.y << std::endl;
    }
  } else {
    File << "const vec2 points[" << Points.size() << "]" << std::endl;
    File << "{" << std::endl;
    File << std::fixed << std::setprecision(6);
    for (const auto& p : Points) {
      File << "\tvec2(" << p.x << "f, " << p.y << "f)," << std::endl;
    }
    File << "};" << std::endl;
  }

  return 0;
}


================================================
FILE: PoissonGenerator.h
================================================
/**
 * \file PoissonGenerator.h
 * \brief
 *
 * Poisson Disk Points Generator
 *
 * \version 1.7.0
 * \date 21/01/2026
 * \author Sergey Kosarevsky, 2014-2026
 * \author support@linderdaum.com   http://www.linderdaum.com   http://blog.linderdaum.com
 *
 * https://github.com/corporateshark/poisson-disk-generator
 */

/*
   Usage example:

      #define POISSON_PROGRESS_INDICATOR 1
      #include "PoissonGenerator.h"
      ...
      PoissonGenerator::DefaultPRNG PRNG;
      const auto Points = PoissonGenerator::generatePoissonPoints( NumPoints, PRNG );
      ...
      const auto Points = PoissonGenerator::generateVogelPoints( NumPoints );
*/

// Fast Poisson Disk Sampling in Arbitrary Dimensions
// http://people.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf

// Implementation based on http://devmag.org.za/2009/05/03/poisson-disk-sampling/

/* Versions history:
 *    1.7     Jan 21, 2026    New arguments: `--shuffle`, `--save-frames`, and `--save-video`; fixed warnings
 *    1.6.2   Aug  8, 2025    Dropped the `argh` library dependency
 *    1.6.1   Feb 16, 2024    Reformatted using .clang-format
 *    1.6     May 29, 2023    Added generateHammersleyPoints() to generate Hammersley points
 *    1.5     Mar 26, 2022    Added generateJitteredGridPoints() to generate jittered grid points
 *    1.4.1   Dec 12, 2021    Replaced default Mersenne Twister and <random> with fast and lightweight LCG
 *    1.4     Dec  5, 2021    Added generateVogelPoints() to generate Vogel disk points
 *    1.3     Mar 14, 2021    Bugfixes: number of points in the !isCircle mode, incorrect loop boundaries
 *    1.2     Dec 28, 2019    Bugfixes; more consistent progress indicator; new command line options in demo app
 *    1.1.6   Dec  7, 2019    Removed duplicate seed initialization; fixed warnings
 *    1.1.5   Jun 16, 2019    In-class initializers; default ctors; naming, shorter code
 *    1.1.4   Oct 19, 2016    POISSON_PROGRESS_INDICATOR can be defined outside of the header file, disabled by default
 *    1.1.3a  Jun  9, 2016    Update constructor for DefaultPRNG
 *    1.1.3   Mar 10, 2016    Header-only library, no global mutable state
 *    1.1.2   Apr  9, 2015    Output a text file with XY coordinates
 *    1.1.1   May 23, 2014    Initialize PRNG seed, fixed uninitialized fields
 *    1.1     May  7, 2014    Support of density maps
 *    1.0     May  6, 2014
 */

#include <stdint.h>
#include <vector>

namespace PoissonGenerator {

const char* Version = "1.7.0 (21/01/2026)";

class DefaultPRNG {
 public:
  DefaultPRNG() = default;
  explicit DefaultPRNG(unsigned int seed) : seed_(seed) {}
  inline float randomFloat() {
    seed_ *= 521167;
    uint32_t a = (seed_ & 0x007fffff) | 0x40000000;
    // remap to 0..1
    return 0.5f * (*((float*)&a) - 2.0f);
  }
  inline uint32_t randomInt(uint32_t maxInt) {
    return uint32_t(randomFloat() * maxInt);
  }
  inline uint32_t getSeed() const {
    return seed_;
  }

 private:
  uint32_t seed_ = 7133167;
};

struct Point {
  Point() = default;
  Point(float X, float Y) : x(X), y(Y), valid_(true) {}
  float x = 0.0f;
  float y = 0.0f;
  bool valid_ = false;
  //
  bool isInRectangle() const {
    return x >= 0 && y >= 0 && x <= 1 && y <= 1;
  }
  //
  bool isInCircle() const {
    const float fx = x - 0.5f;
    const float fy = y - 0.5f;
    return (fx * fx + fy * fy) <= 0.25f;
  }
  Point& operator+(const Point& p) {
    x += p.x;
    y += p.y;
    return *this;
  }
  Point& operator-(const Point& p) {
    x -= p.x;
    y -= p.y;
    return *this;
  }
};

struct GridPoint {
  GridPoint() = delete;
  GridPoint(int X, int Y) : x(X), y(Y) {}
  int x;
  int y;
};

float getDistance(const Point& P1, const Point& P2) {
  return sqrt((P1.x - P2.x) * (P1.x - P2.x) + (P1.y - P2.y) * (P1.y - P2.y));
}

GridPoint imageToGrid(const Point& P, float cellSize) {
  return GridPoint((int)(P.x / cellSize), (int)(P.y / cellSize));
}

struct Grid {
  Grid(int w, int h, float cellSize) : w_(w), h_(h), cellSize_(cellSize) {
    grid_.resize(h_);
    for (auto i = grid_.begin(); i != grid_.end(); i++) {
      i->resize(w);
    }
  }
  void insert(const Point& p) {
    const GridPoint g = imageToGrid(p, cellSize_);
    grid_[g.x][g.y] = p;
  }
  bool isInNeighbourhood(const Point& point, float minDist, float cellSize) {
    const GridPoint g = imageToGrid(point, cellSize);

    // number of adjacent cells to look for neighbour points
    const int D = 5;

    // scan the neighbourhood of the point in the grid
    for (int i = g.x - D; i <= g.x + D; i++) {
      for (int j = g.y - D; j <= g.y + D; j++) {
        if (i >= 0 && i < w_ && j >= 0 && j < h_) {
          const Point P = grid_[i][j];

          if (P.valid_ && getDistance(P, point) < minDist)
            return true;
        }
      }
    }

    return false;
  }

 private:
  int w_;
  int h_;
  float cellSize_;
  std::vector<std::vector<Point>> grid_;
};

template<typename PRNG>
Point popRandom(std::vector<Point>& points, PRNG& generator) {
  const int idx = generator.randomInt(static_cast<int>(points.size()) - 1);
  const Point p = points[idx];
  points.erase(points.begin() + idx);
  return p;
}

template<typename PRNG>
Point generateRandomPointAround(const Point& p, float minDist, PRNG& generator) {
  // start with non-uniform distribution
  const float R1 = generator.randomFloat();
  const float R2 = generator.randomFloat();

  // radius should be between MinDist and 2 * MinDist
  const float radius = minDist * (R1 + 1.0f);

  // random angle
  const float angle = 2 * 3.141592653589f * R2;

  // the new point is generated around the point (x, y)
  const float x = p.x + radius * cos(angle);
  const float y = p.y + radius * sin(angle);

  return Point(x, y);
}

/**
   Return a vector of generated points

   NewPointsCount - refer to bridson-siggraph07-poissondisk.pdf for details (the value 'k')
   Circle  - 'true' to fill a circle, 'false' to fill a rectangle
   MinDist - minimal distance estimator, use negative value for default
**/
template<typename PRNG = DefaultPRNG>
std::vector<Point> generatePoissonPoints(uint32_t numPoints,
                                         PRNG& generator,
                                         bool isCircle = true,
                                         uint32_t newPointsCount = 30,
                                         float minDist = -1.0f) {
  numPoints *= 2;

  // if we want to generate a Poisson square shape, multiply the estimate number of points by PI/4 due to reduced shape area
  if (!isCircle) {
    const double Pi_4 = 0.785398163397448309616; // PI/4
    numPoints = static_cast<int>(Pi_4 * numPoints);
  }

  if (minDist < 0.0f) {
    minDist = sqrt(float(numPoints)) / float(numPoints);
  }

  std::vector<Point> samplePoints;
  std::vector<Point> processList;

  if (!numPoints)
    return samplePoints;

  // create the grid
  const float cellSize = minDist / sqrt(2.0f);

  const int gridW = (int)ceil(1.0f / cellSize);
  const int gridH = (int)ceil(1.0f / cellSize);

  Grid grid(gridW, gridH, cellSize);

  Point firstPoint;
  do {
    firstPoint = Point(generator.randomFloat(), generator.randomFloat());
  } while (!(isCircle ? firstPoint.isInCircle() : firstPoint.isInRectangle()));

  // update containers
  processList.push_back(firstPoint);
  samplePoints.push_back(firstPoint);
  grid.insert(firstPoint);

#if POISSON_PROGRESS_INDICATOR
  size_t progress = 0;
#endif

  // generate new points for each point in the queue
  while (!processList.empty() && samplePoints.size() <= numPoints) {
#if POISSON_PROGRESS_INDICATOR
    // a progress indicator, kind of
    if ((samplePoints.size()) % 1000 == 0) {
      const size_t newProgress = 200 * (samplePoints.size() + processList.size()) / numPoints;
      if (newProgress != progress) {
        progress = newProgress;
        std::cout << ".";
      }
    }
#endif // POISSON_PROGRESS_INDICATOR

    const Point point = popRandom<PRNG>(processList, generator);

    for (uint32_t i = 0; i < newPointsCount; i++) {
      const Point newPoint = generateRandomPointAround(point, minDist, generator);
      const bool canFitPoint = isCircle ? newPoint.isInCircle() : newPoint.isInRectangle();

      if (canFitPoint && !grid.isInNeighbourhood(newPoint, minDist, cellSize)) {
        processList.push_back(newPoint);
        samplePoints.push_back(newPoint);
        grid.insert(newPoint);
        continue;
      }
    }
  }

#if POISSON_PROGRESS_INDICATOR
  std::cout << std::endl << std::endl;
#endif // POISSON_PROGRESS_INDICATOR

  return samplePoints;
}

Point sampleVogelDisk(uint32_t idx, uint32_t numPoints, float phi) {
  const float kGoldenAngle = 2.4f;

  const float r = sqrtf(float(idx) + 0.5f) / sqrtf(float(numPoints));
  const float theta = idx * kGoldenAngle + phi;

  return Point(r * cosf(theta), r * sinf(theta));
}

/**
   Return a vector of generated points
**/
std::vector<Point> generateVogelPoints(uint32_t numPoints, bool isCircle = true, float phi = 0.0f, Point center = Point(0.5f, 0.5f)) {
  std::vector<Point> samplePoints;

  samplePoints.reserve(numPoints);

  const uint32_t numSamples = isCircle ? 4 * numPoints : numPoints;

  for (uint32_t i = 0; i != numPoints; i++) {
    const Point p = sampleVogelDisk(i, numSamples, phi * 3.141592653f / 180.0f) + center;
    samplePoints.push_back(p);
  }

  return samplePoints;
}

/**
   Return a vector of generated points
**/
template<typename PRNG = DefaultPRNG>
std::vector<Point> generateJitteredGridPoints(uint32_t numPoints,
                                              PRNG& generator,
                                              bool isCircle = false,
                                              float jitterRadius = 0.004f,
                                              Point center = Point(0.5f, 0.5f)) {
  std::vector<Point> samplePoints;

  samplePoints.reserve(numPoints);

  const uint32_t gridSize = uint32_t(sqrt(numPoints));

  for (uint32_t x = 0; x != gridSize; x++) {
    for (uint32_t y = 0; y != gridSize; y++) {
      Point p;
      do {
        const Point offs = generateRandomPointAround(Point(0, 0), jitterRadius, generator) - center + Point(0.5f, 0.5f);
        p = Point(float(x) / gridSize, float(y) / gridSize) + offs;
        // generate a new point until it is within the boundaries
      } while (!p.isInRectangle());

      if (isCircle)
        if (!p.isInCircle())
          continue;

      samplePoints.push_back(p);
    }
  }

  return samplePoints;
}

namespace {

// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
float radicalInverse_VdC(uint32_t bits) {
  bits = (bits << 16u) | (bits >> 16u);
  bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
  bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
  bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
  bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
  return float(float(bits) * 2.3283064365386963e-10); // / 0x100000000
}

Point hammersley2d(uint32_t i, uint32_t N) {
  return Point(float(i) / float(N), radicalInverse_VdC(i));
}

} // namespace

/**
   Return a vector of generated points
**/
std::vector<Point> generateHammersleyPoints(uint32_t numPoints) {
  std::vector<Point> samplePoints;

  samplePoints.reserve(numPoints);

  const uint32_t gridSize = uint32_t(sqrt(numPoints));

  for (uint32_t i = 0; i != numPoints; i++) {
    Point p = hammersley2d(i, numPoints);

    samplePoints.push_back(p);
  }
  return samplePoints;
}

template<typename PRNG = DefaultPRNG>
void shuffle(std::vector<Point>& points, PRNG& generator) {
  const int length = (int)points.size();
  if (!length)
    return;
  // Fisher-Yates shuffle
  for (int i = length - 1; i-- > 0;) {
    std::swap(points[i], points[generator.randomInt(i)]);
  }
}

} // namespace PoissonGenerator


================================================
FILE: README.md
================================================
**Poisson Disk Points Generator**

(C) Sergey Kosarevsky, 2014-2026

@corporateshark sk@linderdaum.com

http://www.linderdaum.com

http://blog.linderdaum.com

=============================

Poisson disk & Vogel disk points generator in a single file header-only C++11 library.

Usage example:
--------------
```
#define POISSON_PROGRESS_INDICATOR 1
#include "PoissonGenerator.h"
...
PoissonGenerator::DefaultPRNG PRNG;
const auto Points = PoissonGenerator::generatePoissonPoints( numPoints, PRNG );
...
const auto Points = PoissonGenerator::generateVogelPoints( numPoints );
...
const auto Points = PoissonGenerator::generateJitteredGridPoints( numPoints, PRNG );
...
const auto Points = PoissonGenerator::generateHammersleyPoints( numPoints );
```

Build instructions:
-----------

Linux/OSX: ```gcc Poisson.cpp -std=c++17 -lstdc++```

Windows: ```cmake -G "Visual Studio 17 2022" -A x64```

Demo app usage:
---------------
	Poisson [density-map-rgb24.bmp] [--raw-points] [--num-points=<value>] [--shuffle] [--save-frames] [--save-video[=<skip-frames>]] [--save-frames] [--square] [--vogel-disk | --jittered-grid]

Algorithm description can be found in "Fast Poisson Disk Sampling in Arbitrary Dimensions"
http://people.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf

Implementation is based on http://devmag.org.za/2009/05/03/poisson-disk-sampling/

=============================

Poisson disk

![Poisson disk](.github/1_Poisson_disk.png)

Poisson rectangle

![Poisson rectange](.github/2_Poisson_rect.png)

Poisson rectangle with custom density map

![Poisson rectangle with custom density map](.github/3_Poisson_density.png)

Vogel disk

![Vogel disk](.github/4_Vogel_disk.png)

Jittered grid

![image](.github/5_Jittered_grid.png)

Hammersley points

![Points](.github/6_Hammersley.png)
Download .txt
gitextract_pvtu5rlt/

├── .clang-format
├── .github/
│   └── workflows/
│       └── c-cpp.yml
├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── Poisson.cpp
├── PoissonGenerator.h
└── README.md
Download .txt
SYMBOL INDEX (24 symbols across 2 files)

FILE: Poisson.cpp
  type GCC_PACK (line 49) | struct GCC_PACK
  class AVIWriter (line 75) | class AVIWriter {
    method AVIWriter (line 77) | AVIWriter(const char* fileName, int width, int height, int skipFrames) {
    method addFrame (line 126) | bool addFrame(const void* bgrData, bool isLastFrame) {
    method writeFourCC (line 162) | void writeFourCC(const char* fourcc) {
    method writeUInt32 (line 166) | void writeUInt32(uint32_t value) {
    method writeUInt16 (line 170) | void writeUInt16(uint16_t value) {
    method writeChunkHeader (line 174) | void writeChunkHeader(const char* fourcc, uint32_t size) {
    method writeHeader (line 179) | void writeHeader() {
    method writeIndex (line 266) | void writeIndex() {
  function SaveBMP (line 285) | void SaveBMP(const char* FileName, const void* RawBGRImage, int Width, i...
  function LoadDensityMap (line 334) | void LoadDensityMap(const char* FileName) {
  function PrintBanner (line 359) | void PrintBanner() {
  function main (line 371) | int main(int argc, char** argv) {

FILE: PoissonGenerator.h
  function namespace (line 56) | namespace PoissonGenerator {
  function isInCircle (line 92) | bool isInCircle() const {
  type GridPoint (line 109) | struct GridPoint {
  function getDistance (line 116) | float getDistance(const Point& P1, const Point& P2) {
  function GridPoint (line 120) | GridPoint imageToGrid(const Point& P, float cellSize) {
  function insert (line 124) | struct Grid {
  function isInNeighbourhood (line 135) | bool isInNeighbourhood(const Point& point, float minDist, float cellSize) {
  function Point (line 278) | Point sampleVogelDisk(uint32_t idx, uint32_t numPoints, float phi) {
  function radicalInverse_VdC (line 343) | float radicalInverse_VdC(uint32_t bits) {
  function Point (line 352) | Point hammersley2d(uint32_t i, uint32_t N) {
Condensed preview — 8 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (37K chars).
[
  {
    "path": ".clang-format",
    "chars": 2305,
    "preview": "---\nAccessModifierOffset: -1\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclaration"
  },
  {
    "path": ".github/workflows/c-cpp.yml",
    "chars": 903,
    "preview": "name: C/C++ CI\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\njobs:\n  cmake-build:\n"
  },
  {
    "path": ".gitignore",
    "chars": 258,
    "preview": "# 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 librari"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 450,
    "preview": "cmake_minimum_required(VERSION 3.22)\n\nproject(\"poisson-disk-generator\")\n\nadd_library(PoissonGenerator INTERFACE PoissonG"
  },
  {
    "path": "LICENSE",
    "chars": 1088,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014-2026 Sergey Kosarevsky\n\nPermission is hereby granted, free of charge, to any p"
  },
  {
    "path": "Poisson.cpp",
    "chars": 16219,
    "preview": "/**\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"
  },
  {
    "path": "PoissonGenerator.h",
    "chars": 11840,
    "preview": "/**\n * \\file PoissonGenerator.h\n * \\brief\n *\n * Poisson Disk Points Generator\n *\n * \\version 1.7.0\n * \\date 21/01/2026\n"
  },
  {
    "path": "README.md",
    "chars": 1805,
    "preview": "**Poisson Disk Points Generator**\n\n(C) Sergey Kosarevsky, 2014-2026\n\n@corporateshark sk@linderdaum.com\n\nhttp://www.linde"
  }
]

About this extraction

This page contains the full source code of the corporateshark/poisson-disk-generator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 8 files (34.1 KB), approximately 10.3k tokens, and a symbol index with 24 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!