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 s3dlib.surface as s3d
import s3dlib.cmap_utilities as cmu

# 0. Define animation control parameters ............................

totalTime, f_domain, numFrames = 8, (0.0,1.0), 100   # time in seconds
frames=np.linspace(*f_domain, numFrames, endpoint=False)
interval = int(1000.0*totalTime/numFrames)         # milliseconds

# 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),facecolor='k')
ax = plt.axes(projection='3d',facecolor='k', aspect='equal')
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()
#plt.show()

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

def update_fig(frame):
    global surface
    surface.remove()

    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

anim = FuncAnimation(fig, update_fig, frames, interval=interval, repeat=True)
anim.save('klein.html',writer='html')

msg = "saved {} frames, values: [{:.3f} to {:.3f}] @ {} milliseconds/frane"
print(msg.format(numFrames,np.min(frames),np.max(frames),interval))