Cylinder to Klein Bottle¶
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))