[orx-shapes] Add RectangleGrid abstraction and update related utility functions

This commit is contained in:
Edwin Jakobs
2025-08-16 15:53:05 +02:00
parent c44175c1c9
commit 72b02ce585

View File

@@ -6,6 +6,18 @@ import kotlin.jvm.JvmName
import kotlin.math.round
import kotlin.random.Random
/**
* Represents a rectangular grid structure composed of lists of [Rectangle] instances.
*
* This class provides a convenient abstraction for working with 2D grids,
* where each cell is represented by a [Rectangle]. The class delegates
* to the underlying grid structure, which is implemented as a list of lists,
* allowing it to be used interchangeably with other list-based data structures.
*
* @property grid The 2D list structure containing [Rectangle] elements.
*/
class RectangleGrid(val grid: List<List<Rectangle>>) : List<List<Rectangle>> by grid
/**
* Divides a rectangle into a grid of sub-rectangles with irregular spacing,
* based on the specified column and row weights. Optionally, margins can be
@@ -30,7 +42,7 @@ fun Rectangle.irregularGrid(
rowWeights: List<Double>,
marginX: Double = 0.0,
marginY: Double = 0.0,
): List<List<Rectangle>> {
): RectangleGrid {
val columnWeight = columnWeights.sum()
val rowWeight = rowWeights.sum()
@@ -56,7 +68,7 @@ fun Rectangle.irregularGrid(
}
result.add(row)
}
return result
return RectangleGrid(result)
}
/**
@@ -100,7 +112,7 @@ fun Rectangle.grid(
minMarginY: Double = 0.0,
gutterX: Double = 0.0,
gutterY: Double = 0.0
): List<List<Rectangle>> {
): RectangleGrid {
val availableWidth = (width - minMarginX * 2).coerceAtLeast(0.0)
val availableHeight = (height - minMarginY * 2).coerceAtLeast(0.0)
@@ -112,7 +124,7 @@ fun Rectangle.grid(
val rows = round((availableHeight + gutterY) / cellSpaceY).toInt()
if (columns == 0 || rows == 0) {
return emptyList()
return RectangleGrid(emptyList())
}
val totalGutterWidth = gutterX * (columns - 1).coerceAtLeast(0)
@@ -132,7 +144,7 @@ fun Rectangle.grid(
cellWidth, cellHeight
)
}
}
}.let { RectangleGrid(it) }
}
/**
@@ -143,14 +155,14 @@ fun Rectangle.grid(
*
* @return A new 2D list of rectangles where rows and columns are swapped.
*/
fun List<List<Rectangle>>.transpose(): List<List<Rectangle>> {
fun RectangleGrid.transpose(): RectangleGrid {
val columns = MutableList<MutableList<Rectangle>>(this[0].size) { mutableListOf() }
for (row in this) {
for ((index, column) in row.withIndex()) {
columns[index].add(column)
}
}
return columns
return RectangleGrid(columns)
}
/**
@@ -161,7 +173,7 @@ fun List<List<Rectangle>>.transpose(): List<List<Rectangle>> {
* @param y The row index in the two-dimensional list.
* @return The [Rectangle] at the specified indices (x, y).
*/
operator fun List<List<Rectangle>>.get(x: Int, y: Int): Rectangle = this[y][x]
operator fun RectangleGrid.get(x: Int, y: Int): Rectangle = this[y][x]
/**
@@ -171,7 +183,7 @@ operator fun List<List<Rectangle>>.get(x: Int, y: Int): Rectangle = this[y][x]
* @param y The index of the row from which the sublist is retrieved.
* @return A sublist of [Rectangle] objects within the specified range of columns from the specified row.
*/
operator fun List<List<Rectangle>>.get(xRange: IntRange, y: Int): List<Rectangle> = this[y].slice(xRange)
operator fun RectangleGrid.get(xRange: IntRange, y: Int): List<Rectangle> = this[y].slice(xRange)
/**
* Retrieves a list of rectangles at a specific x-coordinate for a range of y-coordinates
@@ -181,7 +193,7 @@ operator fun List<List<Rectangle>>.get(xRange: IntRange, y: Int): List<Rectangle
* @param yRange The range of y-coordinates (indices of the outer list) to retrieve rectangles from.
* @return A list of rectangles corresponding to the specified x-coordinate and y-coordinate range.
*/
operator fun List<List<Rectangle>>.get(x: Int, yRange: IntRange): List<Rectangle> = yRange.map { y -> this[y][x] }
operator fun RectangleGrid.get(x: Int, yRange: IntRange): List<Rectangle> = yRange.map { y -> this[y][x] }
/**
* Retrieves a subgrid from a 2D list of [Rectangle]s based on the specified ranges.
@@ -190,8 +202,8 @@ operator fun List<List<Rectangle>>.get(x: Int, yRange: IntRange): List<Rectangle
* @param yRange The range of y indices to include in the subgrid.
* @return A 2D list containing the elements of the subgrid specified by the ranges.
*/
operator fun List<List<Rectangle>>.get(xRange: IntRange, yRange: IntRange): List<List<Rectangle>> =
yRange.map { y -> xRange.map { x -> this[y][x] } }
operator fun RectangleGrid.get(xRange: IntRange, yRange: IntRange): RectangleGrid =
RectangleGrid(yRange.map { y -> xRange.map { x -> this[y][x] } })
/**
@@ -230,13 +242,13 @@ fun List<List<Rectangle>>.uniform(random: Random): Rectangle {
* @param random An instance of Random used to generate random coordinates and dimensions for the block.
* @return A 2D list of rectangles representing the randomly extracted uniform block.
*/
fun List<List<Rectangle>>.uniformBlock(
fun RectangleGrid.uniformBlock(
minWidth: Int = 1,
maxWidth: Int = this[0].size,
minHeight: Int = 1,
maxHeight: Int = this.size,
random: Random = Random.Default
): List<List<Rectangle>> {
): RectangleGrid {
require(minWidth > 0) { "Minimum width must be greater than zero." }
require(minHeight > 0) { "Minimum height must be greater than zero." }
require(minWidth <= maxWidth) { "Minimum width must be less than or equal to maximum width." }
@@ -277,14 +289,14 @@ fun List<List<Rectangle>>.row(index: Int): List<Rectangle> = this[index]
* @param height The height of the block, specifying the number of rows to include.
* @return A 2D list of rectangles representing the extracted block.
*/
fun List<List<Rectangle>>.block(x: Int, y: Int, width: Int, height: Int): List<List<Rectangle>> {
fun RectangleGrid.block(x: Int, y: Int, width: Int, height: Int): RectangleGrid {
require(x + width <= this[0].size) { "Width of block exceeds bounds of the original 2D list." }
require(y + height <= this.size) { "Height of block exceeds bounds of the original 2D list." }
require(width > 0) { "Width of block must be greater than zero." }
require(height > 0) { "Height of block must be greater than zero." }
require(x >= 0) { "X coordinate of block must be non-negative." }
require(y >= 0) { "Y coordinate of block must be non-negative." }
return this[x..<x + width, y..<y + height]
return RectangleGrid(this[x..<x + width, y..<y + height])
}
/**
@@ -296,7 +308,7 @@ fun List<List<Rectangle>>.block(x: Int, y: Int, width: Int, height: Int): List<L
* @param n The number of columns to drop from each inner list. Must be non-negative.
* @return A new 2D list of Rectangles with the first n columns removed.
*/
fun List<List<Rectangle>>.dropColumns(n: Int): List<List<Rectangle>> = map { it.drop(n) }
fun RectangleGrid.dropColumns(n: Int): RectangleGrid = RectangleGrid(map { it.drop(n) })
/**
* Removes the last `n` columns from each row (inner list) within a two-dimensional list.
@@ -304,7 +316,7 @@ fun List<List<Rectangle>>.dropColumns(n: Int): List<List<Rectangle>> = map { it.
* @param n The number of columns to drop from the end of each inner list.
* @return A new two-dimensional list with the last `n` columns removed from each row.
*/
fun List<List<Rectangle>>.dropLastColumns(n: Int): List<List<Rectangle>> = map { it.dropLast(n) }
fun RectangleGrid.dropLastColumns(n: Int): RectangleGrid = RectangleGrid(map { it.dropLast(n) })
/**
* Selects the first `n` columns from each row in a 2D list of `Rectangle` objects.
@@ -313,7 +325,7 @@ fun List<List<Rectangle>>.dropLastColumns(n: Int): List<List<Rectangle>> = map {
* all elements of that row are returned.
* @return A new 2D list containing the first `n` columns from each row of the original list.
*/
fun List<List<Rectangle>>.takeColumns(n: Int): List<List<Rectangle>> = map { it.take(n) }
fun RectangleGrid.takeColumns(n: Int): RectangleGrid = RectangleGrid(map { it.take(n) })
/**
* Returns a new list where each sub-list contains only the last `n` elements of the original sub-list.
@@ -321,7 +333,7 @@ fun List<List<Rectangle>>.takeColumns(n: Int): List<List<Rectangle>> = map { it.
* @param n The number of elements to retain from the end of each sub-list.
* @return A list containing sub-lists that include the last `n` elements of each original sub-list.
*/
fun List<List<Rectangle>>.takeLastColumns(n: Int): List<List<Rectangle>> = map { it.takeLast(n) }
fun RectangleGrid.takeLastColumns(n: Int): RectangleGrid = RectangleGrid(map { it.takeLast(n) })
/**
* Slices the specified range of columns from each row of a two-dimensional list.
@@ -329,7 +341,7 @@ fun List<List<Rectangle>>.takeLastColumns(n: Int): List<List<Rectangle>> = map {
* @param range The range of column indices to slice from each row.
* @return A new list containing sublists with columns sliced from the input range.
*/
fun List<List<Rectangle>>.sliceColumns(range: IntRange): List<List<Rectangle>> = map { it.slice(range) }
fun RectangleGrid.sliceColumns(range: IntRange): RectangleGrid = RectangleGrid(map { it.slice(range) })
/**
* Selects specific columns from a two-dimensional list of rectangles.
@@ -338,4 +350,4 @@ fun List<List<Rectangle>>.sliceColumns(range: IntRange): List<List<Rectangle>> =
* @param indices The collection of column indices to retain in each inner list.
* @return A new two-dimensional list of rectangles with only the selected columns.
*/
fun List<List<Rectangle>>.sliceColumns(indices: Iterable<Int>): List<List<Rectangle>> = map { it.slice(indices) }
fun RectangleGrid.sliceColumns(indices: Iterable<Int>): RectangleGrid = RectangleGrid(map { it.slice(indices) })