KiCad PCB EDA Suite
Loading...
Searching...
No Matches
poisson_disk.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 */
20
22
23#include <algorithm>
24#include <cmath>
25#include <utility>
26
27#include <boost/random/mersenne_twister.hpp>
28#include <boost/random/uniform_int_distribution.hpp>
29#include <boost/random/uniform_real_distribution.hpp>
30
31
32std::vector<VECTOR2D> POISSON_DISK::ToroidalUnitTile( double aMinDist, uint32_t aSeed )
33{
34 std::vector<VECTOR2D> samples;
35
36 if( aMinDist <= 0.0 || aMinDist > 0.5 )
37 return samples; // unsupported / degenerate
38
39 // The cell count must tile the torus exactly: with a truncated seam cell the +-2 cell
40 // neighborhood spans less than aMinDist of actual distance near the wrap seam, letting
41 // cross-seam sample pairs slip under the minimum distance. 1/gridN <= aMinDist/sqrt(2)
42 // keeps at most one sample per cell, and 2/gridN >= aMinDist holds for any aMinDist
43 // <= 0.5, so the +-2 scan still covers the exclusion radius.
44 const int gridN = std::max( 3, (int) std::ceil( std::sqrt( 2.0 ) / aMinDist ) );
45 const double cellSize = 1.0 / gridN;
46
47 std::vector<int> grid( (size_t) gridN * gridN, -1 );
48 std::vector<int> active;
49
50 auto cellOf =
51 [&]( const VECTOR2D& p ) -> std::pair<int, int>
52 {
53 int gx = (int) std::floor( p.x / cellSize );
54 int gy = (int) std::floor( p.y / cellSize );
55 gx = ( ( gx % gridN ) + gridN ) % gridN;
56 gy = ( ( gy % gridN ) + gridN ) % gridN;
57 return { gx, gy };
58 };
59
60 auto torDistSq =
61 []( double dx, double dy ) -> double
62 {
63 if( dx > 0.5 ) dx -= 1.0;
64 if( dx < -0.5 ) dx += 1.0;
65 if( dy > 0.5 ) dy -= 1.0;
66 if( dy < -0.5 ) dy += 1.0;
67 return dx * dx + dy * dy;
68 };
69
70 boost::random::mt19937 rng( aSeed );
71 boost::random::uniform_real_distribution<double> uniform( 0.0, 1.0 );
72
73 // Initial sample
74 VECTOR2D p( uniform( rng ), uniform( rng ) );
75 auto [gx, gy] = cellOf( p );
76 samples.push_back( p );
77 grid[(size_t) gy * gridN + gx] = 0;
78 active.push_back( 0 );
79
80 constexpr int K = 30; // candidate attempts per active sample (Bridson default)
81
82 while( !active.empty() )
83 {
84 boost::random::uniform_int_distribution<size_t> pickIdx( 0, active.size() - 1 );
85 size_t idx = pickIdx( rng );
86 VECTOR2D base = samples[active[idx]];
87 bool accepted = false;
88
89 for( int k = 0; k < K; ++k )
90 {
91 double theta = uniform( rng ) * 2.0 * M_PI;
92 double rho = aMinDist + uniform( rng ) * aMinDist; // annulus [r, 2r)
93 VECTOR2D cand( base.x + rho * std::cos( theta ),
94 base.y + rho * std::sin( theta ) );
95
96 // Wrap into the unit square.
97 cand.x -= std::floor( cand.x );
98 cand.y -= std::floor( cand.y );
99
100 auto [cx, cy] = cellOf( cand );
101 bool tooClose = false;
102
103 // Any sample within aMinDist is within +-2 background cells, modulo
104 // toroidal wraparound.
105 for( int dy = -2; dy <= 2 && !tooClose; ++dy )
106 {
107 int ny = ( ( cy + dy ) % gridN + gridN ) % gridN;
108
109 for( int dx = -2; dx <= 2 && !tooClose; ++dx )
110 {
111 int nx = ( ( cx + dx ) % gridN + gridN ) % gridN;
112 int idxN = grid[(size_t) ny * gridN + nx];
113
114 if( idxN < 0 )
115 continue;
116
117 if( torDistSq( samples[idxN].x - cand.x, samples[idxN].y - cand.y )
118 < aMinDist * aMinDist )
119 {
120 tooClose = true;
121 }
122 }
123 }
124
125 if( tooClose )
126 continue;
127
128 samples.push_back( cand );
129 grid[(size_t) cy * gridN + cx] = (int) samples.size() - 1;
130 active.push_back( (int) samples.size() - 1 );
131 accepted = true;
132 break;
133 }
134
135 if( !accepted )
136 {
137 active[idx] = active.back();
138 active.pop_back();
139 }
140 }
141
142 return samples;
143}
static thread_local boost::mt19937 rng
Definition kiid.cpp:49
std::vector< VECTOR2D > ToroidalUnitTile(double aMinDist, uint32_t aSeed)
Bridson's "Fast Poisson Disk Sampling in Arbitrary Dimensions" with toroidal boundary conditions on t...
static double torDistSq(const VECTOR2D &a, const VECTOR2D &b)
#define M_PI
VECTOR2< double > VECTOR2D
Definition vector2d.h:682