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 rectangle

Poisson rectangle with custom density map

Vogel disk

Jittered grid

Hammersley points

gitextract_pvtu5rlt/ ├── .clang-format ├── .github/ │ └── workflows/ │ └── c-cpp.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Poisson.cpp ├── PoissonGenerator.h └── README.md
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.