org.joml.sampling.PoissonSampling Maven / Gradle / Ivy
/*
* The MIT License
*
* Copyright (c) 2016-2019 JOML
*
* 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.
*/
package org.joml.sampling;
import java.util.ArrayList;
import org.joml.Random;
import org.joml.Vector2f;
/**
* Generates Poisson samples.
*
* The algorithm implemented here is based on Fast Poisson Disk Sampling in Arbitrary
* Dimensions.
*
* @author Kai Burjack
*/
public class PoissonSampling {
/**
* Generates Poisson samples on a disk.
*
* The algorithm implemented here is based on Fast Poisson Disk Sampling in Arbitrary
* Dimensions.
*
* @author Kai Burjack
*/
public static class Disk {
private final Vector2f[] grid;
private final float diskRadius;
private final float diskRadiusSquared;
private final float minDist;
private final float minDistSquared;
private final float cellSize;
private final int numCells;
private final Random rnd;
private final ArrayList processList;
/**
* Create a new instance of {@link Disk} which computes poisson-distributed samples on a disk with the given radius diskRadius
and notifies the given
* callback
for each found sample point.
*
* The samples are distributed evenly on the disk with a minimum distance to one another of at least minDist
.
*
* @param seed
* the seed to initialize the random number generator with
* @param diskRadius
* the disk radius
* @param minDist
* the minimum distance between any two generated samples
* @param k
* determines how many samples are tested before rejection. Higher values produce better results. Typical values are 20 to 30
* @param callback
* will be notified about each sample point
*/
public Disk(long seed, float diskRadius, float minDist, int k, Callback2d callback) {
this.diskRadius = diskRadius;
this.diskRadiusSquared = diskRadius * diskRadius;
this.minDist = minDist;
this.minDistSquared = minDist * minDist;
this.rnd = new Random(seed);
this.cellSize = minDist / (float) Math.sqrt(2.0);
this.numCells = (int) ((diskRadius * 2) / cellSize) + 1;
this.grid = new Vector2f[numCells * numCells];
this.processList = new ArrayList();
compute(k, callback);
}
private void compute(int k, Callback2d callback) {
float x, y;
do {
x = rnd.nextFloat() * 2.0f - 1.0f;
y = rnd.nextFloat() * 2.0f - 1.0f;
} while (x * x + y * y > 1.0f);
Vector2f initial = new Vector2f(x, y);
processList.add(initial);
callback.onNewSample(initial.x, initial.y);
insert(initial);
while (!processList.isEmpty()) {
int i = rnd.nextInt(processList.size());
Vector2f sample = (Vector2f) processList.get(i);
boolean found = false;
search: for (int s = 0; s < k; s++) {
float angle = rnd.nextFloat() * (float) Math.PI2;
float radius = minDist * (rnd.nextFloat() + 1.0f);
x = (float) (radius * Math.sin_roquen_9(angle + Math.PIHalf));
y = (float) (radius * Math.sin_roquen_9(angle));
x += sample.x;
y += sample.y;
if (x * x + y * y > diskRadiusSquared)
continue search;
if (!searchNeighbors(x, y)) {
found = true;
callback.onNewSample(x, y);
Vector2f f = new Vector2f(x, y);
processList.add(f);
insert(f);
break;
}
}
if (!found) {
processList.remove(i);
}
}
}
private boolean searchNeighbors(float px, float py) {
int row = (int) ((py + diskRadius) / cellSize);
int col = (int) ((px + diskRadius) / cellSize);
if (grid[row * numCells + col] != null)
return true;
int minX = Math.max(0, col - 1);
int minY = Math.max(0, row - 1);
int maxX = Math.min(col + 1, numCells - 1);
int maxY = Math.min(row + 1, numCells - 1);
for (int y = minY; y <= maxY; y++) {
for (int x = minX; x <= maxX; x++) {
Vector2f v = grid[y * numCells + x];
if (v != null) {
float dx = v.x - px;
float dy = v.y - py;
if (dx * dx + dy * dy < minDistSquared) {
return true;
}
}
}
}
return false;
}
private void insert(Vector2f p) {
int row = (int) ((p.y + diskRadius) / cellSize);
int col = (int) ((p.x + diskRadius) / cellSize);
grid[row * numCells + col] = p;
}
}
}