To get started: Clone this repository and all its submodule dependencies using:
git clone --recursive https://github.com/dilevin/CSC417-a2-mass-spring-3d.git
Do not fork: Clicking "Fork" will create a public repository. If you'd like to use GitHub while you work on your assignment, then mirror this repo as a new private repository: https://stackoverflow.com/questions/10065526/github-how-to-make-a-fork-of-public-repository-private
The second assignment has two purposes is really where we start doing Computer Graphics. The main focus will be on pushing the ideas we explored in 1D (the previous assignment) to larger scale examples (bunnies!) in 3D.
On all platforms, we will assume you have installed cmake and a modern c++ compiler on Mac OS X¹, Linux², or Windows³.
We also assume that you have cloned this repository using the --recursive
flag (if not then issue git submodule update --init --recursive
).
Note: We only officially support these assignments on Ubuntu Linux 18.04 (the OS the teaching labs are running) and OSX 10.13 (the OS I use on my personal laptop). While they should work on other operating systems, we make no guarantees.
All grading of assignments is done on Linux 18.04
All assignments will have a similar directory and file layout:
README.md
CMakeLists.txt
main.cpp
include/
function1.h
function2.h
...
src/
function1.cpp
function2.cpp
...
data/
...
...
The README.md
file will describe the background, contents and tasks of the
assignment.
The CMakeLists.txt
file setups up the cmake build routine for this
assignment.
The main.cpp
file will include the headers in the include/
directory and
link to the functions compiled in the src/
directory. This file contains the
main
function that is executed when the program is run from the command line.
The include/
directory contains one file for each function that you will
implement as part of the assignment.
The src/
directory contains empty implementations of the functions
specified in the include/
directory. This is where you will implement the
parts of the assignment.
The data/
directory contains sample input data for your program. Keep in
mind you should create your own test data to verify your program as you write
it. It is not necessarily sufficient that your program only works on the given
sample data.
This and all following assignments will follow a typical cmake/make build routine. Starting in this directory, issue:
mkdir build
cd build
cmake ..
If you are using Mac or Linux, then issue:
make
Compiling the code in the above manner will yield working, but very slow executables. To run the code at full speed, you should compile it in release mode. Starting in the build directory, do the following:
cmake .. -DCMAKE_BUILD_TYPE=Release
Followed by:
make
Your code should now run significantly (sometimes as much as ten times) faster.
If you are using Windows, then running cmake ..
should have created a Visual Studio solution file
called a2-mass-spring-3d.sln
that you can open and build from there. Building the project will generate an .exe file.
Why don't you try this right now?
Once built, you can execute the assignment from inside the build/
using
./a2-mass-spring-3d
Its happening! We are finally doing some computer graphics ! You can tell because this assignment has a bunny in it (their name is Terry).
The goal of this assignment is to take what you've learned about 1D mass-spring systems and move it to 3D. While much of what we previously covered on variational mechanics and time integration still applies, you will soon see that implementations in 3D become much more complicated. Crucially, this assignment will tackle three concepts which will be important for the remainder of the course
- the potential energy of a 3D, non-zero rest length spring
- the notion of assembly. Assembly is a process which builds global linear algebra operations describing the motion of a whole object (bunny) from smaller operators describing the behaviour of individual pieces (in this case springs).
- The linearly implicit time integrator in 3D -- arguably the most popular time integration scheme in computer graphics.
Github does not render the math in this Markdown. Please look at README.html to see the equations in their proper form
The previous assignment had you implement a simple simulation of a coupled mass and spring in one-dimension. In this assignment we are going to level that up to three dimensions. Specifically, the assignment 2 code reads in a a 3D tetrahedral mesh (created using TetWild) and interprets all the edges of this tetrahedral mesh as springs. The user (you!!) will be able to poke and prod this bunny by clicking on vertices, in order to elicit, springy wobbly motion.
In 1D, we used the one-dimensional position of the mass particle as our generalized coordinate. In 3D we will use the obvious extension, which is that the generalized coordinate of each mass particle will be its 3D position. For the simple case of a spring connecting two masses we will define the
where subscripts are used to denote the particle each variable is associated with. Analogous with the 1D case,
The first important difference between the 1D and 3D cases, is the nature of our spring itself. in 1D we assumed the spring has zero rest-length -- in other words, it has zero potential energy when the distance between the 1D particle, and the wall to which it is affixed, is zero. This won't work for 3D shapes since it will cause them to collapse to a point (like a bunny in a blackhole). So we need to define a new potential energy, one which encourages the spring to maintain its original length.
We are going to define a way to measure the deformation of our spring, relative to its rest, or undeformed, length -- such a measure is called a strain measure.
Lets define
- It's
$0$ when the spring has length$l^0$ (rather than being$0$ when length is$0$ ) - It's invariant to rigid body transformations (i.e the strain isn't effected by translating or rotating the spring)
Now in keeping with the variational approach to mechanics, we can use this strain to define a potential energy which we can use to construct our equations of motion.
Let's define the potential energy for a single spring to be
where
Let's express
We can compute the vector
so that means the length of the spring can be written as
Thus
The kinetic energy,
Because
Now that we've seen the basics of how a single spring can be simulated, let's see what happens when we consider a whole system consisting of many springs!
Now let's consider the case where we have
Whereas in the previous section, our generalized coordinate was
We'll start with the kinetic energy of a mass-spring system because its the same formula as for a single spring ! However, because it will be convenient later on, we will replace the scalar mass
where
It's really the force computation that gets effected most by the presence of multiple springs, but its easiest to see this by starting with the potential energy.
The potential energy of our entire mass spring system, is the sum of the potential energy of all the springs
where
To make things a little easier conceptually, we are going to introduce another set of convenience matrices
An
So we can express the potential energy of the mass-spring system like this
and now we can take derivatives of this in order to compute, say the forces. Let's see what happens if we compute the gradient of this energy with respect to the generalized coordinates. Then we get
and because we know that the energy, in this case, only depends on the end points of the spring (
Let's unpack this a bit. First, you should be convinced that
Implementation Note: You could implement assembly by mirroring the formula above. In other words, instantiate a sparse
As we discussed in the previous assignment, making the right choice of time integrator is crucial for the stability, appearance and performance of a simulation. In this assignment we are going to implement one of the most famous time integrators in computer graphics, the Linearly-Implicit Time Integrator. This integrator is not fully-implicit like backward Euler, but it is efficient to compute and reasonably stable, thus it is a good initial place to start when integrating an elastic system like these mass-springs.
You should be able to convince yourself that, given the potential and kinetic energies above, that the backward Euler update for a 3D, mass-spring system is
\begin{align} M\dot{\mathbf{q}}^{t+1} & = M\dot{\mathbf{q}}^{t} + \Delta t \mathbf{f}\left(\mathbf{q}^{t+1}\right)\ \mathbf{q}^{t+1} & = \mathbf{q}^{t} + \Delta t \dot{\mathbf{q}}^{t+1} \end{align}.
Our big problem is estimating the effect of the forces at
We proceed by exploiting the fact that
\begin{align} M\dot{\mathbf{q}}^{t+1} &= M\dot{\mathbf{q}}^{t} + \Delta t \mathbf{f}\left(\mathbf{q}^t\right) + \Delta t^2\underbrace{\frac{\partial \mathbf{f}}{\partial \mathbf{q}}}_{\mbox{K}} \dot{\mathbf{q}}^{t+1} \ \mathbf{q}^{t+1} &= \mathbf{q}^{t} + \Delta t \dot{\mathbf{q}}^{t+1} \end{align}.
Importantly we see the appearance of the force gradient,
or
Ok, let's do one more rearrangement of these update equations to get them in their final, solvable form:
\begin{align} \left(M-\Delta t^2 K\right)\dot{\mathbf{q}}^{t+1} &= M\dot{\mathbf{q}}^{t} + \Delta t \mathbf{f}\left(\mathbf{q}^t\right) \ \mathbf{q}^{t+1} &= \mathbf{q}^{t} + \Delta t \dot{\mathbf{q}}^{t+1} \end{align}.
Now we see where linearly-implicit time integration gets its performance, it requires solving a single, sparse, symmetric linear system. Note: A fun game is to convince yourself that
In practice, once we have assembled (there's that word again)
Let's end this section by talking about the assembly of
Where
WARNING: to assemble the stiffness matrix replace
Again, you could implement this using a whole bunch of Eigen::Triplet
objects that can be used to directly initialize a sparse matrix.
Hint: a good test for any integrator is to test that your object doesn't move when no forces are exerted on it.
A Mass-Spring system acting purely under gravity is very boring, it just falls straight down (a rigid motion!). What's the point of doing all this work of we don't get a wiggly bunny out of it? In order to generate wiggliness, we need to, at a minimum, fix parts of our object to the environment. These are the boundary conditions for the physics simulation. Because we are fixing the position of particles, these are Dirichlet boundary conditions. There are a few ways to go about this, but we are going to take a projection approach -- we will express our generalized coordinates using a smaller set of variables.
Given that a fixed vertex, by definition, does not move, it makes sense to choose all other vertex positions as our subspace variables. We can now construct a linear subspace which rebuilds all the positions of our particles from this subspace --
Let's say we have
This is exactly what the assignment 2 simulator does. In practice, it is useful to implement
Implementations of nearly any task you're asked to implemented in this course can be found online. Do not copy these and avoid googling for code; instead, search the internet for explanations. Many topics have relevant wikipedia articles. Use these as references. Always remember to cite any references in your comments.
For this course most functions will be implemented in .cpp files. In this assignment the only exception is that time integrators are implemented in .h files. This is due to the use of lambda functions to pass force data to the time integration algorithms.
Compute the kinetic energy of a single mass particle.
Compute the gravitational potental energy of a single mass particle.
Compute the potential energy of a spring which connects two particles.
Compute the gradient of the gravitational potential energy for a single particle.
Compute the forces exerted by a single spring on its end-points.
Compute the per-spring hessian of the spring potential energy.
Compute the sparse, diagonal mass matrix that stores the mass of each particle in the mass-spring on its diagonal.
Iterate through each spring in the mesh, compute the per-spring forces and assemble the global force vector.
Iterate through each spring in the mesh, compute the per-spring stiffness matrix and assemble the global, sparse stiffness matrix. To do this, you should construct a list of Eigen::Triplet
objects and then set your sparse matrix using these triplets.
Compute the sparse projection matrix which projects out fixed point vertices.
Given a point on the screen (i.e a mouse position clicked by the user), find all vertices within a given radius. For this method, and this method alone you are allowed to use the igl::unproject
and igl::ray_mesh_intersect
functions. I have provided the appropriate ray shooting function for you to use in the code as well.
Implement the linearly implicit Euler time integrator.