Skip to content

Instantly share code, notes, and snippets.

@ylegall
Created October 4, 2020 18:12
Show Gist options
  • Save ylegall/0216a384cd55e95d2214b089b5d74d71 to your computer and use it in GitHub Desktop.
Save ylegall/0216a384cd55e95d2214b089b5d74d71 to your computer and use it in GitHub Desktop.
expanding spiral mesh: code for https://www.instagram.com/p/CF5BIglnoEo
package org.ygl.openrndr.demos
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.CullTestPass
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.loadImage
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.gui.GUI
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.applyTo
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.transforms.transform
import org.ygl.openrndr.utils.isolatedWithTarget
import kotlin.math.PI
import kotlin.math.pow
import kotlin.math.sin
private const val WIDTH = 920
private const val HEIGHT = 920
private const val TOTAL_FRAMES = 360 * 2
private const val LOOPS = 2
private const val DELAY_FRAMES = 60
private const val RECORDING = true
fun main() = application {
configure {
width = WIDTH
height = HEIGHT
}
program {
var time = 0.0
val lengthSegments = 360
val radialSegments = 36
val texture = loadImage("data/images/voronoi-tile-2.png")
val textureScale = radialSegments
val camera = OrbitalCamera(
//eye = Vector3(0.0, 0.0, -2.0 * width),
eye = Vector3(x=-79.46, y=-215.56, z=-967.36),
lookAt = Vector3(x=0.0, y=0.0, z=0.0),
fov = 60.0,
far = 2000.0
)
class VertexInfo(
val pos: Vector3 = Vector3.ZERO,
val normal: Vector3 = Vector3.ZERO,
val texture: Vector2 = Vector2.ZERO
)
val vertices = Array(lengthSegments) { Array(radialSegments) { VertexInfo() } }
val mesh = vertexBuffer(vertexFormat {
position(3)
normal(3)
textureCoordinate(2)
}, lengthSegments * radialSegments * 6)
val params = @Description("my params") object {
@DoubleParameter("spiral curvature", 0.0, 8.0)
var spiralFactor = 4.0
@DoubleParameter("spiral rotation speed", 0.0, 10.0)
var rotationSpeed = 4.0
@DoubleParameter("z factor", 0.0, 4.0)
var zFactor = 0.385
@DoubleParameter("radius factor", 0.0, 10.0)
var radiusFactor = 1.1
@DoubleParameter("radius exponent", 0.0, 4.0, precision = 2)
var radiusExponent = 2.5
}
fun computeVertexInfo() {
val startAngle = 360 * params.rotationSpeed * (1 - time)
params.radiusExponent + 2.0 + 1 * sin(2 * PI * time)
for (s in 0 until lengthSegments) {
val spiralProgress = s / (lengthSegments - 0.0)
val offset = (spiralProgress + 0.0) % 1.0
//val theta = 360 * params.spiralFactor * sqrt(angleOffset)
val theta = startAngle + 360 * params.spiralFactor * offset
val radius = width * offset.pow(params.radiusExponent) * params.radiusFactor
val (cx, cy) = Vector2.fromPolar(Polar(theta, radius))
val z = radius * params.zFactor
val center = Vector3(cx, cy, -z)
val spoke = Vector3(cx, cy, 0.0).normalized * z
val axis = spoke.xy.perpendicular().xy0
val texCoordx = ((s % textureScale) + 1) / (textureScale + 0.0)
for (i in 0 until radialSegments) {
val angleProgress = i / (radialSegments - 1.0)
val angleOffset = (angleProgress + 2 * time) % 1.0
//val angleOffset = (4 + angleProgress - 3 * time) % 1.0
val angle = 360 * angleOffset
val rotatedSpoke = transform {
rotate(axis, angle)
} * spoke.xyz1
val pos = center + rotatedSpoke.xyz
val normal = rotatedSpoke.xyz.normalized
val textureCoord = Vector2(texCoordx, angleProgress)
vertices[s][i] = VertexInfo(pos, normal, textureCoord)
}
}
}
fun updateMesh() {
mesh.put {
for (s in 1 until lengthSegments) {
for (i in 1 until radialSegments) {
val prevTexCoord1 = vertices[s - 1][i - 1].texture.let { it.copy(x = it.x % 1.0) }
val prevTexCoord2 = vertices[s - 1][i - 0].texture.let { it.copy(x = it.x % 1.0) }
write(vertices[s - 1][i - 1].pos)
write(vertices[s - 1][i - 1].normal)
write(prevTexCoord1)
write(vertices[s - 0][i - 1].pos)
write(vertices[s - 0][i - 1].normal)
write(vertices[s - 0][i - 1].texture)
write(vertices[s - 0][i - 0].pos)
write(vertices[s - 0][i - 0].normal)
write(vertices[s - 0][i - 0].texture)
write(vertices[s - 1][i - 0].pos)
write(vertices[s - 1][i - 0].normal)
write(prevTexCoord2)
write(vertices[s - 1][i - 1].pos)
write(vertices[s - 1][i - 1].normal)
write(prevTexCoord1)
write(vertices[s - 0][i - 0].pos)
write(vertices[s - 0][i - 0].normal)
write(vertices[s - 0][i - 0].texture)
}
}
}
}
fun update() {
time = ((frameCount - 1) % TOTAL_FRAMES) / TOTAL_FRAMES.toDouble()
camera.update(deltaTime)
computeVertexInfo()
updateMesh()
}
val composite = compose {
draw {
camera.applyTo(drawer)
drawer.clear(ColorRGBa.BLACK)
drawer.stroke = ColorRGBa.WHITE
drawer.strokeWeight = 3.0
drawer.fill = ColorRGBa.PINK
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
float tcx = fract(va_texCoord0.x + p_time);
float tcy = va_texCoord0.y;
vec4 baseColor = texture(p_texture, vec2(tcx, tcy));
vec3 lightDir = vec3(0.0, 0.0, 1.0);
float shade = dot(lightDir, -va_normal);
shade = 0.25 * abs(shade) + 0.55 * smoothstep(-0.9, 0.9, shade) + 0.2 * smoothstep(0, 1, shade);
baseColor.rgb *= shade;
x_fill = baseColor;
""".trimIndent()
parameter("texture", texture)
parameter("time", 10 * (1 - time))
}
drawer.cullTestPass = CullTestPass.BACK
drawer.vertexBuffer(mesh, DrawPrimitive.TRIANGLES)
drawer.cullTestPass = CullTestPass.FRONT
drawer.vertexBuffer(mesh, DrawPrimitive.TRIANGLES)
}
post(FrameBlur())
}
val videoTarget = renderTarget(width, height) { colorBuffer() }
val videoWriter = VideoWriter.create()
.size(width, height)
.frameRate(60)
.output("video/SpiralSurface.mp4")
if (RECORDING) {
videoWriter.start()
} else {
// extend(OrbitalControls(camera))
extend(GUI()) {
add(params)
}
}
extend {
update()
if (RECORDING) {
drawer.isolatedWithTarget(videoTarget) {
composite.draw(this)
}
drawer.image(videoTarget.colorBuffer(0))
if (frameCount > DELAY_FRAMES) {
videoWriter.frame(videoTarget.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