3D Visualization and Animation#
Visualization is a crucial part of evaluating any attempt of modelling a system. When dealing with a system with more than a couple of parameters, plots become difficult to interpret. By visualizing our system in 3D we can leverage our spatial and physics intuition of the world. This is the same intuition which tells us that when we throw a ball, its trajectory should be parabolic, or the intuition that it takes less effort stop a bike than a car. When it comes to mechanical systems, when something doesn’t look right, it’s indicative of a model built on fundamentally flawed assumptions. Visualizing your system through animation will help you weed out unintended behaviour from your model and will ultimately deepen your understanding of your model. This page will go into the basic principles of animating a system in 3D, as well as some common frameworks for visualization an animation.
Principles of Animation#
By Janke - Own work, CC BY-SA 2.5, Link
We distinguish between two approaches to animation: real-time animation and precomputed animation.
Real-time animation means that we simulate the system’s state in the same loop (as in programming) as the animation progress. For each new animation frame we integrate our system from the last frame to the current frame. This allows for interactive and responsive animations, but can be computationally demanding for complex systems or stiff systems that demand fine time resolutions.
Precomputed animation uses pre-generated trajectories (state over time). We compute the system’s trajectory before animating, and play back the trajectory upon animation. This approach is often more practical and efficient, as it allows us to decouple simulation from rendering. This allows us to reuse our simulation data, and is generally more stable for visualization purposes.
In both cases, we need a time series of the trajectory of our system. This trajectory could include positions, orientations and other relevant quantities and measures. Additionally, we need to specify the time step or frame duration between each state, as this determines how long each animation frame should last, consequently determining our frame rate (frames/second). Matching the animation frame rate with the simulation time step produces a reproduction of the system’s motion. However, in some cases adjusting the playback speed (faster or slower) can be both practically useful (i.e. a very slow or fast system).
A trajectory alone is not very useful to animate without a corresponding 3D (or 2D) model of the system. This model doesn’t need to be photorealistic nor physically accurate. It just needs to capture the essential geometry and behaviour of our system. For example, the details on the bike seat are of little interest when analysing the motion of a bike. Most animation libraries include primitive geometric shapes like cubes, spheres and cylinders, which are usually sufficient to represent the key components of a system. You’d be surprised how basic a model can be, as our brains are excellent at filling in the blanks when the motion is convincing. A simple rectangle moving like a boat can evoke the image of a vessel at sea given that the motion behaves as expected.
Our advice: start simple. Focus on correct motion first, as you undoubtedly will make some interesting mistakes with reference frames and defining coordinate systems. You can always improve the visual flair and fidelity later. The goal is by no means to produce a Pixar-quality animation, but rather to understand and effectively communicate how your system behaves.
In the following sections, we’ll present four animation tools of increasing complexity, from lightweight plotting using Matplotlib to state-of-the-art 3D engines using Blender.
Matplotlib.animation#
Note
Matplotlib is quite old and rigorous for simple data visualization. If you want to plot simple 2D data, we recommend checking out Plotly for a more modern plotting library. However, Plotly it is not designed for 3D animation.
Many of you are already familiar with the Matplotlib library. Inspired by Matlab’s plotting library, Matplotlib offers many useful easy-to-use functions for plotting and visualizing data. While library was made with data visualization in mind, it also supports a rudimentary framework for plotting and animating in 3D. In this section we’ll go through a basic example of a 3D pendulum with damping. For a more comprehensive introduction to Matplotlib.animation, take a look at the official documentation.
Example: Pendulum#
In this example we’ll simulate a 3D pendulum with damping terms.
Derivation
We consider a point mass \(m\) suspended by a rigid rod of length \(L\) from a fixed pivot. Using spherical coordinates, the generalized coordinates are
where \(\theta\) is the polar angle from the vertical and \(\phi\) is the azimuthal angle around the vertical axis.
The position of the mass is \(\mathbf{r} = L(\sin\theta\cos\phi, \sin\theta\sin\phi, -\cos\theta)\). Computing the kinetic energy \(T = \tfrac{1}{2}m|\dot{\mathbf{r}}|^2\) gives us
The potential energy, with reference at the pivot at the origin, is
Thus, the Lagrangian can be expressed as
The kinetic energy can be written in quadratic form
with inertia matrix
The Euler-Lagrange equations yield
where
The final equations of motion are
This compact form can be inverted to solve for \(\ddot{\theta}\) and \(\ddot{\phi}\) explicitly:
Adding damping terms \(-c\dot{\theta}\) and \(-c\dot{\phi}\), we finally have:
From Euler-Lagrange using vertical angle \(\theta\) and azimuth \(\phi\) as generalized coordinates we get the following EoM:
Note
This simplified damping model works for demonstration but isn’t fully realistic
We separate the second order ODE into four first order ODEs on the standard SciPy format. We also define our parameters.
import numpy as np
g, L = 9.81, 2.0
c = 0.5 # damping coeff
def spherical_pendulum_damped(t, y):
theta, theta_dot, phi, phi_dot = y
theta_ddot = (np.sin(theta)*np.cos(theta)*phi_dot**2
- (g/L)*np.sin(theta)
- c*theta_dot)
phi_ddot = (-2*np.cos(theta)/max(np.sin(theta), 1e-6))*theta_dot*phi_dot - c*phi_dot # Numerical stability
return [theta_dot, theta_ddot, phi_dot, phi_ddot]
Integrating with SciPy
from scipy.integrate import solve_ivp
# Initial conditions
theta0, theta_dot0 = 0.8, 0.0
phi0, phi_dot0 = 0.8, 2.0
# Integrate
t_span = (0, 10) # 10 seconds
t_eval = np.linspace(*t_span, 600) # Resolution
y0 = [theta0, theta_dot0, phi0, phi_dot0]
sol = solve_ivp(spherical_pendulum_damped, t_span, y0, t_eval=t_eval)
theta, phi = sol.y[0], sol.y[2] # Extract trajectory polar coordinates
Polar coordinates can be tricky to work with, so to make animation simpler we convert to Cartesian
x = L * np.sin(theta) * np.cos(phi)
y = L * np.sin(theta) * np.sin(phi)
z = -L * np.cos(theta)
Similar to an ordinary plot, we create a figure with plt.fig()
and add a 3D subplot. We can also set the bounds
of our plot explicitly by set_lim()
for all axes.
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.set_xlim(-L, L)
ax.set_ylim(-L, L)
ax.set_zlim(-L, 0.5*L)
ax.set_box_aspect([1, 1, 0.6]) # Not strictly necessary, but nice for web view

