[orx-shapes] Add contour blending

This commit is contained in:
Edwin Jakobs
2024-03-18 20:23:45 +01:00
parent d51d303050
commit bdc1dd3bec
6 changed files with 195 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
package org.openrndr.extra.shapes.blend
import org.openrndr.extra.shapes.rectify.RectifiedContour
import org.openrndr.extra.shapes.rectify.rectified
import org.openrndr.shape.ShapeContour
/**
* ContourBlend holds two rectified contours with an equal amount of segments
*/
class ContourBlend(val a: RectifiedContour, val b: RectifiedContour) {
fun mix(blendFunction: (Double) -> Double): ShapeContour {
return a.mix(b, blendFunction)
}
fun mix(blend: Double): ShapeContour {
return a.mix(b) { blend }
}
}
/**
* Create a [ContourBlend] for contours [a] and [b]
*
* Finding the pose that minimizes the error between [a] and [b] is not part of this function's work.
*
*/
fun ContourBlend(a: ShapeContour, b: ShapeContour): ContourBlend {
val ra = a.rectified()
val rb = b.rectified()
val sa = ra.splitForBlend(rb)
val sb = rb.splitForBlend(ra)
require(sa.contour.segments.size == sb.contour.segments.size) {
"preprocessing for contours failed to produce equal number of segments. ${sa.contour.segments.size}, ${sb.contour.segments.size}"
}
return ContourBlend(sa, sb)
}

View File

@@ -0,0 +1,30 @@
package org.openrndr.extra.shapes.blend
import org.openrndr.extra.shapes.rectify.RectifiedContour
import org.openrndr.extra.shapes.rectify.rectified
import org.openrndr.extra.shapes.utilities.fromContours
import org.openrndr.shape.ShapeContour
/**
* Split for blending with [other]
*/
fun RectifiedContour.splitForBlend(other: RectifiedContour): RectifiedContour {
val ts = (0 until other.contour.segments.size + 1).map { it.toDouble() / other.contour.segments.size }
val rts = ts.map { other.inverseRectify(it) }
return ShapeContour.fromContours(splitAt(rts), contour.closed && other.contour.closed).rectified()
}
fun RectifiedContour.mix(other: RectifiedContour, blendFunction: (Double) -> Double): ShapeContour {
val n = this.contour.segments.size.toDouble()
val segs = (this.contour.segments zip other.contour.segments).mapIndexed { index, it ->
val t0 = inverseRectify(index / n)
val t1 = inverseRectify((index + 1 / 3.0) / n)
val t2 = inverseRectify((index + 2 / 3.0) / n)
val t3 = inverseRectify((index + 1) / n)
it.first.mix(it.second, blendFunction(t0), blendFunction(t1), blendFunction(t2), blendFunction(t3))
}
return ShapeContour.fromSegments(segs, contour.closed && other.contour.closed)
}

View File

@@ -0,0 +1,28 @@
package org.openrndr.extra.shapes.blend
import org.openrndr.math.mix
import org.openrndr.shape.Segment
/**
* Cubic segment mix
* @param other the segment to mix with
* @param f0 the mix factor for the start point
* @param f1 the mix factor for the first control point
* @param f2 the mix factor for the second control point
* @param f3 the mix factor for the end point
*/
fun Segment.mix(other: Segment, f0: Double, f1: Double, f2: Double, f3: Double): Segment {
val ac = this.cubic
val bc = other.cubic
val acc = if (ac.corner) 1.0 else 0.0
val bcc = if (bc.corner) 1.0 else 0.0
return Segment(
ac.start.mix(bc.start, f0),
ac.control[0].mix(bc.control[0], f1),
ac.control[1].mix(bc.control[1], f2),
ac.end.mix(bc.end, f3),
corner = mix(acc, bcc, f0) >= 0.5
)
}

View File

@@ -0,0 +1,26 @@
package org.openrndr.extra.shapes.blend
import org.openrndr.shape.ShapeContour
/**
* Mix between two [ShapeContour] instances
*
* @param other other [ShapeContour] to mix with
* @param factor the blend factor between 0.0 and 1.0
* @see ContourBlend
*/
fun ShapeContour.mix(other: ShapeContour, factor: Double): ShapeContour {
return ContourBlend(this, other).mix(factor)
}
fun ShapeContour.mix(other: ShapeContour, factor: (Double) -> Double): ShapeContour {
return ContourBlend(this, other).mix(factor)
}
/**
* Create a [ContourBlend] instance for blending between this and [other]
* @see ContourBlend
*/
fun ShapeContour.blend(other: ShapeContour) : ContourBlend {
return ContourBlend(this, other)
}

View File

@@ -0,0 +1,37 @@
package blend
import org.openrndr.application
import org.openrndr.draw.isolated
import org.openrndr.extra.shapes.blend.blend
import org.openrndr.extra.shapes.primitives.grid
import org.openrndr.extra.shapes.primitives.regularStar
import org.openrndr.math.Vector2
import org.openrndr.shape.Circle
import kotlin.math.PI
import kotlin.math.cos
/**
* Demonstration of uniform contour blending
*/
fun main() {
application {
configure {
width = 720
height = 720
}
program {
val a = Circle(Vector2.ZERO, 90.0).contour
val b = regularStar(5, 30.0, 90.0, center = Vector2.ZERO, phase = 180.0)
val blend = a.blend(b)
extend {
drawer.bounds.grid(3, 3).flatten().forEachIndexed { index, it ->
drawer.isolated {
drawer.translate(it.center)
drawer.contour(blend.mix(cos(index * PI * 2.0 / 9.0 + seconds) * 0.5 + 0.5))
}
}
}
}
}
}

View File

@@ -0,0 +1,39 @@
package blend
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.isolated
import org.openrndr.extra.shapes.blend.blend
import org.openrndr.extra.shapes.primitives.grid
import org.openrndr.extra.shapes.primitives.regularStar
import org.openrndr.math.Vector2
import org.openrndr.shape.Circle
import kotlin.math.PI
import kotlin.math.cos
/**
* Demonstration of non-uniform contour blending
*/
fun main() {
application {
configure {
width = 720
height = 720
}
program {
val a = Circle(Vector2.ZERO, 90.0).contour
val b = regularStar(5, 30.0, 90.0, center = Vector2.ZERO, phase = 180.0)
val blend = a.blend(b)
extend {
drawer.clear(ColorRGBa.WHITE)
drawer.fill = ColorRGBa.BLACK
drawer.bounds.grid(3, 3).flatten().forEachIndexed { index, it ->
drawer.isolated {
drawer.translate(it.center)
drawer.contour(blend.mix { t -> cos(t * PI * 2.0 + index * PI * 2.0 / 9.0 + seconds) * 0.5 + 0.5 })
}
}
}
}
}
}