From 2438dbe7701f31d1a4e49feb39c577008e7460fd Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Thu, 26 Sep 2024 22:15:07 +0200 Subject: [PATCH] [orx-shapes] Add Box.intersection() and Box.grid() --- .../commonMain/kotlin/primitives/BoxGrid.kt | 101 ++++++++++++++++++ .../kotlin/primitives/BoxIntersection.kt | 26 +++++ 2 files changed, 127 insertions(+) create mode 100644 orx-shapes/src/commonMain/kotlin/primitives/BoxGrid.kt create mode 100644 orx-shapes/src/commonMain/kotlin/primitives/BoxIntersection.kt diff --git a/orx-shapes/src/commonMain/kotlin/primitives/BoxGrid.kt b/orx-shapes/src/commonMain/kotlin/primitives/BoxGrid.kt new file mode 100644 index 00000000..064624c4 --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/primitives/BoxGrid.kt @@ -0,0 +1,101 @@ +package org.openrndr.extra.shapes.primitives + +import org.openrndr.shape.Box +import kotlin.math.round + +/** + * Split [Box] into a grid of [Box]es + * @param columns the number of columns in the resulting grid + * @param rows the number of rows in the resulting grid + * @param marginX the unitless margin width + * @param marginY the unitless margin height + * @param marginZ the untless margin depth + * @param gutterX the unitless gutter width, the horizontal space between grid cells + * @param gutterY the unitless gutter height, the vertical space between grid cells + * @param gutterZ the unitless gutter depth + */ +fun Box.grid( + columns: Int, + rows: Int, + slices: Int, + marginX: Double = 0.0, + marginY: Double = 0.0, + marginZ: Double = 0.0, + gutterX: Double = 0.0, + gutterY: Double = 0.0, + gutterZ: Double = 0.0 +) = grid( + (width - marginX * 2 - gutterX * (columns - 1)) / columns, + (height - marginY * 2 - gutterY * (rows - 1)) / rows, + (depth - marginZ * 2 - gutterZ * (slices - 1)) / slices, + marginX, marginY, marginZ, + gutterX, gutterY, gutterZ +) + +/** + * Split [Box] into a grid of [Box]es + * @param cellWidth the unitless width of a cell + * @param cellHeight the unitless height of a cell + * @param cellDepth the unitless depth 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 minMarginZ the unitless minimum margin depth (may increase to produce + * @param gutterX the unitless gutter width, the horizontal space between grid cells + * @param gutterY the unitless gutter height, the vertical space between grid cells + * @param gutterZ the unitless gutter depth + */ +fun Box.grid( + cellWidth: Double, + cellHeight: Double, + cellDepth: Double, + minMarginX: Double = 0.0, + minMarginY: Double = 0.0, + minMarginZ: Double = 0.0, + gutterX: Double = 0.0, + gutterY: Double = 0.0, + gutterZ: Double = 0.0 +): List>> { + + val availableWidth = (width - minMarginX * 2).coerceAtLeast(0.0) + val availableHeight = (height - minMarginY * 2).coerceAtLeast(0.0) + val availableDepth = (depth - minMarginZ * 2).coerceAtLeast(0.0) + + val cellSpaceX = cellWidth + gutterX + val cellSpaceY = cellHeight + gutterY + val cellSpaceZ = cellDepth + gutterZ + + val columns = round((availableWidth + gutterX) / cellSpaceX).toInt() + val rows = round((availableHeight + gutterY) / cellSpaceY).toInt() + val slices = round((availableDepth + gutterZ) / cellSpaceZ).toInt() + + if (columns == 0 || rows == 0 || slices == 0) { + return emptyList() + } + + val totalGutterWidth = gutterX * (columns - 1).coerceAtLeast(0) + val totalGutterHeight = gutterY * (rows - 1).coerceAtLeast(0) + val totalGutterDepth = gutterZ * (slices - 1).coerceAtLeast(0) + + val totalWidth = cellWidth * columns + totalGutterWidth + val totalHeight = cellHeight * rows + totalGutterHeight + val totalDepth = cellDepth * slices + totalGutterDepth + + val x0 = corner.x + (width - totalWidth) / 2 + val y0 = corner.y + (height - totalHeight) / 2 + val z0 = corner.z + (depth - totalDepth) / 2 + + return (0 until slices).map { slice -> + (0 until rows).map { row -> + (0 until columns).map { column -> + Box( + x0 + column * cellSpaceX, + y0 + row * cellSpaceY, + z0 + slice * cellSpaceZ, + cellWidth, cellHeight, cellDepth + ) + } + } + } +} diff --git a/orx-shapes/src/commonMain/kotlin/primitives/BoxIntersection.kt b/orx-shapes/src/commonMain/kotlin/primitives/BoxIntersection.kt new file mode 100644 index 00000000..42da9707 --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/primitives/BoxIntersection.kt @@ -0,0 +1,26 @@ +package org.openrndr.extra.shapes.primitives + +import org.openrndr.shape.Box +import org.openrndr.shape.Rectangle +import kotlin.math.max +import kotlin.math.min + +/** + * Find intersection of [this] and [other] + * @return a rectangle shaped intersection or [Rectangle.EMPTY] when the intersection is empty. + */ +fun Box.intersection(other: Box) : Box = if (this.intersects(other)) { + val tn = this.normalized + val on = other.normalized + + val left = max(tn.corner.x, on.corner.x) + val right = min(tn.corner.x + tn.width, on.corner.x + on.width) + val top = max(tn.corner.y, on.corner.y) + val bottom = min(tn.corner.y + tn.height, on.corner.y + on.height) + val near = max(tn.corner.z, on.corner.z) + val far = min(tn.corner.z + tn.depth, on.corner.z + on.depth) + + Box(left, top, near, right - left, bottom - top, far - near) +} else { + Box.EMPTY +} \ No newline at end of file