[orx-shapes] Add rectangle align, distribute and fit functions
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
package org.openrndr.extra.shapes.primitives
|
||||
|
||||
import org.openrndr.shape.Rectangle
|
||||
|
||||
/**
|
||||
* Adjusts the dimensions and position of the rectangle based on the provided parameters.
|
||||
* The method calculates the new dimensions and coordinates of the rectangle based on specified
|
||||
* values for left, right, top, bottom, width, or height. If conflicting parameters are provided
|
||||
* (e.g., both `left` and `right` or `top` and `bottom`), an error is thrown.
|
||||
*
|
||||
* @param left Optional offset to adjust the left side of the rectangle.
|
||||
* If provided along with `right`, an error is thrown.
|
||||
* @param right Optional offset to adjust the right side of the rectangle.
|
||||
* If provided along with `left`, an error is thrown.
|
||||
* @param top Optional offset to adjust the top side of the rectangle.
|
||||
* If provided along with `bottom`, an error is thrown.
|
||||
* @param bottom Optional offset to adjust the bottom side of the rectangle.
|
||||
* If provided along with `top`, an error is thrown.
|
||||
* @param width Optional value to override the width of the rectangle. Ignored if both
|
||||
* `left` and `right` are provided.
|
||||
* @param height Optional value to override the height of the rectangle. Ignored if both
|
||||
* `top` and `bottom` are provided.
|
||||
* @return A new [Rectangle] with the adjusted dimensions and position.
|
||||
*/
|
||||
fun Rectangle.adjacent(left: Double? = null, right: Double? = null, top: Double? = null, bottom: Double? = null,
|
||||
width: Double? = null, height: Double? = null
|
||||
|
||||
) : Rectangle {
|
||||
val newWidth = when {
|
||||
left != null && right != null -> this.width + left + right
|
||||
else -> width?: this.width
|
||||
}
|
||||
val newHeight = when {
|
||||
top != null && bottom != null -> this.height + top + bottom
|
||||
else -> height?: this.height
|
||||
}
|
||||
|
||||
val newX = when {
|
||||
left != null && right != null -> error("set either left or right, not both")
|
||||
left != null -> corner.x - newWidth - left
|
||||
right != null -> corner.x + this.width + right
|
||||
else -> corner.x
|
||||
}
|
||||
|
||||
val newY = when {
|
||||
bottom != null && top != null -> error("set either top or bottom, not both")
|
||||
top != null -> corner.y - newHeight - top
|
||||
bottom != null -> corner.y + this.height + bottom
|
||||
else -> corner.y
|
||||
}
|
||||
|
||||
return Rectangle(newX, newY, newWidth, newHeight)
|
||||
}
|
||||
146
orx-shapes/src/commonMain/kotlin/primitives/RectangleAlign.kt
Normal file
146
orx-shapes/src/commonMain/kotlin/primitives/RectangleAlign.kt
Normal file
@@ -0,0 +1,146 @@
|
||||
package org.openrndr.extra.shapes.primitives
|
||||
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.shape.Rectangle
|
||||
import org.openrndr.shape.bounds
|
||||
|
||||
/**
|
||||
* Aligns a list of rectangles horizontally relative to a specified rectangle.
|
||||
*
|
||||
* Each rectangle in the list is repositioned so that its horizontal
|
||||
* alignment matches the specified anchor point of the target rectangle.
|
||||
*
|
||||
* @param to The target rectangle to align to.
|
||||
* @param anchor A value between 0.0 and 1.0 representing the horizontal position
|
||||
* within the target rectangle. Default is 0.5 (center).
|
||||
* @return A new list of rectangles aligned horizontally relative to the target rectangle.
|
||||
*/
|
||||
fun List<Rectangle>.alignToHorizontally(to: Rectangle, anchor: Double = 0.5): List<Rectangle> {
|
||||
val tox = to.position(anchor, 0.0).x
|
||||
|
||||
return this.map {
|
||||
Rectangle.fromAnchor(Vector2(anchor, 0.0), Vector2(tox, it.y), it.width, it.height)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns the rectangles in the list vertically to a reference rectangle.
|
||||
* The vertical position of each rectangle is determined based on the reference rectangle
|
||||
* and the specified vertical anchor point.
|
||||
*
|
||||
* @param to The reference rectangle to which the list of rectangles is vertically aligned.
|
||||
* @param anchor A value between 0.0 and 1.0 representing the vertical anchor point.
|
||||
* Defaults to 0.5, which aligns based on the center.
|
||||
* @return A new list of rectangles aligned vertically to the specified rectangle.
|
||||
*/
|
||||
fun List<Rectangle>.alignToVertically(to: Rectangle, anchor: Double = 0.5): List<Rectangle> {
|
||||
val toy = to.position(0.0, anchor).y
|
||||
|
||||
return this.map {
|
||||
Rectangle.fromAnchor(Vector2(0.0, anchor), Vector2(it.x, toy), it.width, it.height)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Distributes the rectangles in the list horizontally within a specified bounding rectangle.
|
||||
*
|
||||
* Each rectangle is positioned at regular intervals, ensuring equal spacing between them.
|
||||
* The method maintains the height and y-coordinate of each rectangle, only adjusting their x-coordinates.
|
||||
*
|
||||
* @param within The bounding rectangle within which the rectangles are horizontally distributed.
|
||||
* Defaults to the bounding rectangle covering all rectangles in the list.
|
||||
* @return A new list of rectangles with updated positions that are evenly distributed horizontally.
|
||||
*/
|
||||
fun List<Rectangle>.distributeHorizontally(within: Rectangle = bounds): List<Rectangle> {
|
||||
val usedWidth = sumOf { it.width }
|
||||
val unusedWidth = within.width - usedWidth
|
||||
val betweenWidth = unusedWidth / (size - 1)
|
||||
|
||||
val distributed = mutableListOf<Rectangle>()
|
||||
|
||||
var x = within.x
|
||||
for (i in indices) {
|
||||
distributed.add(Rectangle(x, this[i].y, this[i].width, this[i].height))
|
||||
x += this[i].width + betweenWidth
|
||||
}
|
||||
return distributed
|
||||
}
|
||||
|
||||
/**
|
||||
* Distributes the rectangles in the list vertically within the given bounding rectangle.
|
||||
* The rectangles are spaced evenly, ensuring an equal distance between them, while
|
||||
* maintaining their original height and width.
|
||||
*
|
||||
* @param within The bounding rectangle within which the rectangles will be vertically
|
||||
* distributed. Defaults to the minimal bounding rectangle containing all rectangles in the list.
|
||||
* @return A new list of rectangles that are vertically distributed within the specified bounds.
|
||||
*/
|
||||
fun List<Rectangle>.distributeVertically(within: Rectangle = bounds): List<Rectangle> {
|
||||
val usedHeight = sumOf { it.height }
|
||||
val unusedHeight = within.height - usedHeight
|
||||
val betweenHeight = unusedHeight / (size - 1)
|
||||
|
||||
val distributed = mutableListOf<Rectangle>()
|
||||
|
||||
var y = within.y
|
||||
for (i in indices) {
|
||||
distributed.add(Rectangle(this[i].x, y, this[i].width, this[i].height))
|
||||
y += this[i].height + betweenHeight
|
||||
}
|
||||
return distributed
|
||||
}
|
||||
|
||||
/**
|
||||
* Distributes a list of rectangles horizontally within a given container rectangle,
|
||||
* maintaining their relative width proportions and adding an optional gutter
|
||||
* between them.
|
||||
*
|
||||
* @param within The container rectangle within which the rectangles will be distributed.
|
||||
* The default value is the bounding box of the current list of rectangles.
|
||||
* @param gutter The space (in units) to be added between adjacent rectangles. Default is 0.0.
|
||||
* @return A new list of rectangles distributed horizontally within the container rectangle.
|
||||
*/
|
||||
fun List<Rectangle>.fitHorizontally(within: Rectangle = bounds, gutter: Double = 0.0): List<Rectangle> {
|
||||
val gutterlessWidth = within.width - gutter * (size - 1)
|
||||
|
||||
val usedWidth = sumOf { it.width }
|
||||
val ratios = map { it.width / usedWidth }
|
||||
|
||||
var x = within.x
|
||||
val distributed = mutableListOf<Rectangle>()
|
||||
for (i in indices) {
|
||||
val w = ratios[i] * gutterlessWidth
|
||||
distributed.add(Rectangle(x, this[i].y, w, this[i].height))
|
||||
x += w + gutter
|
||||
}
|
||||
return distributed
|
||||
}
|
||||
|
||||
/**
|
||||
* Fits a list of rectangles within a given vertical rectangular area.
|
||||
* Each rectangle's height is adjusted proportionally based on its original height
|
||||
* relative to the total height of all rectangles in the list. The rectangles
|
||||
* are then distributed vertically, with an optional gutter spacing between them.
|
||||
*
|
||||
* @param within The bounding rectangle within which the list of rectangles should
|
||||
* fit. If not provided, the bounds of the current list of rectangles
|
||||
* will be used.
|
||||
* @param gutter The vertical spacing between the rectangles. Default value is 0.0.
|
||||
* @return A new list of rectangles that are proportionally resized and vertically
|
||||
* distributed within the specified bounding rectangle.
|
||||
*/
|
||||
fun List<Rectangle>.fitVertically(within: Rectangle = bounds, gutter: Double = 0.0): List<Rectangle> {
|
||||
val gutterlessHeight = within.height - gutter * (size - 1)
|
||||
|
||||
val usedHeight = sumOf { it.height }
|
||||
val ratios = map { it.height / usedHeight }
|
||||
|
||||
var y = within.y
|
||||
val distributed = mutableListOf<Rectangle>()
|
||||
for (i in indices) {
|
||||
val h = ratios[i] * gutterlessHeight
|
||||
distributed.add(Rectangle(this[i].x, y, this[i].width, h))
|
||||
y += h + gutter
|
||||
}
|
||||
return distributed
|
||||
}
|
||||
55
orx-shapes/src/commonMain/kotlin/primitives/RectangleTake.kt
Normal file
55
orx-shapes/src/commonMain/kotlin/primitives/RectangleTake.kt
Normal file
@@ -0,0 +1,55 @@
|
||||
package org.openrndr.extra.shapes.primitives
|
||||
|
||||
import org.openrndr.shape.Rectangle
|
||||
|
||||
/**
|
||||
* Creates a new [Rectangle] by modifying its dimensions and position based on the provided parameters.
|
||||
* The method adjusts the position and size of the rectangle depending on which of the optional parameters
|
||||
* are supplied. Any omitted parameters are calculated to maintain the rectangle's overall layout.
|
||||
*
|
||||
* Some quick recipes:
|
||||
* * Take a 40x50 rectangle from the center: `Rectangle(0.0, 0.0, 100.0, 100.0).take(width=40.0, height=50.0)`
|
||||
*
|
||||
*. * Take a 20x30 rectangle from the top left: `Rectangle(0.0, 0.0, 100.0, 100.0).take(left=0.0, top=0.0, width=20, height=30.0)`
|
||||
*
|
||||
* * Take a 10x30 rectangle from the bottom right: `Rectangle(0.0, 0.0, 100.0, 100.0).take(bottom=0.0, right=0.0, width=20, height=30.0)`
|
||||
*
|
||||
* @param left The amount to shift the rectangle's left edge inward. If null, the left edge remains unchanged.
|
||||
* @param top The amount to shift the rectangle's top edge inward. If null, the top edge remains unchanged.
|
||||
* @param right The amount to shift the rectangle's right edge inward. If null, the right edge remains unchanged.
|
||||
* @param bottom The amount to shift the rectangle's bottom edge inward. If null, the bottom edge remains unchanged.
|
||||
* @param width The new width for the rectangle. If null, the width is adjusted based on left and right shifts.
|
||||
* @param height The new height for the rectangle. If null, the height is adjusted based on top and bottom shifts.
|
||||
* @return A new [Rectangle] instance with the updated dimensions and position.
|
||||
*/
|
||||
fun Rectangle.take(
|
||||
left: Double? = null,
|
||||
top: Double? = null,
|
||||
right: Double? = null,
|
||||
bottom: Double? =null,
|
||||
width: Double? = null,
|
||||
height: Double? = null
|
||||
) :Rectangle {
|
||||
val newWidth = when {
|
||||
width != null -> width
|
||||
else -> this.width - (left ?: 0.0) - (right ?: 0.0)
|
||||
}
|
||||
|
||||
val newHeight = when {
|
||||
height != null -> height
|
||||
else -> this.height - (top ?: 0.0) - (bottom ?: 0.0)
|
||||
}
|
||||
|
||||
val newX = when {
|
||||
left != null -> corner.x + left
|
||||
right != null -> corner.x + this.width - right - newWidth
|
||||
else -> corner.x + (this.width - newWidth) / 2.0
|
||||
}
|
||||
|
||||
val newY = when {
|
||||
top != null -> corner.y + top
|
||||
bottom != null -> corner.y + this.height - bottom - newHeight
|
||||
else -> corner.y + (this.height - newHeight) / 2.0
|
||||
}
|
||||
return Rectangle(newX, newY, newWidth, newHeight)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package primitives
|
||||
|
||||
import org.openrndr.application
|
||||
import org.openrndr.extra.noise.shapes.uniformSub
|
||||
import org.openrndr.extra.shapes.primitives.alignToVertically
|
||||
import org.openrndr.extra.shapes.primitives.distributeHorizontally
|
||||
import org.openrndr.shape.Rectangle
|
||||
import kotlin.math.cos
|
||||
import kotlin.random.Random
|
||||
|
||||
/** This function creates an interactive graphical application that displays a dynamic visual composition
|
||||
* of rectangles, which are generated and manipulated based on time and random parameters. The application
|
||||
* follows these steps:
|
||||
*
|
||||
* 1. Initializes a random generator seeded with the elapsed seconds since the start of the program.
|
||||
* 2. Creates a sequence of rectangles using the `uniformSub` function to generate random sub-rectangles
|
||||
* within the bounding rectangle of the canvas.
|
||||
* 3. Distributes the generated rectangles horizontally within the canvas using the `distributeHorizontally` method.
|
||||
* 4. Aligns the rectangles vertically according to their position in relation to the bounding rectangle
|
||||
* and a dynamic anchor point derived from the cosine of elapsed time.
|
||||
* 5. Renders the rectangles on the canvas in the output window.
|
||||
*/
|
||||
fun main() {
|
||||
application {
|
||||
program {
|
||||
extend {
|
||||
val random = Random(seconds.toInt())
|
||||
val rs = (0 until 7).map { drawer.bounds.uniformSub(minWidth = 0.01, maxWidth = 0.1, random = random) }
|
||||
.distributeHorizontally(drawer.bounds)
|
||||
.alignToVertically(drawer.bounds, cos(seconds) * 0.5 + 0.5)
|
||||
|
||||
drawer.rectangles(rs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package primitives
|
||||
|
||||
import org.openrndr.application
|
||||
import org.openrndr.extra.noise.shapes.uniformSub
|
||||
import org.openrndr.extra.shapes.primitives.alignToHorizontally
|
||||
import org.openrndr.extra.shapes.primitives.alignToVertically
|
||||
import org.openrndr.extra.shapes.primitives.distributeHorizontally
|
||||
import org.openrndr.extra.shapes.primitives.fitHorizontally
|
||||
import org.openrndr.extra.shapes.primitives.fitVertically
|
||||
import org.openrndr.shape.Rectangle
|
||||
import kotlin.math.cos
|
||||
import kotlin.random.Random
|
||||
|
||||
fun main() = application {
|
||||
configure {
|
||||
width = 720
|
||||
height = 720
|
||||
}
|
||||
program {
|
||||
extend {
|
||||
val rs = (0 until 7).map { drawer.bounds.uniformSub(minWidth = 0.01, maxWidth = 0.1, random = Random(it)) }
|
||||
.fitHorizontally(drawer.bounds, gutter = 30.0 * mouse.position.y / height)
|
||||
.alignToVertically(drawer.bounds, cos(seconds) * 0.5 + 0.5)
|
||||
|
||||
drawer.rectangles(rs)
|
||||
|
||||
|
||||
val rsh = (0 until 7).map { drawer.bounds.uniformSub(minHeight = 0.01, maxHeight = 0.1, random = Random(it)) }
|
||||
.fitVertically(drawer.bounds, gutter = 30.0 * mouse.position.y / height)
|
||||
.alignToHorizontally(drawer.bounds, cos(seconds) * 0.5 + 0.5)
|
||||
|
||||
drawer.rectangles(rsh)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user