Created
September 14, 2020 16:18
-
-
Save ylegall/189196cb1e16b648b2c616aab58d2655 to your computer and use it in GitHub Desktop.
code for particle stream
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.ygl.openrndr.demos | |
import org.openrndr.application | |
import org.openrndr.color.ColorRGBa | |
import org.openrndr.draw.DrawPrimitive | |
import org.openrndr.draw.VertexElementType | |
import org.openrndr.draw.renderTarget | |
import org.openrndr.draw.shadeStyle | |
import org.openrndr.draw.vertexBuffer | |
import org.openrndr.draw.vertexFormat | |
import org.openrndr.extra.compositor.compose | |
import org.openrndr.extra.compositor.draw | |
import org.openrndr.extra.compositor.post | |
import org.openrndr.extra.fx.blur.FrameBlur | |
import org.openrndr.extra.fx.blur.GaussianBloom | |
import org.openrndr.extra.parameters.Description | |
import org.openrndr.extra.parameters.DoubleParameter | |
import org.openrndr.extras.camera.OrbitalCamera | |
import org.openrndr.extras.camera.OrbitalControls | |
import org.openrndr.extras.camera.isolated | |
import org.openrndr.extras.meshgenerators.sphereMesh | |
import org.openrndr.ffmpeg.VideoWriter | |
import org.openrndr.math.Polar | |
import org.openrndr.math.Vector2 | |
import org.openrndr.math.Vector3 | |
import org.openrndr.math.mix | |
import org.openrndr.math.smoothstep | |
import org.openrndr.math.transforms.transform | |
import org.openrndr.shape.Segment3D | |
import org.ygl.fastnoise.FastNoise | |
import org.ygl.openrndr.utils.BokehDepthBlur | |
import org.ygl.openrndr.utils.cubicPulse | |
import org.ygl.openrndr.utils.isolatedWithTarget | |
import kotlin.math.PI | |
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 DELAY_FRAMES = 60 | |
private const val LOOPS = 1 | |
private const val RECORDING = true | |
fun main() = application { | |
configure { | |
width = WIDTH | |
height = HEIGHT | |
} | |
program { | |
var time = 0.0 | |
val boxSize = 400.0 | |
val numPaths = 1200 | |
val particlesPerPath = 12 | |
val numParticles = numPaths * particlesPerPath | |
val rng = Random(2) | |
val noise = FastNoise() | |
val bloom = GaussianBloom() | |
val blur = BokehDepthBlur() | |
val eye = Vector3(x=0.0, y=0.01, z=200.0) | |
val camera = OrbitalCamera( | |
//eye = Vector3(0.0, 0.0, boxSize/2), | |
eye = eye, | |
lookAt = Vector3.ZERO, | |
far = 500.0 | |
) | |
val params = @Description("params") object { | |
@DoubleParameter("noise scale", 0.0, 1.0, precision = 3) | |
var noiseScale = 0.5 | |
@DoubleParameter("noise mag X", 0.0, 100.0) | |
var noiseMagX = 4.0 | |
@DoubleParameter("noise mag Z", 0.0, 100.0) | |
var noiseMagY = 4.0 | |
} | |
val pathNoiseScale = 0.47 | |
val pathNoiseMag = 2.9 | |
val particleGeometry = sphereMesh() | |
val transforms = vertexBuffer(vertexFormat { | |
attribute("transform", VertexElementType.MATRIX44_FLOAT32) | |
}, numParticles) | |
fun pathStartPoint(i: Int): Vector2 { | |
val pct = i / (numPaths - 0.0) | |
val theta = 7 * 360 * pct | |
val radius = boxSize * 0.75 * pct / 2 | |
return Vector2.fromPolar(Polar(theta, radius)) | |
} | |
val paths = List(numPaths) { i -> | |
var (x, y) = pathStartPoint(i) | |
var z = -boxSize/2 | |
val pathPoints = mutableListOf<Vector3>() | |
while (z < boxSize/2) { | |
val mag = pathNoiseMag * cubicPulse(0.0, boxSize/2.0, z) | |
noise.seed = 9 | |
val noiseX = mag * noise.getSimplex(pathNoiseScale * x, pathNoiseScale * y, pathNoiseScale * z) | |
noise.seed = 2 | |
val noiseY = mag * noise.getSimplex(pathNoiseScale * z, pathNoiseScale * y, pathNoiseScale * x) | |
val delta = -Vector2(x, y).normalized | |
val rotation = delta.perpendicular() * 0.7 | |
val centerForce = 0.3 * smoothstep(-boxSize/2, boxSize/2, z) | |
x += noiseX + rotation.x + delta.x * centerForce | |
y += noiseY + rotation.y + delta.y * centerForce | |
z += 1.0 | |
pathPoints.add(Vector3(x, y, z)) | |
} | |
pathPoints.zipWithNext().map { Segment3D(it.first, it.second) } | |
} | |
val pathOffsets = List(numPaths) { rng.nextDouble() } | |
fun getParticlePosition(path: List<Segment3D>, particleIndex: Int, t: Double): Vector3 { | |
val segmentIndex = (path.size * t).toInt() | |
val segment = path[segmentIndex] | |
var pos = mix(segment.start, segment.end, t) | |
noise.seed = particleIndex | |
val offset = Vector2( | |
params.noiseMagX * noise.getSimplex(params.noiseScale * pos.x, params.noiseScale * pos.y, params.noiseScale * pos.z), | |
params.noiseMagY * noise.getSimplex(params.noiseScale * pos.z, params.noiseScale * pos.x, params.noiseScale * pos.y) | |
) | |
return Vector3(pos.x + offset.x, pos.y + offset.y, pos.z) | |
} | |
fun computeParticlePositions() { | |
transforms.put { | |
for (p in 0 until numPaths) { | |
val path = paths[p] | |
val pathOffset = pathOffsets[p] | |
//val timeOffset = (pathOffsets[p] + time) % 1.0 | |
for (i in 0 until particlesPerPath) { | |
val pct = i / (particlesPerPath - 1.0) | |
val timeOffset = (pathOffset + time + pct) % 1.0 | |
val pos = getParticlePosition(path, p * particlesPerPath + i, timeOffset) | |
val tx = transform { | |
translate(pos) | |
scale(0.5 + 0.5 * sin(14 * PI * (time + pathOffset))) | |
} | |
write(tx) | |
} | |
} | |
} | |
} | |
val imageTarget = renderTarget(width, height) { colorBuffer(); depthBuffer() } | |
val composite = compose { | |
draw { | |
drawer.isolatedWithTarget(imageTarget) { | |
camera.isolated(drawer) { | |
drawer.clear(ColorRGBa.BLACK) | |
drawer.shadeStyle = shadeStyle { | |
vertexTransform = """ | |
//x_viewMatrix *= i_transform; | |
x_modelMatrix *= i_transform; | |
""".trimIndent() | |
} | |
drawer.fill = ColorRGBa.WHITE.opacify(0.5) | |
drawer.vertexBufferInstances( | |
listOf(particleGeometry), | |
listOf(transforms), | |
DrawPrimitive.TRIANGLES, | |
transforms.vertexCount | |
) | |
} | |
} | |
drawer.image(imageTarget.colorBuffer(0)) | |
} | |
post(blur) { | |
far = camera.far | |
focusPoint = 0.8 | |
focusScale = 1.0 | |
near = 1.0 | |
depthBuffer = imageTarget.depthBuffer!! | |
} | |
post(bloom) { | |
sigma = 0.1 | |
shape = 0.1 | |
} | |
post(FrameBlur()) { | |
blend = 0.5 | |
} | |
} | |
val videoTarget = renderTarget(width, height) { colorBuffer() } | |
val videoWriter = VideoWriter.create() | |
.size(width, height) | |
.frameRate(60) | |
.output("video/blizzard.mp4") | |
if (RECORDING) videoWriter.start() | |
extend(OrbitalControls(camera)) | |
extend { | |
time = ((frameCount - 1) % TOTAL_FRAMES) / TOTAL_FRAMES.toDouble() | |
camera.update(deltaTime) | |
computeParticlePositions() | |
if (frameCount % 500 == 0) { | |
println(Vector3.fromSpherical(camera.spherical)) | |
} | |
if (RECORDING) { | |
drawer.isolatedWithTarget(videoTarget) { | |
composite.draw(drawer) | |
} | |
drawer.image(videoTarget.colorBuffer(0)) | |
if (frameCount > DELAY_FRAMES) { | |
videoWriter.frame(videoTarget.colorBuffer(0)) | |
} | |
if (frameCount >= DELAY_FRAMES + (TOTAL_FRAMES * LOOPS)) { | |
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