Skip to content

Instantly share code, notes, and snippets.

@kimlaberinto
Created April 12, 2021 14:56
Show Gist options
  • Save kimlaberinto/f78def64acb453e4796c90c770c1228f to your computer and use it in GitHub Desktop.
Save kimlaberinto/f78def64acb453e4796c90c770c1228f to your computer and use it in GitHub Desktop.
Klein Bottle Math Politcal Compass meme animation (Julia)
## Source for math political compass klein bottle meme by @KimPLab on Twitter
## Tweet: https://twitter.com/KimPLab/status/1381621398636949511
## Inspired by the math political compass torus meme by @jessebett
## https://twitter.com/jessebett/status/1379162611414138885
## @jessebett source code notes:
## upcycled from torus knot fibration visualization:
## http://www.jessebett.com/TorusKnotFibration/torusknot.html
## Note:
## Code to plot the klein bottle mesh below is modified from @jessebett's code
using GLMakie
using Images
using Colors
using Animations
compass = load("mathcompass_crop_rot90CCW.jpg")
# make_translucent(rgb) = RGBA(rgb, 0.8)
# compass = map(make_translucent, compass)
# Klein Bottle parametrization from Wikipedia page
# https://en.wikipedia.org/wiki/Klein_bottle
# Image describes it as the parametrization provided by Robert Israel
# https://en.wikipedia.org/wiki/Klein_bottle#/media/File:Klein_bottle_translucent.png
function klein_bottle(u, v)
x = (-2/15)*cos(u)*(3*cos(v) - 30*sin(u)+90*(cos(u)^4)*sin(u)-60*(cos(u)^6)*sin(u)+5*cos(u)*cos(v)*sin(u))
y = (-1/15)*sin(u)*(3*cos(v)-3*(cos(u)^2)*cos(v) - 48*(cos(u)^4)*cos(v)+48*(cos(u)^6)*cos(v) - 60*sin(u) + 5*cos(u)*cos(v)*sin(u) - 5*(cos(u)^3)*cos(v)*sin(u) - 80*(cos(u)^5)*cos(v)*sin(u) + 80*(cos(u)^7)*cos(v)*sin(u))
z = (2/15)*(3 + 5*cos(u)*sin(u))*sin(v)
return (x,y,z)
end
# Plotting function modified from @jessebett original source code
# Additions include: using Osbervables/Nodes for animation and adapting it to a klein bottle
# https://twitter.com/jessebett/status/1379162611414138885
function plot_kleinbottle!(s, a, b, u_start, v_start)
u = @lift(range($u_start, $u_start + pi*$a, length=100))
v = @lift(range($v_start, $v_start + -2*pi*$b, length=100))
meshgrid = @lift(Iterators.product($u, $v))
xyz = @lift [klein_bottle(u, v) for (u, v) in $meshgrid]
tx = @lift [x for (x,y,z) in $xyz]
ty = @lift [y for (x,y,z) in $xyz]
tz = @lift [z for (x,y,z) in $xyz]
surf = surface!(s,tx,ty,tz, backlight = 1f0, ambient = Vec3f0(0.5*0.55))
surf.color = compass
# Note of code above: backlight = 1f0, ambient = Vec3f0(0.5*0.55)
# Suggestion is from @ffreyer on Julia Slack.
# Related to bug with `ambient` being applied twice
# Pull Request Fix: https://github.com/JuliaPlots/GLMakie.jl/pull/178
# If it is fixed by the time you run this code, feel free to omit `ambient = Vec3f0(0.5*0.55)`
return s
end
function make_animation()
ts = range(-0.6, 5, length=1400)
anim_a = Animation(0, 0.025,
sineio(prewait=0.1, postwait=0.1),
0.3, 0.2,
sineio(prewait=0.1, postwait=0.1),
1.0, 1.0,
4.0, 1.0,
sineio(prewait=0.1, postwait=0.1),
4.5, 0.040,
sineio(prewait=0.1, postwait=0.1),
5.0, 0.025)
anim_b = Animation(0, 0.025,
sineio(prewait=0.1, postwait=0.1),
0.3, 1.0,
sineio(prewait=0.1, postwait=0.1),
1.0, 1.0,
4.0, 1.0,
sineio(prewait=0.1, postwait=0.1),
4.5, 0.040,
sineio(prewait=0.1, postwait=0.1),
5.0, 0.025)
anim_u_start = Animation(1.0, 0.0,
linear(prewait=0.05, postwait = 0.0),
4.0, 1.0*pi*(-2.9),
sineio(prewait=0.05, postwait=0.0),
4.5, 1.0*pi*(-2.0),
sineio(prewait=0.0, postwait=0.0),
5.0, 1.0*pi*(-2.0))
anim_v_start = Animation(1.0, 0.0,
5.0, 0.0)
anim_theta = Animation(
-0.6, -0.75*pi,
sineio(prewait=0.0, postwait=0.0),
-0.2, -1/2*pi,
sineio(prewait=0.0, postwait=0.0),
0.0, 0+pi/2+pi*0.05,
linear(prewait=0.02, postwait=0.0),
4., 3*pi,
sineio(prewait=0.0, postwait=0.2),
5.0, 4*pi-0.75*pi)
anim_camera_h = Animation(0., 0.1,
sineio(prewait=0.4, postwait=0.1),
1.0, 0.4,
sineio(prewait=0.1, postwait=0.1),
2.0, 0.5,
4.0, 0.5,
sineio(prewait=0.2, postwait=0.2),
5.0, 0.1)
anim_image_shift = Animation(-1.0, 0.0,
4.0, 8.5,
5.0, 8.5)
scene = Scene(resolution=(1000, 1000), scale=(1,1,1), show_axis=false)
aNode = Node(0.0)
bNode = Node(0.0)
u_startNode = Node(0.0)
v_startNode = Node(0.0)
plot_kleinbottle!(scene, aNode, bNode, u_startNode, v_startNode)
cam3d!(scene)
for (i, t) in enumerate(ts)
a = at(anim_a, t) #anim_a(t) should also work
b = at(anim_b, t)
u_start = at(anim_u_start, t)
v_start = at(anim_v_start, t)
theta = at(anim_theta, t)
camera_h = at(anim_camera_h, t)
aNode[] = a
bNode[] = b
u_startNode[] = u_start
v_startNode[] = v_start
println(" $i / $(length(ts)) = $(round(i/length(ts); sigdigits=4)) \t t = $(round(t; sigdigits=3)) \t theta = $(round(theta; sigdigits=4))")
update_cam!(scene, [cos(theta), sin(theta), camera_h], [0., 0., 0.])
update!(scene)
filename = joinpath("anim/", lpad(i,5,"0") * ".png")
save(filename, scene)
end
end
make_animation()
# Uses ffmpeg to create animation (40 fps)
# (ffmpeg snippet I had lying around. Not sure how it works exactly... but gets the job done)
run(`ffmpeg -r:v 40 -i "anim/%05d.png" -codec:v libx264 -preset veryslow -pix_fmt yuv420p -crf 28 -an "anim.mp4" -y`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment