[orx-noise] Add hash functions

This commit is contained in:
Edwin Jakobs
2024-10-20 14:14:50 +02:00
parent 6e1e161726
commit fba1e5b61a
23 changed files with 473 additions and 63 deletions

View File

@@ -1,35 +1,25 @@
package org.openrndr.extra.noise
import org.openrndr.extra.hashgrid.HashGrid
import org.openrndr.extra.noise.shapes.hash
import org.openrndr.extra.noise.shapes.uniform
import org.openrndr.math.Vector2
import org.openrndr.shape.*
import kotlin.random.Random
/**
* Returns a random [Vector2] point located inside a [ShapeProvider] while
* maintaining a distance to the edge of the shape of [distanceToEdge] units.
* Generates specified amount of random points that lie inside the [Shape].
*
* @param pointCount The number of points to generate.
* @param random The [Random] number generator to use, defaults to [Random.Default].
*/
fun ShapeProvider.uniform(distanceToEdge: Double = 0.0, random: Random = Random.Default): Vector2 {
val shape = shape
require(!shape.empty)
var attempts = 0
val innerBounds = shape.bounds.offsetEdges(-distanceToEdge.coerceAtLeast(0.0))
return Vector2.uniformSequence(innerBounds, random).first {
attempts++
require(attempts < 100)
if (distanceToEdge == 0.0) {
shape.contains(it)
} else {
shape.contains(it) && shape.contours.minOf { c -> c.nearest(it).position.distanceTo(it) } > distanceToEdge
}
}
fun ShapeProvider.uniform(pointCount: Int, random: Random = Random.Default): List<Vector2> {
return shape.triangulation.uniform(pointCount, random)
}
/**
* Generate [sampleCount] uniformly distributed points inside the area of [ShapeProvider]
*/
fun ShapeProvider.uniform(sampleCount: Int, random: Random = Random.Default) : List<Vector2> = shape.triangulation.uniform(sampleCount, random)
fun ShapeProvider.hash(pointCount: Int, seed: Int, x: Int): List<Vector2> {
return shape.triangulation.hash(pointCount, seed, x)
}
/**
* Returns a list of pairs in which the first component is a radius and the

View File

@@ -1,26 +0,0 @@
package org.openrndr.extra.noise
import org.openrndr.math.Vector2
import org.openrndr.shape.Triangle
import kotlin.random.Random
/**
* Generate [count] uniform samples from a list of [Triangle]s
*/
fun List<Triangle>.uniform(count: Int, random: Random = Random.Default): List<Vector2> {
val totalArea = this.sumOf { it.area }
val randoms = (0 until count).map {
Double.uniform(0.0, totalArea, random = random)
}.sorted()
val result = mutableListOf<Vector2>()
var idx = 0
var sum = 0.0
for (t in this) {
sum += t.area
while (idx <= randoms.lastIndex && sum > randoms[idx]) {
result.add(t.randomPoint(random))
idx++
}
}
return result
}

View File

@@ -23,6 +23,12 @@ fun Double.Companion.uniform(
) =
(random.nextDouble() * (max - min)) + min
fun Double.Companion.hash(
seed: Int, x: Int,
min: Double = -1.0, max: Double = 1.0
) = fhash1D(seed, x) * (max - min) + min
fun Vector2.Companion.uniform(
min: Vector2 = -ONE, max: Vector2 = ONE,
random: Random = Random.Default
@@ -32,12 +38,28 @@ fun Vector2.Companion.uniform(
Double.uniform(min.y, max.y, random)
)
fun Vector2.Companion.hash(
seed: Int, x: Int,
min: Vector2 = -ONE, max: Vector2 = ONE
) =
Vector2(
Double.hash(seed, x, min.x, max.x),
Double.hash(seed xor 0x7f7f7f7f, x, min.y, max.y)
)
fun Vector2.Companion.uniform(
min: Double = -1.0, max: Double = 1.0,
random: Random = Random.Default
) =
Vector2.uniform(Vector2(min, min), Vector2(max, max), random)
fun Vector2.Companion.hash(
seed: Int, x: Int,
min: Double = -1.0, max: Double = 1.0,
) =
Vector2.hash(seed, x, Vector2(min, min), Vector2(max, max))
fun Vector2.Companion.uniform(
rect: Rectangle,
random: Random = Random.Default
@@ -78,7 +100,7 @@ fun Vector2.Companion.uniformRing(
val eps = 1E-6
if ( abs(innerRadius - outerRadius) < eps) {
if (abs(innerRadius - outerRadius) < eps) {
val angle = Double.uniform(-180.0, 180.0, random)
return Polar(angle, innerRadius).cartesian

View File

@@ -0,0 +1,28 @@
package org.openrndr.extra.noise.shapes
import org.openrndr.extra.noise.uhash11
import org.openrndr.math.Vector3
import org.openrndr.shape.Box
import kotlin.random.Random
fun Box.uniform(random: Random = Random.Default): Vector3 {
val x = random.nextDouble() * width + corner.x
val y = random.nextDouble() * height + corner.y
val z = random.nextDouble() * depth + corner.z
return Vector3(x, y ,z)
}
fun Box.hash(seed: Int, x: Int): Vector3 {
val ux = uhash11(seed.toUInt() + uhash11(x.toUInt()))
val uy = uhash11(ux + x.toUInt())
val uz = uhash11(uy + x.toUInt())
val fx = ux.toDouble() / UInt.MAX_VALUE.toDouble()
val fy = uy.toDouble() / UInt.MAX_VALUE.toDouble()
val fz = uz.toDouble() / UInt.MAX_VALUE.toDouble()
val x = fx * width + corner.x
val y = fy * height + corner.y
val z = fz * depth + corner.z
return Vector3(x, y, z)
}

View File

@@ -0,0 +1,27 @@
package org.openrndr.extra.noise.shapes
import org.openrndr.extra.noise.fhash1D
import org.openrndr.extra.noise.hash
import org.openrndr.math.Polar
import org.openrndr.math.Vector2
import org.openrndr.shape.Circle
import kotlin.math.sqrt
import kotlin.random.Random
/**
* Generate a uniformly distributed random point inside [Circle]
*/
fun Circle.hash(seed: Int, x: Int): Vector2 {
val r = radius * sqrt(fhash1D(seed, x))
val phi = 360.0 * fhash1D(seed xor 0x7f7f_7f7f, x)
return Polar(phi, r).cartesian + center
}
/**
* Generate a uniformly distributed random point inside [Circle]
*/
fun Circle.uniform(random: Random = Random.Default): Vector2 {
val r = radius * sqrt(random.nextDouble())
val phi = 360.0 * random.nextDouble()
return Polar(phi, r).cartesian + center
}

View File

@@ -0,0 +1,24 @@
package org.openrndr.extra.noise.shapes
import org.openrndr.extra.noise.uhash11
import org.openrndr.math.Vector2
import org.openrndr.shape.Rectangle
import kotlin.random.Random
fun Rectangle.uniform(random: Random = Random.Default): Vector2 {
val x = random.nextDouble() * width + corner.x
val y = random.nextDouble() * height + corner.y
return Vector2(x, y)
}
fun Rectangle.hash(seed: Int, x: Int): Vector2 {
val ux = uhash11(seed.toUInt() + uhash11(x.toUInt()))
val uy = uhash11(ux + x.toUInt())
val fx = ux.toDouble() / UInt.MAX_VALUE.toDouble()
val fy = uy.toDouble() / UInt.MAX_VALUE.toDouble()
val x = fx * width + corner.x
val y = fy * height + corner.y
return Vector2(x, y)
}

View File

@@ -0,0 +1,58 @@
package org.openrndr.extra.noise.shapes
import org.openrndr.extra.noise.fhash1D
import org.openrndr.extra.noise.uniform
import org.openrndr.math.Vector2
import org.openrndr.shape.Triangle
import kotlin.random.Random
/**
* Generate [count] uniform samples from a list of [Triangle]s
*/
fun List<Triangle>.uniform(count: Int, random: Random = Random.Default): List<Vector2> {
val totalArea = this.sumOf { it.area }
val randoms = (0 until count).map {
Double.uniform(0.0, totalArea, random = random)
}.sorted()
val result = mutableListOf<Vector2>()
var idx = 0
var sum = 0.0
for (t in this) {
sum += t.area
while (idx <= randoms.lastIndex && sum > randoms[idx]) {
result.add(t.uniform(random))
idx++
}
}
return result
}
fun List<Triangle>.hash(count: Int, seed: Int = 0, x: Int = 0): List<Vector2> {
val totalArea = this.sumOf { it.area }
val randoms = (0 until count).map {
Pair(x + it, fhash1D(seed, x + it) * totalArea)
}.sortedBy { it.second }
val result = mutableListOf<Vector2>()
var idx = 0
var sum = 0.0
for (t in this) {
sum += t.area
while (idx <= randoms.lastIndex && sum > randoms[idx].second) {
result.add(t.hash(seed, randoms[idx].first))
idx++
}
}
return result
}
/** Generates a random point that lies inside the [Triangle]. */
fun Triangle.uniform(random: Random = Random.Default): Vector2 {
return position(random.nextDouble(), random.nextDouble())
}
fun Triangle.hash(seed: Int, x: Int): Vector2 {
val u = fhash1D(seed, x)
val v = fhash1D(seed, u.toRawBits().toInt() + x)
return position(u, v)
}

View File

@@ -0,0 +1,33 @@
import org.openrndr.application
import org.openrndr.extra.noise.shapes.hash
import org.openrndr.extra.noise.shapes.uniform
import org.openrndr.shape.Circle
import kotlin.random.Random
fun main() {
application {
configure {
width = 720
height = 720
}
program {
extend {
val b = drawer.bounds
val b0 = b.sub(0.0, 0.0, 0.5, 1.0).offsetEdges(-10.0)
val b1 = b.sub(0.5, 0.0, 1.0, 1.0).offsetEdges(-10.0)
val c0 = Circle(b0.center, b0.width/2.0)
val c1 = Circle(b1.center, b1.width/2.0)
val r = Random(0)
for (i in 0 until 2000) {
drawer.circle(c0.uniform(r), 2.0)
drawer.circle(c1.hash(909, i),2.0)
}
}
}
}
}

View File

@@ -0,0 +1,29 @@
import org.openrndr.application
import org.openrndr.extra.noise.shapes.hash
import org.openrndr.extra.noise.shapes.uniform
import kotlin.random.Random
fun main() {
application {
configure {
width = 720
height = 720
}
program {
extend {
val b = drawer.bounds
val b0 = b.sub(0.0, 0.0, 0.5, 1.0).offsetEdges(-10.0)
val b1 = b.sub(0.5, 0.0, 1.0, 1.0).offsetEdges(-10.0)
val r = Random(0)
for (i in 0 until 20000) {
drawer.circle(b0.uniform(r), 2.0)
drawer.circle(b1.hash(909, i),2.0)
}
}
}
}
}

View File

@@ -1,8 +1,7 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.noise.shapes.hash
import org.openrndr.shape.Triangle
import kotlin.random.Random
/**
* Demonstrate the generation of uniformly distributed points inside a list of triangles
@@ -17,8 +16,10 @@ fun main() {
program {
val r = drawer.bounds.offsetEdges(-100.0)
val triangle = Triangle(r.position(0.5, 0.0), r.position(0.0, 1.0), r.position(1.0, 1.0))
val pts = listOf(triangle).uniform(1000, Random(0))
//val pts = listOf(triangle).uniform(1000, Random(0))
extend {
val pts = listOf(triangle).hash(1000, 0, (seconds*500.0).toInt())
drawer.clear(ColorRGBa.PINK)
drawer.stroke = null
drawer.contour(triangle.contour)