diff --git a/orx-shapes/src/commonMain/kotlin/RectangleGrid.kt b/orx-shapes/src/commonMain/kotlin/RectangleGrid.kt index b9eef519..45fb9273 100644 --- a/orx-shapes/src/commonMain/kotlin/RectangleGrid.kt +++ b/orx-shapes/src/commonMain/kotlin/RectangleGrid.kt @@ -18,26 +18,62 @@ fun Rectangle.grid( marginY: Double = 0.0, gutterX: Double = 0.0, gutterY: Double = 0.0 +) = grid( + (width - marginX * 2 - gutterX * (columns - 1)) / columns, + (height - marginY * 2 - gutterY * (rows - 1)) / rows, + marginX, marginY, + gutterX, gutterY +) + +/** + * Splits [Rectangle] into a grid of [Rectangle]s + * @param cellWidth the unitless width of a cell + * @param cellHeight the unitless height of a cell + * @param minMarginX the unitless minimum margin width (may increase to produce + * the desired cell aspect ratio) + * @param minMarginY the unitless minimum margin height (may increase to produce + * the desired cell aspect ratio) + * @param gutterX the unitless gutter width, the horizontal space between grid cells + * @param gutterY the unitless gutter height, the vertical space between grid cells + */ +fun Rectangle.grid( + cellWidth: Double, + cellHeight: Double, + minMarginX: Double = 0.0, + minMarginY: Double = 0.0, + gutterX: Double = 0.0, + gutterY: Double = 0.0 ): List> { - val totalWidth = width - marginX * 2.0 - val totalHeight = height - marginY * 2.0 - - val totalGutterWidth = gutterX * (columns - 1).coerceAtLeast(0) - val totalGutterHeight = gutterY * (rows - 1).coerceAtLeast(0) - - val cellWidth = ((totalWidth - totalGutterWidth) / columns).coerceAtLeast(0.0) - val cellHeight = ((totalHeight - totalGutterHeight) / rows).coerceAtLeast(0.0) + val availableWidth = (width - minMarginX * 2).coerceAtLeast(0.0) + val availableHeight = (height - minMarginY * 2).coerceAtLeast(0.0) val cellSpaceX = cellWidth + gutterX val cellSpaceY = cellHeight + gutterY - val x0 = x + marginX - val y0 = y + marginY + val columns = ((availableWidth + gutterX) / cellSpaceX).toInt() + val rows = ((availableHeight + gutterY) / cellSpaceY).toInt() + + if (columns == 0 || rows == 0) { + return emptyList() + } + + val totalGutterWidth = gutterX * (columns - 1).coerceAtLeast(0) + val totalGutterHeight = gutterY * (rows - 1).coerceAtLeast(0) + + val totalWidth = cellWidth * columns + totalGutterWidth + val totalHeight = cellHeight * rows + totalGutterHeight + + val x0 = x + (width - totalWidth) / 2 + val y0 = y + (height - totalHeight) / 2 return (0 until rows).map { row -> (0 until columns).map { column -> - Rectangle(x0 + column * cellSpaceX, y0 + row * cellSpaceY, cellWidth, cellHeight) + Rectangle( + x0 + column * cellSpaceX, + y0 + row * cellSpaceY, + cellWidth, cellHeight + ) } } -} \ No newline at end of file +} diff --git a/orx-shapes/src/demo/kotlin/DemoRectangleGrid.kt b/orx-shapes/src/demo/kotlin/DemoRectangleGrid01.kt similarity index 83% rename from orx-shapes/src/demo/kotlin/DemoRectangleGrid.kt rename to orx-shapes/src/demo/kotlin/DemoRectangleGrid01.kt index 589e4390..9e64f1f4 100644 --- a/orx-shapes/src/demo/kotlin/DemoRectangleGrid.kt +++ b/orx-shapes/src/demo/kotlin/DemoRectangleGrid01.kt @@ -1,6 +1,5 @@ import org.openrndr.application import org.openrndr.color.ColorRGBa -import org.openrndr.extensions.SingleScreenshot import org.openrndr.extra.shapes.grid fun main() { @@ -13,6 +12,9 @@ fun main() { extend { drawer.fill = ColorRGBa.WHITE.opacify(0.25) drawer.stroke = ColorRGBa.PINK + + // Notice the negative gutter in this grid. It creates an + // overlap between the resulting rectangles. val grid = drawer.bounds.grid(8, 4, 20.0, 20.0, -20.0, -20.0) for (cell in grid.flatten()) { drawer.rectangle(cell) diff --git a/orx-shapes/src/demo/kotlin/DemoRectangleGrid02.kt b/orx-shapes/src/demo/kotlin/DemoRectangleGrid02.kt new file mode 100644 index 00000000..560d228d --- /dev/null +++ b/orx-shapes/src/demo/kotlin/DemoRectangleGrid02.kt @@ -0,0 +1,37 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extra.noise.Random +import org.openrndr.extra.shapes.grid + +fun main() { + application { + // Try changing the resolution. The design will use the available space. + configure { + width = 800 + height = 400 + } + program { + // By specifying the cell size we make sure the design will + // contain squares, independently of the window size and its + // aspect ratio. + val grid = drawer.bounds.grid(50.0, 50.0, + 20.0, 20.0, 20.0, 20.0).flatten() + + val grid2 = grid.map { rect -> + // Each of these inner grids will occupy the available space + // in the parent grid cells. Notice how we don't specify cell + // sizes here but counts instead (between 1 and 3 columns and + // rows) + val count = Random.int(1, 4) + rect.grid(count, count, 5.0, 5.0, 5.0, 5.0).flatten() + }.flatten().filter { Random.bool(0.5)} + + extend { + drawer.clear(ColorRGBa.PINK) + drawer.rectangles(grid) + drawer.fill = ColorRGBa.BLACK + drawer.rectangles(grid2) + } + } + } +} \ No newline at end of file