Skip to content

Instantly share code, notes, and snippets.

@ylegall
Created November 8, 2020 20:21
Show Gist options
  • Save ylegall/fae29c54390fd1976115331c12947a7e to your computer and use it in GitHub Desktop.
Save ylegall/fae29c54390fd1976115331c12947a7e to your computer and use it in GitHub Desktop.
package org.ygl.openrndr.demos
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.color.mix
import org.openrndr.color.rgb
import org.openrndr.draw.BufferMultisample
import org.openrndr.draw.renderTarget
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.compositor.compose
import org.openrndr.extra.compositor.draw
import org.openrndr.extra.compositor.layer
import org.openrndr.extra.compositor.post
import org.openrndr.extra.fx.blur.FrameBlur
import org.openrndr.extra.fx.shadow.DropShadow
import org.openrndr.extra.gui.GUI
import org.openrndr.extra.parameters.Description
import org.openrndr.extra.parameters.DoubleParameter
import org.openrndr.ffmpeg.VideoWriter
import org.openrndr.math.Vector2
import org.openrndr.math.clamp
import org.openrndr.math.linearstep
import org.openrndr.math.mix
import org.openrndr.math.smoothstep
import org.openrndr.math.transforms.transform
import org.openrndr.shape.Circle
import org.openrndr.shape.ShapeContour
import org.ygl.kxa.ease.Ease
import org.ygl.openrndr.demos.util.GlamourBlur
import org.ygl.openrndr.utils.isolated
import org.ygl.openrndr.utils.isolatedWithTarget
import kotlin.math.PI
import kotlin.math.asin
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
private const val WIDTH = 920
private const val HEIGHT = 920
private const val TOTAL_FRAMES = 360 * 1
private const val LOOPS = 4
private const val DELAY_FRAMES = 60
private const val RECORDING = true
fun main() = application {
configure {
width = WIDTH
height = HEIGHT
multisample = WindowMultisample.SampleCount(7)
}
program {
var time = 0.0
val bgColor = rgb("383B56")
val fgColor1 = rgb("c4517a")
val fgColor2 = rgb("57CBB6")
val numPoints = 500
val rows = 6
val cols = 6
val colWidth = width.toDouble() / cols
val rowHeight = height.toDouble() / rows
val shrinkFactor = sqrt(2.0) / 2.0
val params = @Description("params") object {
@DoubleParameter("a", 0.01, 400.0, precision = 2)
var a = 100.0
@DoubleParameter("b", 0.0, 400.0, precision = 2)
var b = width / 4.0
@DoubleParameter("maxA", 100.0, 500.0, precision = 1)
var maxA = width/2.0
@DoubleParameter("maxB", 100.0, 400.0, precision = 1)
var maxB = 250.0
}
val maxCacheSize = 40
val shapeCache = object: LinkedHashMap<Double, List<ShapeContour>>() {
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Double, List<ShapeContour>>?): Boolean {
return this.size > maxCacheSize
}
}
fun signedSqrt(x: Double): Double {
return if (x < 0) {
-sqrt(-x)
} else {
sqrt(x)
}
}
fun combineAndSortPoints(p1: List<Vector2>, p2: List<Vector2> = emptyList()): List<Vector2> {
val combined = p1 + p2
val (top, bottom) = combined.partition { it.y < 0 }
return (top.sortedBy { it.x } + bottom.sortedBy { -it.x }).filter { !it.x.isNaN() && !it.y.isNaN() }
}
val rightCirclePoints = Circle(width/2.0, 0.0, params.b * shrinkFactor).contour.equidistantPositions(2 * numPoints).let { combineAndSortPoints(it) }
val leftCirclePoints = rightCirclePoints.map { Vector2(-it.x, -it.y) }
fun mixPoints(p1: List<Vector2>, p2: List<Vector2>, mixFactor: Double): List<Vector2> {
return p1.mapIndexed { idx, point -> mix(point, p2[idx], mixFactor) }
}
fun makeShapes(a: Double, b: Double): List<ShapeContour> {
val points1 = MutableList(numPoints) { Vector2.ZERO }
val points2 = MutableList(numPoints) { Vector2.ZERO }
for (i in 0 until numPoints) {
val pct = i / (numPoints - 0.0)
val e = (b / a)
val ee = e * e
val angle = if (a < b) {
pct * 2 * PI
} else {
val t0 = 0.5 * asin(ee % (2 * PI))
mix(-t0, t0, pct)
}
val eeee = ee * ee
val radicand = sqrt(eeee - sin(2 * angle).let { it * it })
val radicand1 = a * signedSqrt(cos(2 * angle) + radicand)
val radicand2 = a * signedSqrt(cos(2 * angle) - radicand)
val x1 = radicand1 * cos(angle)
val x2 = radicand2 * cos(angle)
val y1 = radicand1 * sin(angle)
val y2 = radicand2 * sin(angle)
points1[i] = Vector2(x1, y1)
points2[i] = Vector2(x2, y2)
}
return if (a < b) {
listOf(
ShapeContour.fromPoints(combineAndSortPoints(points1), true)
)
} else {
val rightPoints = combineAndSortPoints(points1, points2)
val leftPoints = rightPoints.map { Vector2(-it.x, it.y) }.reversed()
val mixFactor = linearstep(200.0, params.maxA, a)
val mixedRight = mixPoints(rightPoints, rightCirclePoints, mixFactor)
val mixedLeft = mixPoints(leftPoints, leftCirclePoints, mixFactor)
listOf(
ShapeContour.fromPoints(mixedRight, true),
ShapeContour.fromPoints(mixedLeft, true),
)
}
}
fun getShapes(t: Double): List<ShapeContour> {
val shapeList = shapeCache[t]
return if (shapeList == null) {
val a = 0.01 + params.maxA * Ease.QUAD_INOUT(t)
val b = mix(params.b, params.maxB, Ease.QUAD_INOUT(t))
return makeShapes(a, b).also {
shapeCache[t] = it
}
} else {
shapeList
}
}
fun update() {
time = ((frameCount - 1) % TOTAL_FRAMES) / TOTAL_FRAMES.toDouble()
//val a = 0.01 + params.maxA * Ease.CUBE_OUT(time)
//val b = mix(params.b, params.maxB, time)
//shapes = makeShapes(a, b)
//shapes = makeShapes(params.a, params.b)
}
val composite = compose {
layer {
draw {
drawer.clear(bgColor)
}
}
layer {
draw {
drawer.translate(drawer.bounds.center)
drawer.clear(ColorRGBa.TRANSPARENT)
drawer.scale(1.0 + (sqrt(2.0) - 1) * time)
drawer.rotate(-45.0 * time)
for (i in -1 .. rows) {
for (j in -1 .. cols) {
drawer.isolated {
val dist = Vector2(j * 1.0, i * 1.0).distanceTo(Vector2(2.5, 2.5))
val delay = 0.2 * dist / 6.0
val shapeList = getShapes(clamp(time - delay, 0.0, 1.0))
drawer.isolated {
translate(colWidth/2.0 - width/2.0, rowHeight/2.0 - height/2.0)
translate(j * colWidth, i * rowHeight)
scale(1.0/cols, 1.0/rows)
if (((i % 2) + j) % 2 == 0) {
fill = fgColor1
} else {
fill = fgColor2
rotate(90.0)
}
stroke = null
contours(shapeList)
}
}
}
}
//drawer.fill = fgColor1
//drawer.stroke = null
//drawer.contours(shapes)
}
post(DropShadow()) {
xShift = -7.0
yShift = -7.0
gain = 0.7
}
}
if (RECORDING) {
post(FrameBlur())
}
}
val videoTarget1 = renderTarget(width, height, multisample = BufferMultisample.SampleCount(9)) { colorBuffer() }
val videoTarget2 = renderTarget(width, height) { colorBuffer() }
val videoWriter = VideoWriter.create()
.size(width, height)
.frameRate(60)
.output("video/mitosis.mp4")
if (RECORDING) {
videoWriter.start()
} else {
//extend(GUI()) {
// add(params)
//}
}
extend {
update()
if (RECORDING) {
drawer.isolatedWithTarget(videoTarget1) {
composite.draw(this)
}
videoTarget1.colorBuffer(0).copyTo(videoTarget2.colorBuffer(0))
drawer.image(videoTarget2.colorBuffer(0))
if (frameCount > DELAY_FRAMES) {
videoWriter.frame(videoTarget2.colorBuffer(0))
}
if (frameCount >= TOTAL_FRAMES * LOOPS + DELAY_FRAMES) {
videoWriter.stop()
application.exit()
}
} else {
composite.draw(drawer)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment