# Rectangles and Extension functions

This morning I was playing around with kotlin’s Extensions, in view of a future episode in the series @abe and I have started (if you missed it, you can find it here ).
Here’s the code

``````import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extensions.Screenshots
import org.openrndr.extra.noise.Random
import org.openrndr.math.Vector2
import org.openrndr.shape.*
import kotlin.math.*

fun main() = application {
configure {
width = 1000
height = 1000
}

fun <T> Collection<T>.randomWeighted(w: Collection<Double>): Pair<T, Int> {
val items = this.toList()
var weights = w.toList()
if (items.size != weights.size){
throw Exception("Items size and weights size are different!")
}
val prefixSum = weights.sum()
weights = weights.map{it * 1.0/prefixSum}
val t = Random.double(0.0, 1.0)
var s = 0.0
for (i in weights.indices){
val it = weights[i]
s += it
if (t < s){
return Pair(items[i], i)
break
}
}
return Pair(items.last(), items.size - 1)
}

fun Rectangle.split(how:Int, p:Double = 0.3):List<Rectangle>{
val corner = this.corner
val w = this.width
val h = this.height
val t = if (p < 0.5) Random.double(p, 1.0 - p) else Random.double(1.0 - p, p)

val result = if (how == 1){
arrayListOf<Rectangle>(Rectangle(corner, t * w, h ), Rectangle(corner + Vector2(t * w, 0.0), (1.0 -t) * w, h))
}
else {
arrayListOf<Rectangle>(Rectangle(corner, w, t * h ), Rectangle(corner + Vector2(0.0, t * h), w, (1.0 -t) * h))
}
return result
}

fun Rectangle.hatch(dir:Vector2, w: Double = 5.0):List<Segment>{
val corner = if (dir.x > 0) this.corner else this.corner + Vector2.UNIT_X * this.width
val l = (this.center - corner).length * 2.0
val offset = Vector2(-dir.y, dir.x) * l * sign(dir.x)
val steps = (offset.length/w).toInt()
val segments = mutableListOf<Segment>()
repeat(steps){
val t = it * 1.0/(steps - 1)
val seg = Segment(corner - dir * l + offset * t , corner + dir * l + offset * t )
val inter = intersections(this.contour, seg.contour)
if ( inter.size == 2){
}
}
return segments.toList()
}

program {
var rects = mutableListOf<Rectangle>(drawer.bounds)
val palette = arrayListOf("#DB2B39", "#29335C", "#F3A712", "#F0CEA0", "#534D41").map { ColorRGBa.fromHex(it) }
repeat(50) {
val w = rects.map {
it.area
}
val (sh, id) = rects.randomWeighted(w)
val noise = if (Random.bool(0.2)) 1 else 0
val cut = if (sh.width > sh.height) 1 else 0
val sp = sh.split((cut + noise) % 2)
val newRects = mutableListOf<Rectangle>()
for (i in rects.indices) {
if (i == id) {
sp.forEach {
}
} else {
}
}
rects = newRects
}

val colors = rects.map {
palette.random()
}

val hatches = rects.map {
it.hatch(Vector2(0.5 * (Random.int(0, 2) * 2 - 1), -0.5).normalized, Random.double(5.0, 20.0))
}.flatten()

extend(Screenshots())

extend {
drawer.stroke = ColorRGBa.WHITE
drawer.strokeWeight = 0.5

rects.forEachIndexed { id, it ->
drawer.fill = colors[id]
drawer.rectangle(it)
}

drawer.segments(hatches)
drawer.fill = null
drawer.stroke = ColorRGBa.BLACK
drawer.rectangles(rects)
}
}
}
``````

The extensions are the following

• An extension of the Collection class to sample from a collection of type T according to (not necessarily normalized ) weights. I used this to randomly choose the rectangle to split according to area. (If I were more disciplined, I would have added a throwing exception in case of negative weights ). I don’t know if this is already implemented somewhere, but it was easy enough to show how cool extensions functions are.
• An extension of the Rectangle class to split the rectangle either horizontally or vertically, according to the longer side.
• An extension to perform some very basic hatchings from a given direction. It requires some more tweaking to make it into a working tool for any direction vector, unless you are into weird glitches, then welcome on the boat

If you run the code you shoud get something like this

Very nice!

I have similar methods in my collection and have played with hatching too for the pen plotter

My remix:

imports
``````import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.color.rgb
import org.openrndr.draw.rectangleBatch
import org.openrndr.extensions.Screenshots
import org.openrndr.extra.noise.Random
import org.openrndr.math.Polar
import org.openrndr.math.map
import org.openrndr.shape.Rectangle
import org.openrndr.shape.Segment
import org.openrndr.shape.Shape
import org.openrndr.shape.intersection
``````
``````fun main() = application {
configure {
width = 1000
height = 1000
}

/**
* Usage: listOf('a', 'b', 'c').pickWeighted(listOf(0.1, 0.6, 0.3)) ---> 'b' (60% chance)
*/
fun <E> Collection<E>.pickWeighted(weights: Collection<Double>): E {
if (size != weights.size) {
error("pickWeighted() requires two collections with the same number of elements")
}
val rnd = Random.double0(weights.sum())
var sum = 0.0
val index = weights.indexOfFirst { sum += it; sum > rnd }
}

fun Rectangle.split(t: Double = 0.5, horizontal: Boolean) = if (horizontal) {
Pair(
sub(0.0, 0.0, t, 1.0),
sub(t, 0.0, 1.0, 1.0)
)
} else {
Pair(
sub(0.0, 0.0, 1.0, t),
sub(0.0, t, 1.0, 1.0)
)
}

fun Rectangle.hatch(angle: Double, w: Double = 5.0): Shape {
val l = this.dimensions.length
val steps = (l / w).toInt()
val dir = Polar(angle).cartesian
val off = dir.perpendicular() * l

val pattern = Shape(List(steps) {
val t = it.toDouble().map(0.0, steps - 1.0, -l / 2, l / 2)
val p = this.center + dir * t
Segment(p - off, p + off).contour
})

return intersection(pattern, this.contour)
}

program {
val rects = mutableListOf(drawer.bounds)
repeat(50) {
val w = rects.map { it.area }
val (id, sh) = rects.withIndex().toList().pickWeighted(w)

val noise = if (Random.bool(0.2)) 1 else 0
val cut = if (sh.width > sh.height) 1 else 0
val horizontal = (cut + noise) % 2 == 1

val (firstHalf, secondHalf) = sh.split(Random.double(0.3, 0.7), horizontal)
rects[id] = firstHalf
}

val palette = arrayListOf("#DB2B39", "#29335C", "#F3A712", "#F0CEA0", "#534D41").map { rgb(it) }
val coloredRects = drawer.rectangleBatch {
rects.forEach { r ->
fill = palette.random()
rectangle(r)
}
}

val hatches = rects.map { r ->
r.hatch(listOf(-45.0, 45.0).random(), Random.double(5.0, 20.0))
}

extend(Screenshots())

extend {
drawer.stroke = ColorRGBa.WHITE
drawer.strokeWeight = 0.5

drawer.rectangles(coloredRects)
drawer.shapes(hatches)

drawer.fill = null
drawer.stroke = ColorRGBa.BLACK
drawer.rectangles(rects)
}
}
}
``````

Another interesting method to try is `split()` to split shapes with `LineSegment` or `ShapeContour`

1 Like

Very cool, I didn’t know one can avoid using `this` in extensions, very handy. I also like a lot `indexOfFirst`, it’s great that there’s such a native method.
Also, I always forget that `intersection` (no s) returns a Shape