Cylinder to Klein Bottle

../../_images/anim_klein.png

Based on the static plot from the Klein Bottle, Spherical to XYZ example. Animation control:

Visualization Frame Value
Surface geometry functional parameter per frame
Surface position functional parameter per frame
Surface color constant
Shading and highlighting fixed to the coordinate axis
Axis coordinate constant

This animation is a simple transform from cylinder to klein coordinates. Defining Xcyl as the initial cylindrical coordinate and Xkln as the final klein coordinate, the intermediate coordinate is just :

X(β) = Xcyl + β( Xkln - Xcyl )

where β is in the domain from 0 to 1. The difference between initial and final coordinates is the same during the animation. Define this difference as :

Δ = Xkln - Xcyl

so that the intermediate coordinate is simply a linear function of β, i.e.:

X(β) = Xcyl + βΔ

This equation is simply the highlighted line in the following code. Notice that the intermediate function does not use the input xyz argument, only the passed beta value is used. The coordinates change with each frame, not the face colors. To ‘slow-down’ the transitions near the cylinder and klein, a cosine function was used for β with respect to the frame.

import copy
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
import matplotlib.animation as animation
import s3dlib.surface as s3d
import s3dlib.cmap_utilities as cmu

# 1. Define function to examine ....................................

cylCoor, deltaCoor = None, None

def intermediate(rtz, beta) :  return cylCoor + beta*deltaCoor

def rtz_to_klein(rtz) :
    r,t,z = rtz
    u = np.pi*(z+1)/2
    return klein_UV(u,t)

def klein_UV(u,v) :
    cU, sU = np.cos(u), np.sin(u)
    cV, sV = np.cos(v), np.sin(v)
    x = -(2/15)*cU* \
        (  ( 3 )*cV + \
           ( -30 + 90*np.power(cU,4) - 60*np.power(cU,6) + 5*cU*cV )*sU \
        )
    y = -(1/15)*sU* \
        (  ( 3 - 3*np.power(cU,2) -48*np.power(cU,4) +48*np.power(cU,6) )*cV + \
           (-60 + ( 5*cU - 5*np.power(cU,3) - 80*np.power(cU,5) + 80*np.power(cU,7) )*cV  )*sU \
        )
    z = (2/15)*( 3 + 5*cU*sU )*sV
    return x,y,z

def frame_to_beta(f):
    # smooth at the ends of the transition
    beta = ( 0.5*(1-np.cos(2*np.pi*f)) )
    return beta

# 2. Setup and map surface .........................................
rez, dir = 6, s3d.rtv([1,1,1],30,-130)
cmap=cmu.alpha_cmap('hsv',0.75)

surface = s3d.CylindricalSurface(rez,linewidth=0 )

cylCoor = surface.vertices   # note: in xyz coordinates
surface.map_cmap_from_op(lambda rtz:rtz[2], cmap=cmap)
base = copy.copy(surface)
surface.map_geom_from_op( rtz_to_klein, returnxyz=True )
klnCoor = surface.vertices   # note: in xyz coordinates
deltaCoor = np.subtract(klnCoor,cylCoor)

frame = 0.75
beta = frame_to_beta(frame)
surface.map_geom_from_op( lambda xyz : intermediate(xyz, beta), returnxyz=True )

surface.transform(translate=[0,-2*beta,0])
surface.transform(s3d.eulerRot(0,-90*beta))
surface.shade(direction=dir ).hilite(direction=dir)

# 3. Construct figure, add surface plot ............................

fig = plt.figure(figsize=plt.figaspect(1))
ax = plt.axes(projection='3d',facecolor='k')
minmax = (-1.5,1.5)
ax.set(xlim=minmax, ylim=minmax, zlim=minmax)
ax.set_axis_off()
ax.view_init(elev=30, azim=-130)

ax.add_collection3d(surface)

fig.tight_layout()

# 4. Animation ......................................................

def init_fig():
    return surface,

def update_fig(frame):
    global surface

    ax.collections.remove(surface)

    beta = frame_to_beta(frame)
    surface = copy.copy(base)
    surface.map_geom_from_op( lambda xyz : intermediate(xyz, beta), returnxyz=True )
    surface.transform(translate=[0,-2*beta,0])
    surface.transform(s3d.eulerRot(0,-90*beta)) # rotate thru transition
    surface.shade(direction=dir ).hilite(direction=dir)

    ax.add_collection3d(surface)

    return surface,

ani = FuncAnimation(fig, update_fig, frames=np.linspace(0.0, 1.0, 101),
                    init_func=init_fig, blit=False, repeat=True, interval=50)

print(">>>>>>>>>>>>>>> Animation completed, file save proceeds")
#ani.save('ZZZ.mp4')                                   # use for movie file.
ani.save(None,writer=animation.FFMpegFileWriter())    # use for temp files.
print(">>>>>>>>>>>>>>> Save completed, screen display proceeds")

plt.show()