[orx-noise] Add ShapeProvider.poissonDiskSampling
This commit is contained in:
@@ -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
|
||||
|
||||
31
orx-noise/src/commonMain/kotlin/ShapeNoise.kt
Normal file
31
orx-noise/src/commonMain/kotlin/ShapeNoise.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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> =
|
||||
|
||||
29
orx-noise/src/demo/kotlin/DemoPoissonDiskSampling02.kt
Normal file
29
orx-noise/src/demo/kotlin/DemoPoissonDiskSampling02.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user