Boy to Roman Contour AnimationΒΆ

../../_images/anim_boy2roman_contours.png

Animation control:

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

Based on the static plots from the Boy Surface, Planar to XYZ and Spherical Coordinates to XYZ examples.

The Boy and Roman surfaces are homotopic. With a single parameters for the transformation, this animation was fairly straightforward. The only difficulty was parameterizing the scale and center of the surfaces with the homotopic transformation parameter, alpha. Spherical contours of the surfaces were used instead of the surface geometry, highlighting the symmetry of the Roman surface.

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

#.. Homotopy Boy to Roman Surface Animation
# https://mathworld.wolfram.com/BoySurface.html

# 1. Define functions to examine ....................................
f = 0.6                   # axis scaling
zoffset = 1.25*f +.25     # surfaces offset
B2R = 0.5                 # size scaling Boy to Roman
nCntrs = 18               # number of contours
elev, azim = 20, -40

def boy_roman(xyz,alp) :
    x,y,z = xyz
    u = 0.5*(x+1)*np.pi
    v = 0.5*(y+1)*np.pi
    sr2 = np.sqrt(2)
    cv2 = np.cos(v)*np.cos(v)
    denom = 2 - alp*sr2*np.sin(3*u)*np.sin(2*v)
    X = ( sr2*np.cos(2*u)*cv2 + np.cos(u)*np.sin(2*v) )/denom
    Y = ( sr2*np.sin(2*u)*cv2 - np.sin(u)*np.sin(2*v) )/denom
    Z = 3*cv2/denom
    reduc = (B2R-1)*alp +1
    X *=reduc
    Y *=reduc
    Z *=reduc
    return X,Y,Z-zoffset

def colormap_by_A(alp) :
    lowH, hiH = 0,0.33
    hue = (hiH-lowH)*alp + lowH
    return cmu.hsv_cmap_gradient( [hue,1.0,0.15], [hue,0.5,1],smooth=1.6)

def indicator_by_A(fig, A, vOld=None) :
    symbol, blank = r'$\blacktriangleright$', r'$\blacksquare$'
    horz, vBot, vRng = 0.8, 0.22, 0.56
    vert = vBot + vRng*A
    if vOld is not None: 
        fig.text(horz,vOld,blank, ha='right', va='center', fontsize='x-large', color='w')
    fig.text(horz,vert,symbol, ha='right', va='center', fontsize='large')
    return vert

def from_center(xyz,alp) :
    x,y,z = xyz
    ofset = (B2R-1)*alp + 1 - zoffset
    XYZ = np.array([x,y,z-ofset])
    r,t,p = s3d.SphericalSurface.coor_convert(XYZ)
    return r

def frame_2_alpha(frmIndex) :
    return( 1 + np.sin(2*np.pi*frmIndex)) / 2

# 2. Setup and map surfaces .........................................

rez = 5
cmap2=cmu.hsv_cmap_gradient( [0.0,0.75,1], [0.33,0.75,0.75],smooth=1.6)

frm = 0
alpha = frame_2_alpha(frm)
surface = s3d.PlanarSurface(rez,'oct1' )
surface.map_geom_from_op( lambda xyz: boy_roman(xyz,alpha) )
cmap = colormap_by_A(alpha)
conline = surface.contourLineSet(nCntrs,coor='s')
conline.map_cmap_from_op( lambda xyz: from_center(xyz,alpha),cmap)
conline.fade(0.1, elev,azim)

dummy = s3d.PlanarSurface(cmap=cmap2 )
dummy._bounds['vlim'] = [0,1]

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

fig = plt.figure(figsize=plt.figaspect(1))
fig.text(0.82,0.828,'Boy' , ha='center', va='top', fontsize='large',color='g',weight='bold')
fig.text(0.82,0.167,'Roman' , ha='center', va='bottom', fontsize='large',color='r',weight='bold')
ax = plt.axes(projection='3d')
ax.set_title("Spherical Contours")
cbar = plt.colorbar(dummy.cBar_ScalarMappable, ax=ax, ticks=np.linspace(0,1,5), shrink=0.6 )
cbar.set_label(r'Alpha, $\alpha$  ', rotation=270, labelpad = 15)
cbar.ax.tick_params(labelsize='small')
# ....
f = 0.6
minmax = (-f,f)
zminmax = (-f*1.25,f*1.25)
ax.set(xlim=minmax, ylim=minmax, zlim=zminmax )

ax.view_init(elev,azim)
ax.set_axis_off()
prevIndicator = indicator_by_A(fig, alpha)

ax.add_collection3d(conline)

fig.tight_layout()

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

def init_fig():
    return conline,

def update_fig(frame):
    global conline
    global prevIndicator
    ax.collections.remove(conline)

    alpha = frame_2_alpha(frame)

    surface = s3d.PlanarSurface(rez,'oct1' )
    surface.map_geom_from_op( lambda xyz: boy_roman(xyz,alpha) )
    cmap = colormap_by_A(alpha)
    conline = surface.contourLineSet(nCntrs,coor='s')
    conline.map_cmap_from_op( lambda xyz: from_center(xyz,alpha),cmap)
    conline.fade(0.1,elev,azim)

    prevIndicator = indicator_by_A(fig, alpha, prevIndicator)

    ax.add_collection3d(conline)

    return conline,

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()