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

#.. Annimated colormap

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

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

# 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), facecolor='k')
info = str(surface1)+'\n'+str(surface2) 
text = fig.text(0.02, 0.02, info, color='grey', fontsize='small' )
ax = plt.axes(projection='3d', facecolor='k', aspect='equal', proj_type='ortho')
minmax = (-2,2)
ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
ax.set_axis_off()
ax.view_init(elev,azim)

ax.add_collection3d(ring)

fig.tight_layout(pad=0)
#plt.show()

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


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

    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

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

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