[orx-hash-grid] Add generated and verified documentation, improve DemoHashGrid01.kt
This commit is contained in:
@@ -18,6 +18,13 @@ private data class GridCoords(val x: Int, val y: Int) {
|
||||
fun offset(i: Int, j: Int): GridCoords = copy(x = x + i, y = y + j)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a cell in a 2D space, defined by its position and size.
|
||||
*
|
||||
* @property x The x-coordinate of the cell in the grid.
|
||||
* @property y The y-coordinate of the cell in the grid.
|
||||
* @property cellSize The size of the cell along each axis.
|
||||
*/
|
||||
class Cell(val x: Int, val y: Int, val cellSize: Double) {
|
||||
var xMin: Double = Double.POSITIVE_INFINITY
|
||||
private set
|
||||
@@ -28,11 +35,22 @@ class Cell(val x: Int, val y: Int, val cellSize: Double) {
|
||||
var yMax: Double = Double.NEGATIVE_INFINITY
|
||||
private set
|
||||
|
||||
/**
|
||||
* Calculates and returns the rectangular bounds of the cell in the 2D grid.
|
||||
* The bounds are represented as a rectangle with its top-left position and size derived
|
||||
* from the cell's position (`x`, `y`) and `cellSize`.
|
||||
*/
|
||||
val bounds: Rectangle
|
||||
get() {
|
||||
return Rectangle(x * cellSize, y * cellSize, cellSize, cellSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the bounds of the content within the cell, considering the points stored in it.
|
||||
* If no points are present in the cell, the bounds will be represented as an empty rectangle.
|
||||
* Otherwise, the bounds are determined by the minimum and maximum x and y coordinates
|
||||
* among the points in the cell.
|
||||
*/
|
||||
val contentBounds: Rectangle
|
||||
get() {
|
||||
if (points.isEmpty()) {
|
||||
@@ -62,6 +80,12 @@ class Cell(val x: Int, val y: Int, val cellSize: Double) {
|
||||
return dx * dx + dy * dy
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a sequence of points contained within the current cell.
|
||||
* Iterates over the points stored in the cell and yields each point one by one.
|
||||
*
|
||||
* @return A sequence of points in the cell.
|
||||
*/
|
||||
fun points() = sequence {
|
||||
for (point in points) {
|
||||
yield(point)
|
||||
@@ -69,17 +93,39 @@ class Cell(val x: Int, val y: Int, val cellSize: Double) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a 2D spatial hash grid used for efficiently managing and querying points in a sparse space.
|
||||
*
|
||||
* @property radius The maximum distance between points for them to be considered neighbors.
|
||||
*/
|
||||
class HashGrid(val radius: Double) {
|
||||
private val cells = mutableMapOf<GridCoords, Cell>()
|
||||
|
||||
/**
|
||||
* Returns a sequence of all cells stored in the grid.
|
||||
* Iterates through the values in the internal `cells` map and yields each cell.
|
||||
*/
|
||||
fun cells() = sequence {
|
||||
for (cell in cells.values) {
|
||||
yield(cell)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the total number of elements (points or data) that are currently stored in the grid.
|
||||
*
|
||||
* This property is managed internally and reflects the current size of the grid data structure.
|
||||
* It cannot be modified directly from outside the class.
|
||||
*/
|
||||
var size: Int = 0
|
||||
private set
|
||||
|
||||
/**
|
||||
* Represents the size of a single cell in the hash grid.
|
||||
*
|
||||
* Computed as the radius divided by the square root of 2.
|
||||
* This value determines the spatial resolution of each cell in the grid.
|
||||
*/
|
||||
val cellSize = radius / sqrt(2.0)
|
||||
private fun coords(v: Vector2): GridCoords {
|
||||
val x = (v.x / cellSize).fastFloor()
|
||||
@@ -87,6 +133,14 @@ class HashGrid(val radius: Double) {
|
||||
return GridCoords(x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a sequence of all points stored within the grid.
|
||||
*
|
||||
* Iterates through each cell in the grid's `cells` map, yielding all points
|
||||
* contained within each cell.
|
||||
*
|
||||
* @return A sequence of points from all cells in the grid.
|
||||
*/
|
||||
fun points() = sequence {
|
||||
for (cell in cells.values) {
|
||||
for (point in cell.points) {
|
||||
@@ -95,10 +149,24 @@ class HashGrid(val radius: Double) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a random point from the grid using the provided random number generator.
|
||||
*
|
||||
* @param random The random number generator to use. Defaults to `Random.Default`.
|
||||
* @return A randomly selected point, represented as a `Vector2`, from the grid's cells.
|
||||
*/
|
||||
fun random(random: Random = Random.Default): Vector2 {
|
||||
return cells.values.random(random).points.random().first
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a point into the grid, associating it with an owner if provided.
|
||||
* The method calculates the grid cell corresponding to the provided point and inserts
|
||||
* the point into that cell. If the cell does not exist, it is created.
|
||||
*
|
||||
* @param point The point to insert, represented as a `Vector2` object.
|
||||
* @param owner An optional object to associate with the point. Defaults to `null` if no owner is specified.
|
||||
*/
|
||||
fun insert(point: Vector2, owner: Any? = null) {
|
||||
val gc = coords(point)
|
||||
val cell = cells.getOrPut(gc) { Cell(gc.x, gc.y, cellSize) }
|
||||
@@ -106,8 +174,26 @@ class HashGrid(val radius: Double) {
|
||||
size += 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cell corresponding to the given query point in the grid.
|
||||
* The method calculates the grid coordinates for the query point and returns
|
||||
* the cell found at those coordinates, if it exists.
|
||||
*
|
||||
* @param query The point in 2D space, represented as a `Vector2`, for which
|
||||
* to retrieve the corresponding cell.
|
||||
* @return The `Cell` corresponding to the given query point, or `null` if
|
||||
* no cell exists at the calculated coordinates.
|
||||
*/
|
||||
fun cell(query: Vector2): Cell? = cells[coords(query)]
|
||||
|
||||
/**
|
||||
* Checks if a specific query point in 2D space is free from any nearby points or owners,
|
||||
* according to the internal grid structure and other constraints.
|
||||
*
|
||||
* @param query The 2D point represented as a Vector2 to check for available space.
|
||||
* @param ignoreOwners A set of owners to be ignored while checking for nearby points. Defaults to an empty set.
|
||||
* @return `true` if the query point is free, `false` otherwise.
|
||||
*/
|
||||
fun isFree(query: Vector2, ignoreOwners: Set<Any> = emptySet()): Boolean {
|
||||
val c = coords(query)
|
||||
if (cells[c] == null) {
|
||||
|
||||
@@ -17,6 +17,16 @@ private data class GridCoords3D(val x: Int, val y: Int, val z: Int) {
|
||||
fun offset(i: Int, j: Int, k : Int): GridCoords3D = copy(x = x + i, y = y + j, z = z + k)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a 3D cell with a fixed size in a spatial hash grid structure. A `Cell3D` is aligned
|
||||
* along a grid using its integer coordinates and supports operations to manage points within
|
||||
* its bounds, calculate distances to a query point, and retrieve its own bounding boxes.
|
||||
*
|
||||
* @property x The x-coordinate of the cell within the grid.
|
||||
* @property y The y-coordinate of the cell within the grid.
|
||||
* @property z The z-coordinate of the cell within the grid.
|
||||
* @property cellSize The size of the cell in all dimensions.
|
||||
*/
|
||||
class Cell3D(val x: Int, val y: Int, val z: Int, val cellSize: Double) {
|
||||
var xMin: Double = Double.POSITIVE_INFINITY
|
||||
private set
|
||||
@@ -31,11 +41,27 @@ class Cell3D(val x: Int, val y: Int, val z: Int, val cellSize: Double) {
|
||||
var zMax: Double = Double.NEGATIVE_INFINITY
|
||||
private set
|
||||
|
||||
/**
|
||||
* Represents the 3D bounding box of the cell.
|
||||
*
|
||||
* The bounds are calculated based on the cell's position (`x`, `y`, `z`) and
|
||||
* the uniform size of the cell (`cellSize`). It defines a cuboid in 3D space
|
||||
* with its origin at `(x * cellSize, y * cellSize, z * cellSize)` and dimensions
|
||||
* defined by `cellSize` along all three axes.
|
||||
*
|
||||
* @return A `Box` representing the spatial boundary of the cell.
|
||||
*/
|
||||
val bounds: Box
|
||||
get() {
|
||||
return Box(Vector3(x * cellSize, y * cellSize, z * cellSize), cellSize, cellSize, cellSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the bounding 3D box that contains all the points within the cell.
|
||||
* If the `points` collection is empty, it returns an empty box. Otherwise,
|
||||
* it calculates the bounding box based on the minimum and maximum coordinates
|
||||
* of the stored points (`xMin`, `xMax`, `yMin`, `yMax`, `zMin`, `zMax`).
|
||||
*/
|
||||
val contentBounds: Box
|
||||
get() {
|
||||
return if (points.isEmpty()) {
|
||||
@@ -69,6 +95,14 @@ class Cell3D(val x: Int, val y: Int, val z: Int, val cellSize: Double) {
|
||||
return dx * dx + dy * dy + dz * dz
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a sequence of all the points stored in the `points` collection.
|
||||
*
|
||||
* This method iterates over the `points` collection and yields each element.
|
||||
* Useful for lazily accessing the points in the order they are stored.
|
||||
*
|
||||
* @return A sequence of points contained within the `points` collection.
|
||||
*/
|
||||
fun points() = sequence {
|
||||
for (point in points) {
|
||||
yield(point)
|
||||
@@ -76,17 +110,44 @@ class Cell3D(val x: Int, val y: Int, val z: Int, val cellSize: Double) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a 3D Hash Grid structure used for spatial partitioning of points in 3D space.
|
||||
* This structure organizes points into grid-based cells, enabling efficient spatial querying
|
||||
* and insertion operations.
|
||||
*
|
||||
* @property radius The radius used to determine proximity checks within the grid.
|
||||
* Points are considered neighbors if their spatial distance is less than or equal to this radius.
|
||||
*/
|
||||
class HashGrid3D(val radius: Double) {
|
||||
private val cells = mutableMapOf<GridCoords3D, Cell3D>()
|
||||
|
||||
|
||||
/**
|
||||
* Returns a sequence of all the cells present in the hash grid.
|
||||
* Each cell is yielded individually from the internal mapping.
|
||||
*/
|
||||
fun cells() = sequence {
|
||||
for (cell in cells.values) {
|
||||
yield(cell)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the total number of points currently stored in the hash grid.
|
||||
* This property is incremented whenever a new point is inserted into the grid.
|
||||
* It's read-only for external access and cannot be modified outside the class.
|
||||
*/
|
||||
var size: Int = 0
|
||||
private set
|
||||
|
||||
/**
|
||||
* The size of a single cell in the 3D hash grid.
|
||||
*
|
||||
* The cell size is computed as the radius of the grid divided by the square root of 3,
|
||||
* which ensures that the cell dimensions are scaled appropriately in a 3D space.
|
||||
* This value influences the spatial resolution of the grid and determines
|
||||
* how points are grouped into cells during computations such as insertion or querying.
|
||||
*/
|
||||
val cellSize = radius / sqrt(3.0)
|
||||
private fun coords(v: Vector3): GridCoords3D {
|
||||
val x = (v.x / cellSize).fastFloor()
|
||||
@@ -95,6 +156,14 @@ class HashGrid3D(val radius: Double) {
|
||||
return GridCoords3D(x, y, z)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sequence of all points contained in the hash grid.
|
||||
*
|
||||
* Iterates over all cells in the grid and yields each contained point.
|
||||
* Each point is represented as a value yielded by the sequence.
|
||||
*
|
||||
* @return A sequence of all points stored in the hash grid.
|
||||
*/
|
||||
fun points() = sequence {
|
||||
for (cell in cells.values) {
|
||||
for (point in cell.points) {
|
||||
@@ -103,6 +172,12 @@ class HashGrid3D(val radius: Double) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a random 3D vector from the points stored in the hash grid.
|
||||
*
|
||||
* @param random A random number generator to use for selection. Defaults to `Random.Default`.
|
||||
* @return A randomly selected `Vector3` from the hash grid.
|
||||
*/
|
||||
fun random(random: Random = Random.Default): Vector3 {
|
||||
return cells.values.random(random).points.random().first
|
||||
}
|
||||
@@ -114,8 +189,25 @@ class HashGrid3D(val radius: Double) {
|
||||
size += 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the 3D cell corresponding to the given query point in the spatial hash grid.
|
||||
*
|
||||
* This method computes the grid coordinates of the query vector and attempts to fetch
|
||||
* the corresponding cell from the internal cell mapping.
|
||||
*
|
||||
* @param query A `Vector3` object representing the point used to locate the corresponding cell.
|
||||
* @return A `Cell3D` object if a cell exists for the given query point, or `null` if no such cell is found.
|
||||
*/
|
||||
fun cell(query: Vector3): Cell3D? = cells[coords(query)]
|
||||
|
||||
/**
|
||||
* Determines whether a specific point in the 3D grid is free, considering the proximity
|
||||
* to other points and optionally ignoring specified owners.
|
||||
*
|
||||
* @param query The `Vector3` representing the point to check for availability.
|
||||
* @param ignoreOwners A set of owners to ignore during the proximity check. Default is an empty set.
|
||||
* @return `true` if the point is considered free or not occupied; otherwise, `false`.
|
||||
*/
|
||||
fun isFree(query: Vector3, ignoreOwners: Set<Any> = emptySet()): Boolean {
|
||||
val c = coords(query)
|
||||
if (cells[c] == null) {
|
||||
|
||||
@@ -3,6 +3,13 @@ import org.openrndr.extra.hashgrid.filter
|
||||
import org.openrndr.extra.noise.shapes.uniform
|
||||
import kotlin.random.Random
|
||||
|
||||
/** A demo to generate and display filtered random points.
|
||||
*
|
||||
* The program performs the following steps:
|
||||
* - Generates 10,000 random points uniformly distributed within the drawable bounds.
|
||||
* - Filters the generated points to enforce a minimum distance of 20.0 units between them.
|
||||
* - Visualizes the filtered points as circles with a radius of 10.0 units on the canvas.
|
||||
*/
|
||||
fun main() {
|
||||
application {
|
||||
configure {
|
||||
|
||||
@@ -10,6 +10,16 @@ import org.openrndr.extra.noise.uniformRing
|
||||
import org.openrndr.math.Vector3
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* This demo sets up and renders a 3D visualization of filtered random points displayed as small spheres.
|
||||
*
|
||||
* The program performs the following key steps:
|
||||
* - Generates 10,000 random 3D points within a ring defined by a minimum and maximum radius.
|
||||
* - Filters the points to ensure a minimum distance between any two points using a spatial hash grid.
|
||||
* - Creates a small sphere mesh that will be instanced for each filtered point.
|
||||
* - Sets up an orbital camera to allow viewing the 3D scene interactively.
|
||||
* - Renders the filtered points by translating the sphere mesh to each point's position and applying a shader that modifies the fragment color based on the view normal.
|
||||
*/
|
||||
fun main() = application {
|
||||
configure {
|
||||
width = 720
|
||||
|
||||
@@ -4,6 +4,15 @@ import org.openrndr.extra.hashgrid.HashGrid
|
||||
import org.openrndr.extra.noise.shapes.uniform
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* This demo sets up an interactive graphics application with a configurable
|
||||
* display window and visualization logic. It uses a `HashGrid` to manage points
|
||||
* in a 2D space and randomly generates points within the drawable area. These
|
||||
* points are then inserted into the grid if they satisfy certain spatial conditions.
|
||||
* The visual output includes:
|
||||
* - Rectangles representing the bounds of the cells in the grid.
|
||||
* - Circles representing the generated points.
|
||||
*/
|
||||
fun main() {
|
||||
application {
|
||||
configure {
|
||||
@@ -12,16 +21,23 @@ fun main() {
|
||||
}
|
||||
program {
|
||||
val r = Random(0)
|
||||
val hashGrid = HashGrid(20.0)
|
||||
val hashGrid = HashGrid(72.0)
|
||||
|
||||
extend {
|
||||
val p = drawer.bounds.uniform(random = r)
|
||||
if (hashGrid.isFree(p)) {
|
||||
hashGrid.insert(p)
|
||||
for (i in 0 until 100) {
|
||||
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())
|
||||
drawer.fill = null
|
||||
drawer.stroke = ColorRGBa.PINK
|
||||
drawer.circles(hashGrid.points().map { it.first }.toList(), 36.0)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user