Colormap Frame Animation

../../_images/anim_loop_cmap.png

Apparent movement of a static geometric object by changing the colormapping reference per frame. Transparent sections of the colormaps are used to ‘hide’ portions of the geometry per frame. Animation control:

Visualization Frame Value
Surface geometry constant
Surface position constant
Surface colormap colormap position per frame
Shading and highlighting constant
Axis coordinate constant

Following colormaps were used:

../../_images/cmap_loop_cmap.png
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

#.. Annimated colormap

# 1. Define functions to examine ....................................
wdth, subW = 0.4, 0.4
elev, azim = 90, -30
illum = s3d.rtv([1,-1,1],elev,azim)

def Torus(rez,width=wdth ) :
    def fold(rtz,width) :
        r,t,z = rtz
        Z = width*np.sin(z*np.pi)/2
        R = 1 + width*np.cos(z*np.pi)/2
        return R,t,Z
    surface = s3d.CylindricalSurface(rez,basetype='tri_s')
    surface.map_geom_from_op( lambda rtz : fold(rtz,width)    )
    return surface

def Trefoil(rtz) :
    r,t,z = rtz
    rw = 1-wdth/2
    X = rw*(np.sin(t)+2*np.sin(2*t))
    Y = rw*(np.cos(t)-2*np.cos(2*t))
    R0,T,Z = s3d.CylindricalSurface.coor_convert([X,Y,z])   # cylindrical coor
    R = R0 + r - rw
    Z = z - np.sin(3*t)
    return R,T,Z

def shift(rtz, os=0) :
    r,t,z = rtz
    T = np.mod(t+os,np.full(len(t),2*np.pi))
    return T

def ballCoor(rtp,os) :
    xyz = s3d.SphericalSurface.coor_convert(rtp,True)
    rtz = s3d.CylindricalSurface.coor_convert(xyz)
    rtz = np.array(rtz)
    rtz[1,:] = rtz[1,:] - os
    rtz = Trefoil(rtz)
    xyz = s3d.CylindricalSurface.coor_convert(rtz,True)
    return xyz

# 2. Setup and map surfaces .........................................
rez, offset = 6, 0

cyan2clear = cmu.hsv_cmap_gradient( [.5,1,1,1], [.5,1,1,0] )
clear = cmu.hsv_cmap_gradient( [.5,1,1,0], [.5,1,1,0] )
cmap1 = cmu.stitch_cmap( cyan2clear, clear,bndry=[0.075], name='cmap1' )
cmap2 = cmu.stitch_cmap('inferno_r', clear,bndry=[0.58], name='cmap2' )
wt_cy = cmu.hsv_cmap_gradient([.66,0,1],'cyan', name='wt_cy' )

surface1 = Torus(rez)
surf1 = copy.copy(surface1)
surface1.map_cmap_from_op( lambda rtz : shift(rtz,offset), cmap1)
surface1.map_geom_from_op( Trefoil )

surface2 = Torus(rez, subW*wdth)
surf2 = copy.copy(surface2)
surface2.map_cmap_from_op( lambda rtz : shift(rtz,offset), cmap2)
surface2.map_geom_from_op( Trefoil )

ball = s3d.SphericalSurface(5,'octa',color=[0,1,1,1]).domain(wdth/2)
ball.clip(lambda c : np.less_equal(c[1],[0]), usexyz=True )
ball.map_cmap_from_op(lambda c: s3d.SphericalSurface.coor_convert(c,True)[1], wt_cy)
ball.transform(translate=[1,0,0])
ball0 = copy.copy(ball)
ball0.map_geom_from_op(lambda c: ballCoor(c,offset), returnxyz=True)

ring = surface1 + surface2 + ball0
ring.shade(.2,direction=illum).hilite(.8,direction=illum)

# 3. Construct figure, add surfaces, and plot ......................
fig = plt.figure(figsize=plt.figaspect(1))
info = str(surface1)+'\n'+str(surface2) 
text = fig.text(0.02, 0.02, info, color='grey', fontsize='small' )
ax = plt.axes(projection='3d')
minmax = (-2,2)
ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
ax.set_axis_off()
ax.set_proj_type('ortho')
ax.set_facecolor( [ 0, 0, 0 ] )
ax.view_init(elev,azim)

ax.add_collection3d(ring)

fig.tight_layout(pad=0)

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

def init_fig():
    return ring,

def update_fig(frame):
    global ring
  
    ax.collections.remove(ring)

    offset = frame*(2*np.pi)

    surface1 = copy.copy(surf1)
    surface1.map_cmap_from_op( lambda rtz : shift(rtz,offset), cmap1)
    surface1.map_geom_from_op( Trefoil )
    surface2 = copy.copy(surf2)
    surface2.map_cmap_from_op( lambda rtz : shift(rtz,offset), cmap2)
    surface2.map_geom_from_op( Trefoil )
    ball0 = copy.copy(ball)
    ball0.map_geom_from_op(lambda c: ballCoor(c,offset), returnxyz=True)

    ring = surface1 + surface2 + ball0
    ring.shade(.2,direction=illum).hilite(.8,direction=illum)

    ax.add_collection3d(ring)

    return ring,

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

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()
print(">>>>>>>>>>>>>>> process completed")