From b03e39b52e7ce312b5182086af80220e8d5aad88 Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Thu, 20 Feb 2025 23:55:29 +0100 Subject: [PATCH] [orx-shapes] Add rectangle splitting and subtraction utilities Introduce functions to split rectangles at specified axes and subtract overlapping areas. These utilities allow precise manipulation of rectangle shapes, including dividing and removing intersections while preserving non-overlapping sections. --- .../kotlin/primitives/RectangleSubtract.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 orx-shapes/src/commonMain/kotlin/primitives/RectangleSubtract.kt diff --git a/orx-shapes/src/commonMain/kotlin/primitives/RectangleSubtract.kt b/orx-shapes/src/commonMain/kotlin/primitives/RectangleSubtract.kt new file mode 100644 index 00000000..81fd8587 --- /dev/null +++ b/orx-shapes/src/commonMain/kotlin/primitives/RectangleSubtract.kt @@ -0,0 +1,76 @@ +package org.openrndr.extra.shapes.primitives + +import org.openrndr.math.map +import org.openrndr.shape.Rectangle + +/** + * Splits the current Rectangle into two smaller rectangles at a specified x-coordinate. + * + * If the given x-coordinate lies within the range of the current rectangle's width, + * the method returns two rectangles: one to the left and one to the right of the specified x-coordinate. + * If the x-coordinate is outside the bounds of the rectangle, the method returns a list + * containing only the current rectangle. + * + * @param x The x-coordinate at which the rectangle is to be split. + * @return A list of rectangles resulting from the split. The list contains two rectangles if the split + * occurs within the bounds of the current rectangle, or the original rectangle if the x-coordinate + * is outside its bounds. + */ +fun Rectangle.splitAtX(x: Double): List { + return if (x in xRange) { + val u = x.map(this.x, this.x + width, 0.0, 1.0) + listOf(sub(0.0, 0.0, u, 1.0), sub(u, 0.0, 1.0, 1.0)) + } else { + listOf(this) + } +} + +/** + * Splits the rectangle horizontally at the specified `y` value if the value lies within the rectangle's vertical range. + * + * @param y The vertical value along the y-axis at which to split the rectangle. + * @return A list of rectangles resulting from the split. If the y value is outside the vertical range, + * the original rectangle is returned as a single-item list. + */ +fun Rectangle.splitAtY(y: Double): List { + return if (y in yRange) { + val v = y.map(this.y, this.y + height, 0.0, 1.0) + listOf(sub(0.0, 0.0, 1.0, v), sub(0.0, v, 1.0, 1.0)) + } else { + listOf(this) + } +} + +/** + * Subtracts the given rectangle `other` from the current rectangle, splitting and removing + * overlapping areas and returning the remaining non-overlapping parts as a list of rectangles. + * + * @param other The rectangle to subtract from the current rectangle. + * @return A list of rectangles representing the non-overlapping parts of the current rectangle + * after the subtraction. If there is no intersection, the list contains only the original rectangle. + */ +fun Rectangle.subtract(other: Rectangle): List { + return if (this.intersects(other)) { + var items = listOf(this) + items = items.flatMap { it.splitAtX(other.x) } + items = items.flatMap { it.splitAtY(other.y) } + items = items.flatMap { it.splitAtX(other.x + other.width) } + items = items.flatMap { it.splitAtY(other.y + other.height) } + items = items.filter { it.area > 0 && !it.intersects(other.offsetEdges(-1E-5)) } + items + } else { + listOf(this) + } +} + +/** + * Subtracts a rectangle from a list of rectangles, removing overlapping areas and returning + * the non-overlapping parts of the original rectangles. + * + * @param other The rectangle to subtract from the list of rectangles. + * @return A list of rectangles representing the non-overlapping areas from the subtraction + * of the specified rectangle. + */ +fun List.subtract(other : Rectangle) : List { + return this.flatMap { it.subtract(other) } +} \ No newline at end of file