Search code examples
pythonnumpymatplotlibanimationrotational-matrices

How to animate on matplotlib graph


I have an assignment where I need to project 3D cube in to 2D Cartesian plane, I've done plotting the vertex points but will still need to animate it somehow.

I have tried using FuncAnimation(), but no clue how it works. I am still new to python so go easy on me, thank you.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

A = np.array([-0.5,-0.5,-0.5])
B = np.array([0.5,-0.5,-0.5])
C = np.array([0.5,0.5,-0.5])
D = np.array([-0.5,0.5,-0.5])
E = np.array([-0.5,-0.5,0.5])
F = np.array([0.5,-0.5,0.5])
G = np.array([0.5,0.5,0.5])
H = np.array([-0.5,0.5,0.5])

load = np.array([A,B,C,D,E,F,G,H])
print(load)

fig = plt.figure()
ax = plt.axes(xlim =(-1,1),ylim =(-1,1))

#   Declared to allow for x and y axis only
projection = np.array([ [1,0,0], [0,1,0] ])
xdata,ydata = [],[]

plt.title("Render 3D Cube in 2D Space")
for x in load:
    for angle in range(360):
        rotationY = np.array([ [np.cos(angle),0,np.sin(angle)],
                        [0,1,0],
                        [-np.sin(angle),0,np.cos(angle)] ])
        rotationX = np.array([ [1,0,0],
                        [0,np.cos(angle),-np.sin(angle)],
                        [0,np.sin(angle),np.cos(angle)] ])

        #   Drawing each points
        rotated = np.dot(rotationY,x)
        rotated = np.dot(rotationX,rotated)
        projected2d = np.dot(projection,rotated)
        #projected2d = np.dot(projection,x) -With no rotations
    line = ax.plot(projected2d[0],projected2d[1],c = "blue",marker = "o")
def animate(i):
    x0,y0 = i
    xdata.append(x0)
    ydata.append(y0)
    line.set_data(xdata,ydata)
    return line
anim = FuncAnimation(fig,animate,interval =200,frames = 30)

plt.grid()
#plt.draw()
plt.show()

https://i.sstatic.net/2MigP.jpg


Solution

  • The FuncAnimation constructor takes a callable function (in your case animate) which gets the current frame number as an argument (here i) and updates the plot. This means, you should store all your intermediate points in an array (frames) and then later access those (you could also compute the projection on the fly, but I would not recommend this). The animation will then loop through the frames and apply the function to every frame.

    Also, you should use radians (angles from 0 to 2π) for your rotations.

    Here's a version that should work:

    # list of the angles in radians
    angles = np.linspace(0, 2*np.pi, 360)
    
    # storage of single frames - one value per point and angle.
    frames = np.zeros((len(load),len(angles),2))
    
    # loops through all points and angles to store for later usage.
    for i, x in enumerate(load):
        for j, angle in enumerate(angles):
            rotationY = np.array([[np.cos(angle),0,np.sin(angle)],
                            [0,1,0],
                            [-np.sin(angle),0,np.cos(angle)] ])
            rotationX = np.array([ [1,0,0],
                            [0,np.cos(angle),-np.sin(angle)],
                            [0,np.sin(angle),np.cos(angle)] ])
    
            rotated = np.dot(rotationY, x)
            rotated = np.dot(rotationX, rotated)
            projected2d = np.dot(projection, rotated)
    
            # store the point.
            frames[i,j,:] = projected2d
    
    # draws the initial point.
    line, = ax.plot(frames[:,0,0], frames[:,0,1], c="blue", marker="o", ls='')
    
    
    # defines what happens at frame 'i' - you want to update with the current
    # frame that we have stored before.
    def animate(i):
        line.set_data(frames[:,i,0], frames[:,i,1])
        return line # not really necessary, but optional for blit algorithm
    
    # the number of frames is the number of angles that we wanted.
    anim = FuncAnimation(fig, animate, interval=200, frames=len(angles))