diff --git a/orx-shapes/src/commonMain/kotlin/primitives/RectangleGrid.kt b/orx-shapes/src/commonMain/kotlin/primitives/RectangleGrid.kt index 42c96a77..9504c9ec 100644 --- a/orx-shapes/src/commonMain/kotlin/primitives/RectangleGrid.kt +++ b/orx-shapes/src/commonMain/kotlin/primitives/RectangleGrid.kt @@ -1,7 +1,64 @@ package org.openrndr.extra.shapes.primitives import org.openrndr.shape.Rectangle +import org.openrndr.shape.bounds +import kotlin.jvm.JvmName import kotlin.math.round +import kotlin.random.Random + +/** + * Divides a rectangle into a grid of sub-rectangles with irregular spacing, + * based on the specified column and row weights. Optionally, margins can be + * applied on both the horizontal and vertical directions. + * + * @param columnWeights A list of relative weights for the columns. The size + * of this list determines the number of columns, and each weight defines + * the proportional width of the respective column. + * @param rowWeights A list of relative weights for the rows. The size of + * this list determines the number of rows, and each weight defines the + * proportional height of the respective row. + * @param marginX The horizontal margin between the edges of the main rectangle + * and the grid. Defaults to 0.0. + * @param marginY The vertical margin between the edges of the main rectangle + * and the grid. Defaults to 0.0. + * @return A list of lists, where each sublist represents a row of the grid, + * and each element within the row is a sub-rectangle corresponding to a cell + * in the grid. + */ +fun Rectangle.irregularGrid( + columnWeights: List, + rowWeights: List, + marginX: Double = 0.0, + marginY: Double = 0.0, +): List> { + + val columnWeight = columnWeights.sum() + val rowWeight = rowWeights.sum() + + val columnRatios = columnWeights.map { it / columnWeight } + val rowRatios = rowWeights.map { it / rowWeight } + + val us = columnRatios.scan(0.0) { acc, d -> acc + d } + val vs = rowRatios.scan(0.0) { acc, d -> acc + d } + + val result = mutableListOf>() + + val withMargins = this.offsetEdges(-marginX, -marginY) + + for (j in 0 until vs.size - 1) { + val v0 = vs[j] + val v1 = vs[j + 1] + val row = mutableListOf() + for (i in 0 until us.size - 1) { + val u0 = us[i] + val u1 = us[i + 1] + row.add(withMargins.sub(u0, v0, u1, v1)) + } + result.add(row) + } + return result +} + /** * Splits [Rectangle] into a grid of [Rectangle]s @@ -87,7 +144,7 @@ fun Rectangle.grid( * * @return A new 2D list of rectangles where rows and columns are swapped. */ -fun List>.transpose() : List> { +fun List>.transpose(): List> { val columns = MutableList>(this[0].size) { mutableListOf() } for (row in this) { for ((index, column) in row.withIndex()) { @@ -95,4 +152,88 @@ fun List>.transpose() : List> { } } return columns -} \ No newline at end of file +} + +/** + * Retrieves a [Rectangle] from a two-dimensional list of [Rectangle]s based on + * the specified x and y indices. + * + * @param x The column index in the two-dimensional list. + * @param y The row index in the two-dimensional list. + * @return The [Rectangle] at the specified indices (x, y). + */ +operator fun List>.get(x: Int, y: Int): Rectangle = this[y][x] + + +/** + * Retrieves a sublist of [Rectangle] objects from a two-dimensional [List] given a range of indices for rows and a specific column index. + * + * @param xRange The range of indices specifying the columns to be sliced. + * @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>.get(xRange: IntRange, y: Int): List = this[y].slice(xRange) + +/** + * Retrieves a list of rectangles at a specific x-coordinate for a range of y-coordinates + * from a 2D list of rectangles. + * + * @param x The x-coordinate to access within each inner list. + * @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>.get(x: Int, yRange: IntRange): List = yRange.map { y -> this[y][x] } + +/** + * Retrieves a subgrid from a 2D list of [Rectangle]s based on the specified ranges. + * + * @param xRange The range of x indices to include in the subgrid. + * @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>.get(xRange: IntRange, yRange: IntRange): List> = + yRange.map { y -> xRange.map { x -> this[y][x] } } + + +/** + * Computes the bounding rectangle that encompasses all the rectangles contained in the lists. + * + * This property traverses the two-dimensional list structure to compute the bounds of each + * individual rectangle, ultimately returning a single [Rectangle] that encompasses + * all the rectangles in all nested lists. + * + * If the list is empty or contains no rectangles, the resulting bounds might be undefined, + * depending on the behavior of the nested bounds calculations. + */ +val List>.bounds: Rectangle + @JvmName("getRectangleListBounds") get() { + val bounds = map { it.bounds } + return bounds.bounds + } + +/** + * Selects a random [Rectangle] from a nested list of rectangles using the provided random generator. + * + * @param random An instance of [Random] used to select rectangles in a random manner. + * @return A randomly selected [Rectangle] from the nested list. + */ +fun List>.uniform(random: Random): Rectangle { + return this.random(random).random(random) +} + +/** + * Retrieves a column of rectangles from a 2D list of rectangles. + * + * @param index The index of the column to retrieve. + * @return A list of [Rectangle] objects representing the specified column. + */ +fun List>.column(index: Int): List = this.map { it[index] } + +/** + * Retrieves the row from a 2D list of `Rectangle` objects at the specified index. + * + * @receiver The 2D list of `Rectangle` objects. + * @param index The index of the row to retrieve. Must be in the valid range of indices for the list. + * @return A list of `Rectangle` objects representing the row at the given index. + */ +fun List>.row(index: Int): List = this[index] \ No newline at end of file diff --git a/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleGrid03.kt b/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleGrid03.kt new file mode 100644 index 00000000..56cc8b83 --- /dev/null +++ b/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleGrid03.kt @@ -0,0 +1,39 @@ +package primitives + +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extra.shapes.primitives.bounds +import org.openrndr.extra.shapes.primitives.grid +import org.openrndr.extra.shapes.primitives.get +import org.openrndr.shape.bounds + +fun main() = application { + configure { + width = 720 + height = 720 + } + program { + val grid = drawer.bounds.grid(12, 5) + + extend { + drawer.stroke = ColorRGBa.WHITE + drawer.fill = null + drawer.rectangles(grid.flatten()) + + drawer.fill = ColorRGBa.GRAY.shade(0.4).opacify(0.5) + drawer.rectangle(grid[1..10, 0..4].bounds) + + drawer.fill = ColorRGBa.PINK.shade(0.5).opacify(0.5) + drawer.rectangle(grid[5..6, 1].bounds) + + drawer.fill = ColorRGBa.PINK.opacify(0.5) + drawer.rectangle(grid[2..5, 2].bounds) + + drawer.fill = ColorRGBa.GRAY.opacify(0.5) + drawer.rectangle(grid[6..9, 2].bounds) + + drawer.fill = ColorRGBa.GRAY.shade(0.5).opacify(0.5) + drawer.rectangle(grid[5..6, 3].bounds) + } + } +} diff --git a/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleIrregularGrid.kt b/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleIrregularGrid.kt new file mode 100644 index 00000000..16dd778e --- /dev/null +++ b/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleIrregularGrid.kt @@ -0,0 +1,39 @@ +package primitives + +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extra.color.presets.CORAL +import org.openrndr.extra.noise.uniform +import org.openrndr.extra.noise.uniforms +import org.openrndr.extra.shapes.primitives.column +import org.openrndr.extra.shapes.primitives.irregularGrid +import org.openrndr.extra.shapes.primitives.row +import kotlin.random.Random + +fun main() = application { + configure { + width = 720 + height = 720 + } + program { + extend { + val r = Random(100) + val grid = drawer.bounds.irregularGrid( + Double.uniforms(13, 0.1, 0.5, r), + Double.uniforms(13, 0.1, 0.5, r), + 20.0, 20.0 + ) + + drawer.fill = null + drawer.stroke = ColorRGBa.WHITE + drawer.rectangles(grid.flatten()) + + drawer.stroke = ColorRGBa.BLACK + drawer.fill = ColorRGBa.PINK.opacify(0.5) + drawer.rectangles(grid.column(2)) + + drawer.fill = ColorRGBa.CORAL.opacify(0.5) + drawer.rectangles(grid.row(6)) + } + } +} \ No newline at end of file diff --git a/orx-shapes/src/jvmDemo/kotlin/text/DemoText01.kt b/orx-shapes/src/jvmDemo/kotlin/text/DemoText01.kt index e27371cb..506c13ba 100644 --- a/orx-shapes/src/jvmDemo/kotlin/text/DemoText01.kt +++ b/orx-shapes/src/jvmDemo/kotlin/text/DemoText01.kt @@ -5,8 +5,10 @@ import org.openrndr.color.ColorRGBa import org.openrndr.draw.font.loadFace import org.openrndr.extra.shapes.bounds.bounds import org.openrndr.extra.shapes.text.shapesFromText +import org.openrndr.internal.Driver fun main() = application { + System.setProperty("org.openrndr.draw.wait_for_finish", "true") configure { width = 720 height = 720