[orx-noise] Add ShapeProvider.poissonDiskSampling

This commit is contained in:
Edwin Jakobs
2021-10-17 17:26:39 +02:00
parent 8808e62431
commit 737f1bcf85
4 changed files with 80 additions and 7 deletions

View File

@@ -6,6 +6,7 @@ import org.openrndr.math.clamp
import org.openrndr.shape.Rectangle
import kotlin.math.ceil
import kotlin.math.sqrt
import kotlin.random.Random
/*
* TODO v2
@@ -24,8 +25,10 @@ internal const val epsilon = 0.0000001
* @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
* @param random a random number generator, default value is [Random.Default]
* @param boundsMapper a custom function to check if a point is within bounds
* @return a list of points
*/
fun poissonDiskSampling(
@@ -34,7 +37,9 @@ fun poissonDiskSampling(
r: Double,
tries: Int = 30,
randomOnRing: Boolean = false,
boundsMapper: ((w: Double, h: Double, v: Vector2) -> Boolean)? = null
random: Random = Random.Default,
initialPoint: Vector2 = Vector2(width/2.0, height/2.0),
boundsMapper: ((w: Double, h: Double, v: Vector2) -> Boolean)? = null,
): List<Vector2> {
val disk = mutableListOf<Vector2>()
val queue = mutableListOf<Int>()
@@ -46,7 +51,7 @@ fun poissonDiskSampling(
val rows = ceil(height / cellSize).toInt()
val cols = ceil(width / cellSize).toInt()
val grid = List(rows * cols) { -1 }.toMutableList()
val grid = MutableList(rows * cols) { -1 }
fun addPoint(v: Vector2) {
val x = (v.x / cellSize).fastFloor()
@@ -60,21 +65,21 @@ fun poissonDiskSampling(
queue.add(disk.lastIndex)
}
addPoint(Vector2(width / 2.0, height / 2.0))
addPoint(initialPoint)
val boundsRect = Rectangle(0.0, 0.0, width, height)
while (queue.isNotEmpty()) {
val activeIndex = Random.pick(queue)
val activeIndex = queue.random(random)
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
active + Vector2.uniformRing(r, 2 * r, random)
} else {
active + Polar(Random.double0(360.0), radius).cartesian
active + Polar(random.nextDouble(0.0, 360.0), radius).cartesian
}
if (!boundsRect.contains(c)) continue@candidateSearch

View File

@@ -0,0 +1,31 @@
package org.openrndr.extra.noise
import org.openrndr.math.Vector2
import org.openrndr.shape.*
import kotlin.random.Random
fun ShapeProvider.uniform(random: Random = Random.Default): Vector2 {
val shape = shape
return Vector2.uniformSequence(shape.bounds, random).first {
shape.contains(it)
}
}
fun ShapeProvider.poissonDiskSampling(
r: Double,
tries: Int = 30,
random: Random = Random.Default
): List<Vector2> {
val shape = shape
val bounds = shape.bounds
val poissonBounds = Rectangle(0.0, 0.0, bounds.width, bounds.height)
val initialPoint = this.uniform(random).map(bounds, poissonBounds)
return poissonDiskSampling(bounds.width, bounds.height, r, tries, false, random, initialPoint) { _, _, point ->
val contourPoint = point.map(poissonBounds, bounds)
shape.contains(contourPoint)
}.map {
it.map(poissonBounds, bounds)
}
}

View File

@@ -70,6 +70,14 @@ fun Vector2.Companion.uniforms(count: Int,
random: Random = Random.Default): List<Vector2> =
List(count) { Vector2.uniform(rect, random) }
fun Vector2.Companion.uniformSequence(rect: Rectangle,
random: Random = Random.Default): Sequence<Vector2> =
sequence {
while(true) {
yield(uniform(rect, random))
}
}
fun Vector2.Companion.uniformsRing(count: Int,
innerRadius: Double = 0.0, outerRadius: Double = 1.0,
random: Random = Random.Default): List<Vector2> =

View File

@@ -0,0 +1,29 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.poissonDiskSampling
import org.openrndr.math.Vector2
import org.openrndr.math.mod_
import org.openrndr.shape.Circle
import org.openrndr.shape.Ellipse
import kotlin.math.cos
fun main() {
application {
program {
extend {
val shape = Ellipse(Vector2(width/2.0, height/2.0), 200.0, 150.0 + cos(seconds)*125.0).shape
val points = shape.poissonDiskSampling(10.0)
drawer.clear(ColorRGBa.BLACK)
drawer.stroke = null
drawer.fill = ColorRGBa.PINK
drawer.circles(points, 4.0)
if (seconds.mod_(2.0) < 1.0) {
drawer.stroke = ColorRGBa.PINK
drawer.fill = null
drawer.shape(shape)
}
}
}
}
}