We’ve now created out plot. Now for the animation. The simplest way to animate using Matplotlib is to define a trajectory for every object in your system. We create one for the line (rod) and one for the bob (mass attached to rod). Like any other plot we can pick the formatting for each of the trajectories.
line, = ax.plot([], [], [], lw=2, c="black") # The comma "," unpacks the one-element list returned by ax.plot()
bob, = ax.plot([], [], [], "o", c="red", markersize=8)
We’ll now define the functions we need to animate the trajectory using FuncAnimation
. Apart from our figure and data,
the animation function needs an initialization function and an update function to animate our trajectory. The initialization
function sets up all the properties we need for the trajectories we animate, and gets called whenever we reset or restart our animation.
The update function takes in which time step we are on, and returns the updated trajectory of the objects we animate.
That’s it!
def init():
line.set_data([], [])
line.set_3d_properties([])
bob.set_data([], [])
bob.set_3d_properties([])
return line, bob
def update(i):
line.set_data([0, x[i]], [0, y[i]])
line.set_3d_properties([0, z[i]])
bob.set_data([x[i]], [y[i]])
bob.set_3d_properties([z[i]])
return line, bob
Then we just have to pass our figure, functions and remaining parameters.
The frame argument is the number of steps we integrated is self explanatory. The interval parameter controls milliseconds between frames, use 50-100ms for smooth playback.
Additionally, you can use blit=True
to make the animation more efficient, as it makes sure only
updated pixels are drawn for every frame. If the animation doesn’t appear when testing locally, try removing blit=True or use plt.show() instead of HTML display.
from matplotlib.animation import FuncAnimation
from IPython.display import HTML # HTML needed to display on webpage
ani = FuncAnimation(fig, update, frames=len(t_eval), init_func=init,
blit=True, interval=10)
plt.close(fig) # suppress static plot, animate using HTML instead
HTML(ani.to_jshtml()) # Display inline