[orx-shapes] Add contour blending
This commit is contained in:
35
orx-shapes/src/commonMain/kotlin/blend/ContourBlend.kt
Normal file
35
orx-shapes/src/commonMain/kotlin/blend/ContourBlend.kt
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
28
orx-shapes/src/commonMain/kotlin/blend/SegmentExtensions.kt
Normal file
28
orx-shapes/src/commonMain/kotlin/blend/SegmentExtensions.kt
Normal 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
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
37
orx-shapes/src/jvmDemo/kotlin/blend/DemoContourBlend01.kt
Normal file
37
orx-shapes/src/jvmDemo/kotlin/blend/DemoContourBlend01.kt
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
orx-shapes/src/jvmDemo/kotlin/blend/DemoContourBlend02.kt
Normal file
39
orx-shapes/src/jvmDemo/kotlin/blend/DemoContourBlend02.kt
Normal 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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user