Escher Knot 1ΒΆ

../../_images/anim_mceknot_1.png
import copy
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.colors as colors
from matplotlib.animation import FuncAnimation
import s3dlib.surface as s3d
import s3dlib.cmap_utilities as cmu

#.. influenced by M.C.Escher - Knots
#   https://mcescher.com/gallery/mathematical/

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

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

# 1. Define functions to examine ....................................

wdth = 0.75
twst, twstOff = 0.75, 0.25  # MC Escher controls
elev, azim = 90, -30
illum = s3d.rtv([1,-1,1],elev,azim)

def SquareRing(rez, width=wdth) :
    # .....................................................
    def fold(rtz,width,height) :
        r,t,z = rtz
        zeros = np.zeros(len(z))
        width_ar = np.full(len(z),width)
        # fold the cylinder into 4 parts..
        alpha = -2*width*z+width
        alpha = np.where( z <= 0.5, zeros ,     alpha )
        alpha = np.where( z <= 0.0, 2*width*z , alpha )
        alpha = np.where( z <= -.5, -width_ar , alpha )
        beta = height
        beta = np.where( z <= 0.5, 2*height*z,         beta)
        beta = np.where( z <= 0.0, zeros,              beta)
        beta = np.where( z <= -.5, -2*height*z-height, beta)
        R = r + alpha
        R = R + width/2
        Z = beta - height/2
        return R,t,Z
    # .....................................................
    surface = s3d.CylindricalSurface.grid(4,rez*90,'x')
    surface.map_geom_from_op( lambda rtz : fold(rtz,width,width) )
    surface.name = 'ring'
    return surface

def twistFunction(rtz, twists=twst, toff=twstOff) :
    r,t,z = rtz
    offset = toff*np.pi
    x0 = 1-r
    y0 = z
    r0, t0, temp = s3d.PolarSurface.coor_convert([x0,y0,np.zeros(len(z))])
    t0 = t0 - t*twists + offset
    x1, y1, temp = s3d.PolarSurface.coor_convert([r0,t0,np.zeros(len(z))],True)
    R = 1 - x1
    Z = y1
    return R,t,Z

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.PolarSurface.coor_convert([X,Y,z])
    R = R0 + r - rw
    Z = z - np.sin(3*t)
    return R,T,Z

# 2. Setup and map surfaces .........................................
rez = 5
ba = colors.rgb_to_hsv( [ 0.482, 0.333, 0.267 ] )
bb = colors.rgb_to_hsv( [ 0.855, 0.584, 0.427 ] )
cmap = cmu.hsv_cmap_gradient(ba,bb)

ring = SquareRing(rez)
orig_ring = copy.copy(ring)
ring.map_geom_from_op(twistFunction)
ring.map_geom_from_op( Trefoil )
ring.map_cmap_from_normals(cmap,direction=illum)
ring.shade(.2,direction=illum).hilite(.8,direction=illum)

# 3. Construct figure, add surfaces, and plot ......................
info, infocolor, facecolor = 'S3Dlib.org',  [0.898,0.843,0.800], [ 0.933, 0.902, 0.859 ]
fig = plt.figure(figsize=plt.figaspect(1), facecolor=facecolor )
text = fig.text(0.12, 0.05, info, color=infocolor, fontsize=45, fontweight='bold'  )
ax = plt.axes(projection='3d', aspect='equal', proj_type='ortho', facecolor=facecolor )
minmax = (-1.8,1.8)
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()

    ofst = 0.5*frame
    ring = copy.copy(orig_ring)
    ring.map_geom_from_op( lambda rtz : twistFunction(rtz,toff=ofst) )
    ring.map_geom_from_op( Trefoil )
    ring.map_cmap_from_normals(cmap,direction=illum)
    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('mceknot_1.html',writer='html')

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