Skip to content

Instantly share code, notes, and snippets.

@ylegall
Created September 10, 2020 07:06
Show Gist options
  • Save ylegall/b6906e64ed58785ca5f6ebfe1a9196a7 to your computer and use it in GitHub Desktop.
Save ylegall/b6906e64ed58785ca5f6ebfe1a9196a7 to your computer and use it in GitHub Desktop.
package org.ygl.openrndr.demos
import org.openrndr.application
import org.openrndr.color.ColorRGBa
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.parameters.Description
import org.openrndr.extra.parameters.DoubleParameter
import org.openrndr.ffmpeg.VideoWriter
import org.openrndr.math.Vector2
import org.openrndr.math.smoothstep
import org.openrndr.shape.Circle
import org.openrndr.shape.ShapeContour
import org.openrndr.shape.compound
import org.ygl.fastnoise.FastNoise
import org.ygl.openrndr.utils.isolated
import org.ygl.openrndr.utils.isolatedWithTarget
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
import kotlin.random.Random
private const val WIDTH = 920
private const val HEIGHT = 920
private const val TOTAL_FRAMES = 360 * 2
private const val RECORDING = true
private const val DELAY_FRAMES = 360
fun main() = application {
configure {
width = WIDTH
height = HEIGHT
}
program {
var time = 0.0
val rng = Random(1)
val numPaths = 150
val circleRadius = 150.0
val flowSpeed = 1.0
val dt = 1.0
val noise = FastNoise()
val params = @Description("params") object {
@DoubleParameter("noise scale", 0.0, 10.0, precision = 2)
var noiseScale = 0.2
@DoubleParameter("noise radius", 0.0, 100.0)
var noiseRadius = 8.0
@DoubleParameter("noise mag X", 0.0, 100.0)
var noiseMagX = 4.0
@DoubleParameter("noise mag Y", 0.0, 100.0)
var noiseMagY = 7.0
@DoubleParameter("opacity", 0.0, 1.0)
var opacity = 0.1
}
val pointsPerPath = 200
//val numPoints = numPaths * pointsPerPath
val numPoints = (numPaths + 20) * pointsPerPath
val points = MutableList(numPoints) { Vector2.ZERO }
val image = renderTarget(width, height) { colorBuffer(); depthBuffer() }
val videoTarget = renderTarget(width, height) { colorBuffer() }
val videoWriter = VideoWriter.create()
.size(width, height)
.frameRate(60)
.output("video/EyeOfJupiter.mp4")
.start()
val border = compound {
difference {
shape(drawer.bounds.shape)
shape(drawer.bounds.offsetEdges(-30.0).shape)
}
}
fun getFieldVector2(x: Double, y: Double): Vector2 {
val xx = x * x
val yy = y * y
val aa = circleRadius * circleRadius
val denom = (xx + yy).pow(2)
val u = flowSpeed * (1 - aa * (xx - yy) / denom)
val v = -2 * flowSpeed * flowSpeed * aa * x * y / denom
//val wave = Vector2(0.0, 0.5 * sin(x / 32.0)) * smoothstep(circleRadius, circleRadius + 180.0, Vector2(x, y).length)
return Vector2(u, v)// + wave
}
val paths = List(numPaths) { i ->
var y = if (i % 2 == 0) {
-height / 2.0 + (i / (numPaths - 1.0)) * height
} else {
rng.nextDouble(height - 0.0) - height / 2.0
}
var x = -width / 2.0 - 2.0
val pathPoints = mutableListOf(Vector2(x, y))
while (x <= width/2.0) {
val (u, v) = getFieldVector2(x, y)
x += u * dt
y += v * dt
pathPoints.add(Vector2(x, y))
}
if (pathPoints.first().y > 0) {
ShapeContour.fromPoints(pathPoints.reversed(), false)
} else {
ShapeContour.fromPoints(pathPoints, false)
}
} + List(20) {
Circle(Vector2.ZERO, rng.nextDouble(10.0, circleRadius - 15.0)).contour
}
fun computePoints() {
val phase = 2 * PI * time
val globalOffset = 18 * sin(phase) * sin(phase)
//for (p in 0 until numPaths) {
for (p in 0 until paths.size) {
val path = paths[p]
for (i in 0 until pointsPerPath) {
val pointIndex = p * pointsPerPath + i
val pct = i / (pointsPerPath - 0.0)
val offsetTime = (time + pct) % 1.0
val basePosition = path.position(offsetTime)
val magY = params.noiseMagY // * (1 - cubicPulse(0.5, 0.3, pct))
val magX = params.noiseMagX // * (1 - cubicPulse(0.5, 0.3, pct))
noise.seed = pointIndex
val noiseOffsetY = magY * noise.getSimplex(
params.noiseScale * basePosition.x + params.noiseRadius * cos(phase),
params.noiseScale * basePosition.y + params.noiseRadius * sin(phase),
)
noise.seed = pointIndex * 719
val noiseOffsetX = magX * noise.getSimplex(
params.noiseScale * basePosition.x + params.noiseRadius * cos(phase),
params.noiseScale * basePosition.y + params.noiseRadius * sin(phase),
)
val offsetMag = smoothstep(circleRadius, circleRadius + 180.0, basePosition.length)
val localOffset = 4 * sin(6 * PI * (time - pct))
val offset = Vector2(0.0, (globalOffset + localOffset) * offsetMag)
var pos = basePosition + Vector2(noiseOffsetX, noiseOffsetY) + offset
// wrap Y
//pos = pos.copy(y = if (pos.y > height/2) pos.y - height else pos.y)
//pos = pos.copy(y = if (pos.y < -height/2) pos.y + height else pos.y)
points[pointIndex] = pos
}
}
}
fun renderPoints() {
drawer.isolatedWithTarget(image) {
fill = ColorRGBa.BLACK.opacify(params.opacity)
rectangle(bounds)
drawer.isolated {
translate(bounds.center)
stroke = null
fill = ColorRGBa.WHITE.opacify(0.6)
circles(points, 3.0)
}
// draw paths
//drawer.clear(ColorRGBa.BLACK)
//drawer.stroke = ColorRGBa.WHITE
//drawer.strokeWeight = 2.5
//drawer.contours(paths)
// draw border
fill = ColorRGBa.BLACK
stroke = null
shapes(border)
}
}
val composite = compose {
draw {
renderPoints()
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
vec2 pos = c_boundsPosition.xy - vec2(0.5, 0.5);
float dist = smoothstep(0.1, 0.4, length(pos) * 2);
x_fill.rgb *= mix(vec3(0.9, 0.11, 0.25), vec3(0.75, 0.65, 0.70), dist);
""".trimIndent()
}
drawer.image(image.colorBuffer(0))
}
}
extend {
time = ((frameCount - 1) % TOTAL_FRAMES) / TOTAL_FRAMES.toDouble()
computePoints()
if (RECORDING) {
drawer.isolatedWithTarget(videoTarget) {
composite.draw(drawer)
}
drawer.image(videoTarget.colorBuffer(0))
if (frameCount > DELAY_FRAMES) {
videoWriter.frame(videoTarget.colorBuffer(0))
if (frameCount > TOTAL_FRAMES + 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