-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Poisson Disk Sampling to orx-noise
- Loading branch information
1 parent
e29c670
commit 45c9ca1
Showing
2 changed files
with
158 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import org.openrndr.application | ||
import org.openrndr.color.ColorRGBa | ||
import org.openrndr.extensions.SingleScreenshot | ||
import org.openrndr.extra.noise.poissonDiskSampling | ||
import org.openrndr.math.Vector2 | ||
import org.openrndr.shape.Circle | ||
|
||
fun main() { | ||
application { | ||
program { | ||
if (System.getProperty("takeScreenshot") == "true") { | ||
extend(SingleScreenshot()) { | ||
this.outputFile = System.getProperty("screenshotPath") | ||
} | ||
} | ||
|
||
var points = poissonDiskSampling(200.0, 200.0, 5.0, 10) | ||
|
||
val rectPoints = points.map { Circle(Vector2(100.0, 100.0) + it, 3.0) } | ||
|
||
points = poissonDiskSampling(200.0, 200.0, 5.0, 10, true) { w: Double, h: Double, v: Vector2 -> | ||
Circle(Vector2(w, h) / 2.0, 100.0).contains(v) | ||
} | ||
|
||
val circlePoints = points.map { Circle(Vector2(350.0, 100.0) + it, 3.0) } | ||
|
||
extend { | ||
drawer.background(ColorRGBa.BLACK) | ||
|
||
drawer.stroke = null | ||
drawer.fill = ColorRGBa.PINK | ||
drawer.circles(rectPoints) | ||
drawer.circles(circlePoints) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package org.openrndr.extra.noise | ||
|
||
import org.openrndr.math.Polar | ||
import org.openrndr.math.Vector2 | ||
import org.openrndr.math.clamp | ||
import org.openrndr.shape.Rectangle | ||
import kotlin.math.ceil | ||
import kotlin.math.sqrt | ||
|
||
/* | ||
* TODO v2 | ||
* * Generalize to 3 dimensions | ||
*/ | ||
|
||
internal const val epsilon = 0.0000001 | ||
|
||
/** | ||
* Creates a random point distribution on a given area | ||
* Each point gets n [tries] at generating the next point | ||
* By default the points are generated along the circumference of r + epsilon to the point | ||
* They can also be generated on a ring like in the original algorithm from Robert Bridson | ||
* | ||
* @param width the width of the area | ||
* @param height the height of the area | ||
* @param r the minimum distance between each point | ||
* @param tries number of candidates per point | ||
* @param boundsMapper a custom function to check if a point is within bounds | ||
* @param randomOnRing generate random points on a ring with an annulus from r to 2r | ||
* @return a list of points | ||
*/ | ||
fun poissonDiskSampling( | ||
width: Double, | ||
height: Double, | ||
r: Double, | ||
tries: Int = 30, | ||
randomOnRing: Boolean = false, | ||
boundsMapper: ((w: Double, h: Double, v: Vector2) -> Boolean)? = null | ||
): List<Vector2> { | ||
val disk = mutableListOf<Vector2>() | ||
val queue = mutableListOf<Int>() | ||
|
||
val r2 = r * r | ||
val radius = r + epsilon | ||
|
||
val cellSize = r / sqrt(2.0) | ||
val rows = ceil(height / cellSize).toInt() | ||
val cols = ceil(width / cellSize).toInt() | ||
|
||
val grid = List(rows * cols) { -1 }.toMutableList() | ||
|
||
fun addPoint(v: Vector2) { | ||
val x = (v.x / cellSize).fastFloor() | ||
val y = (v.y / cellSize).fastFloor() | ||
val index = x + y * cols | ||
|
||
disk.add(v) | ||
|
||
grid[index] = disk.lastIndex | ||
|
||
queue.add(disk.lastIndex) | ||
} | ||
|
||
addPoint(Vector2(width / 2.0, height / 2.0)) | ||
|
||
val boundsRect = Rectangle(0.0, 0.0, width, height) | ||
|
||
while (queue.isNotEmpty()) { | ||
val activeIndex = Random.pick(queue) | ||
val active = disk[activeIndex] | ||
|
||
var candidateAccepted = false | ||
|
||
candidateSearch@ for (l in 0 until tries) { | ||
val c = if (randomOnRing) { | ||
active + Random.ring2d(r, 2 * r) as Vector2 | ||
} else { | ||
active + Polar(Random.double0(360.0), radius).cartesian | ||
} | ||
|
||
if (!boundsRect.contains(c)) continue@candidateSearch | ||
|
||
// check if it's within bounds | ||
// choose another candidate if it's not | ||
if (boundsMapper != null && !boundsMapper(width, height, c)) continue@candidateSearch | ||
|
||
val x = (c.x / cellSize).fastFloor() | ||
val y = (c.y / cellSize).fastFloor() | ||
|
||
// Check closest neighbours in a 5x5 grid | ||
for (ix in (-2..2)) { | ||
for (iy in (-2..2)) { | ||
val nx = clamp(x + ix, 0, cols - 1) | ||
val ny = clamp(y + iy, 0, rows - 1) | ||
|
||
val neighborIdx = grid[nx + ny * cols] | ||
|
||
// -1 means the grid has no sample at that point | ||
if (neighborIdx == -1) continue | ||
|
||
val neighbor = disk[neighborIdx] | ||
|
||
// if the candidate is within one of the neighbours radius, try another candidate | ||
if ((neighbor - c).squaredLength <= r2) continue@candidateSearch | ||
} | ||
} | ||
|
||
addPoint(c) | ||
|
||
candidateAccepted = true | ||
|
||
break | ||
} | ||
|
||
// If no candidate was accepted, remove the sample from the active list | ||
if (!candidateAccepted) { | ||
queue.remove(activeIndex) | ||
} | ||
} | ||
|
||
return disk | ||
} |