[orx-hash-grid] Add documentation, demos and improve API
This commit is contained in:
@@ -1,3 +1,29 @@
|
||||
# orx-hash-grid
|
||||
|
||||
A 2D space partitioning for points.
|
||||
|
||||
## Usage
|
||||
|
||||
Create a hash grid for a given radius.
|
||||
```kotlin
|
||||
val grid = HashGrid(radius)
|
||||
```
|
||||
|
||||
Check for a given query point if the grid is free, i.e. there is no point in the grid at distance less than `radius` away from the
|
||||
query point.
|
||||
|
||||
```kotlin
|
||||
grid.isFree(query)
|
||||
```
|
||||
|
||||
Add a point to the hash grid structure:
|
||||
```kotlin
|
||||
grid.insert(point)
|
||||
```
|
||||
|
||||
Iterate over all points in the hash grid:
|
||||
```kotlin
|
||||
for (point in grid.points()) {
|
||||
// do something with point
|
||||
}
|
||||
```
|
||||
@@ -15,5 +15,15 @@ kotlin {
|
||||
implementation(libs.kotlin.reflect)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val jvmDemo by getting {
|
||||
dependencies {
|
||||
implementation(project(":orx-color"))
|
||||
implementation(project(":orx-fx"))
|
||||
implementation(project(":orx-noise"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.openrndr.extra.hashgrid
|
||||
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.shape.Rectangle
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
@@ -15,14 +16,33 @@ private data class GridCoords(val x: Int, val y: Int) {
|
||||
fun offset(i: Int, j: Int): GridCoords = copy(x = x + i, y = y + j)
|
||||
}
|
||||
|
||||
private class Cell(
|
||||
var xMin: Double = Double.POSITIVE_INFINITY,
|
||||
var xMax: Double = Double.NEGATIVE_INFINITY,
|
||||
var yMin: Double = Double.POSITIVE_INFINITY,
|
||||
var yMax: Double = Double.NEGATIVE_INFINITY,
|
||||
) {
|
||||
val points = mutableListOf<Pair<Vector2, Any?>>()
|
||||
fun insert(point: Vector2, owner: Any?) {
|
||||
class Cell(val x: Int, val y: Int, val cellSize: Double) {
|
||||
var xMin: Double = Double.POSITIVE_INFINITY
|
||||
private set
|
||||
var xMax: Double = Double.NEGATIVE_INFINITY
|
||||
private set
|
||||
var yMin: Double = Double.POSITIVE_INFINITY
|
||||
private set
|
||||
var yMax: Double = Double.NEGATIVE_INFINITY
|
||||
private set
|
||||
|
||||
val bounds: Rectangle
|
||||
get() {
|
||||
return Rectangle(x * cellSize, y * cellSize, cellSize, cellSize)
|
||||
}
|
||||
|
||||
val contentBounds: Rectangle
|
||||
get() {
|
||||
if (points.isEmpty()) {
|
||||
return Rectangle.EMPTY
|
||||
} else {
|
||||
return Rectangle(xMin, yMin, xMax - xMin, yMax - yMin)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal val points = mutableListOf<Pair<Vector2, Any?>>()
|
||||
internal fun insert(point: Vector2, owner: Any?) {
|
||||
points.add(Pair(point, owner))
|
||||
xMin = min(xMin, point.x)
|
||||
xMax = max(xMax, point.x)
|
||||
@@ -30,7 +50,7 @@ private class Cell(
|
||||
yMax = max(yMax, point.y)
|
||||
}
|
||||
|
||||
fun squaredDistanceTo(query: Vector2): Double {
|
||||
internal fun squaredDistanceTo(query: Vector2): Double {
|
||||
val width = xMax - xMin
|
||||
val height = yMax - yMin
|
||||
val x = (xMin + xMax) / 2.0
|
||||
@@ -39,10 +59,25 @@ private class Cell(
|
||||
val dy = max(abs(query.y - y) - height / 2, 0.0)
|
||||
return dx * dx + dy * dy
|
||||
}
|
||||
|
||||
fun points() = sequence {
|
||||
for (point in points) {
|
||||
yield(point)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HashGrid(val radius: Double) {
|
||||
private val cells = mutableMapOf<GridCoords, Cell>()
|
||||
fun cells() = sequence {
|
||||
for (cell in cells.values) {
|
||||
yield(cell)
|
||||
}
|
||||
}
|
||||
|
||||
var size: Int = 0
|
||||
private set
|
||||
|
||||
val cellSize = radius / sqrt(2.0)
|
||||
private inline fun coords(v: Vector2): GridCoords {
|
||||
val x = (v.x / cellSize).fastFloor()
|
||||
@@ -58,17 +93,20 @@ class HashGrid(val radius: Double) {
|
||||
}
|
||||
}
|
||||
|
||||
fun random(random: Random = Random.Default) : Vector2 {
|
||||
fun random(random: Random = Random.Default): Vector2 {
|
||||
return cells.values.random(random).points.random().first
|
||||
}
|
||||
|
||||
fun insert(point: Vector2, owner:Any? = null) {
|
||||
fun insert(point: Vector2, owner: Any? = null) {
|
||||
val gc = coords(point)
|
||||
val cell = cells.getOrPut(gc) { Cell() }
|
||||
val cell = cells.getOrPut(gc) { Cell(gc.x, gc.y, cellSize) }
|
||||
cell.insert(point, owner)
|
||||
size += 1
|
||||
}
|
||||
|
||||
fun isFree(query: Vector2, ignoreOwners:Set<Any> = emptySet()): Boolean {
|
||||
fun cell(query: Vector2): Cell? = cells[coords(query)]
|
||||
|
||||
fun isFree(query: Vector2, ignoreOwners: Set<Any> = emptySet()): Boolean {
|
||||
val c = coords(query)
|
||||
if (cells[c] == null) {
|
||||
for (j in -2..2) {
|
||||
@@ -96,3 +134,33 @@ class HashGrid(val radius: Double) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a hash grid containing all points in the list
|
||||
* @param radius radius of the hash grid
|
||||
*/
|
||||
fun List<Vector2>.hashGrid(radius: Double): HashGrid {
|
||||
val grid = HashGrid(radius)
|
||||
for (point in this) {
|
||||
grid.insert(point)
|
||||
}
|
||||
return grid
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list that only contains points at a minimum distance.
|
||||
* @param radius the minimum distance between any two points in the returned list
|
||||
*/
|
||||
fun List<Vector2>.filter(radius: Double): List<Vector2> {
|
||||
return if (size <= 1) {
|
||||
this
|
||||
} else {
|
||||
val grid = HashGrid(radius)
|
||||
for (point in this) {
|
||||
if (grid.isFree(point)) {
|
||||
grid.insert(point)
|
||||
}
|
||||
}
|
||||
grid.points().map { it.first }.toList()
|
||||
}
|
||||
}
|
||||
23
orx-hash-grid/src/jvmDemo/kotlin/DemoFilter01.kt
Normal file
23
orx-hash-grid/src/jvmDemo/kotlin/DemoFilter01.kt
Normal file
@@ -0,0 +1,23 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.extra.hashgrid.filter
|
||||
import org.openrndr.extra.noise.uniform
|
||||
import kotlin.random.Random
|
||||
|
||||
fun main() {
|
||||
application {
|
||||
configure {
|
||||
width = 720
|
||||
height = 720
|
||||
}
|
||||
program {
|
||||
val r = Random(0)
|
||||
val points = (0 until 10000).map {
|
||||
drawer.bounds.uniform(random = r)
|
||||
}
|
||||
val filteredPoints = points.filter(20.0)
|
||||
extend {
|
||||
drawer.circles(filteredPoints, 4.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
orx-hash-grid/src/jvmDemo/kotlin/DemoHashGrid01.kt
Normal file
28
orx-hash-grid/src/jvmDemo/kotlin/DemoHashGrid01.kt
Normal file
@@ -0,0 +1,28 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.extra.hashgrid.HashGrid
|
||||
import org.openrndr.extra.noise.uniform
|
||||
import kotlin.random.Random
|
||||
|
||||
fun main() {
|
||||
application {
|
||||
configure {
|
||||
width = 720
|
||||
height = 720
|
||||
}
|
||||
program {
|
||||
val r = Random(0)
|
||||
val hashGrid = HashGrid(20.0)
|
||||
extend {
|
||||
val p = drawer.bounds.uniform(random = r)
|
||||
if (hashGrid.isFree(p)) {
|
||||
hashGrid.insert(p)
|
||||
}
|
||||
drawer.circles(hashGrid.points().map { it.first }.toList(), 4.0)
|
||||
drawer.fill = null
|
||||
drawer.stroke = ColorRGBa.WHITE
|
||||
drawer.rectangles(hashGrid.cells().map { it.bounds }.toList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user