DD1354 Project Blog

Update 0 - In Unity There Is Strength

25th of February 2024

Welcome to my project blog in the DD1354 Models and Simulation course. My name is Azeez Daoud and I am here to invite you to venture with me in world of WAVES!. There we will encounter differential equations, numerical analysis, and a whole lot of springs and masses. Together we will build a system of coupled springs and masses, shake it (A LOT!) to produce two types of waves: transverse and longitudinal waves. The former propagates through the medium by displacing it perpendicularly in the direction of its motion. The latter displaces the medium in the direction of motion. This might seem quite abstract, so here is a figure showing the two types:

An image of transverse and longitudinal waves moving through a spring
A person shaking a spring producing transverse and longitudinal waves. Image from wikimedia.org.

How do we visualise that? Well, we need 3 ingredients:

  1. Physics
  2. Numerical Analysis
  3. Somewhere to do the simulation
Let us focus on the third point first by choosing a tool to do our simulation. I have decided to use The Unity Game Engine, because it is easy set up and use. The medium we are going to make waves propagate through will be a rope-like object modelled as a chain of springs and masses, like in the following figure.

An image of transverse and longitudinal waves moving through a spring
A coupled spring-mass system. Image from wikimedia.org.

This is called a coupled spring-mass system which we will abbreviate as CSMS, but more on that later. First we need to set up a few things in Unity:

  1. Springs
  2. Masses
  3. Walls
Masses and walls are fairly easy to set up. Masses will be just an cube with coordinates for position and velocity (we can actually omit the mass for a reason that will become clear soon). Walls will also be a cube stretched and compressed to look like plane. We will also create a point on that wall where we can attach things to it. The most difficult of the three is springs, because they can stretch and compress. How should we achieve that in Unity? Simply we will attach a script to a cylinder that stores two coordinates (Transforms in Unity-lingo) it can attach to. Based on the vector between those objects we will stretch, move and orient that cylinder based on that vector. In the inspector it looks like this

An image of of a coupled spring-mass system as a chain of three springs and two masses
Unity fields for setting the connection points of a spring.

Let's add a couple of cube that move up and down and attach their Transforms to the spring to test that our stretchiness works.

IT WORKS! What next? Well let us set up a scene where we can do our little experiment. Just two walls, a floor and some light adjustments should work (we'll maybe make it more beautiful later on).

An image of the scenery in the Unity Editor as two walls and a floor
The scenery of the experiment in Unity Editor
Alright the stage is set! In the next update we will get our hands dirty with some physics and numerical integrators!

Update 1 - Let There Be Waves

26th of February 2024

Welcome back! Today's update will be filled with mathematics and physics but the results will hopefully be satisfying! Let us begin by creating a CSMS. We create an object which we will use to generate the CSMS and handle the physics for it. We give it the number of masses and it subdivides the vector between the walls into small parts in which we will place masses. Between two masses (or wall and mass) we put a spring whose connection points are the masses. The results look like this:

An image of the unity
The CSMS generated in the Unity Editor

Alright, now it is time to add some forces on this CSMS. Take any mass at coordinates given by \(X_i = (x_i, y_i, z_i)^T\) and with mass \(m\) (to avoid confusion I will use 'mass' for the physical property, and 'body' for the object that is a mass) in the system it will experience a number of forces:

How do we combine all these forces? The answer is Newton's Second Law of Motion: \[F_\text{total} = m\ddot{X}_i\] or in other words: the total force on a body is equal to its mass times its acceleration. So, what we get is the following: \begin{align*} F_\text{total} &= F_\text{gravity} + F_\text{spring} + F_\text{damping} + F_\text{driving} = \\ m\ddot{X}_i &= -mg\hat{y} -k\Delta X_i - \gamma\dot{X}_i + F_0\cos(\omega_d t)\hat{y}= \\ \ddot{X}_i &= -g\hat{y} - \frac{k}{m}\Delta X_i - \frac{\gamma}{m}\dot{X}_i + \frac{F_0}{m}\cos(\omega_d t)\hat{y} \end{align*} If we make each body have the same mass then we can forget about the mass and give the parameters to the simulation as values normalised by the mass. So the equation reduces to: \[ \ddot{X}_i = -g\hat{y} - \omega^2\Delta X_i - \Gamma\dot{X}_i + \Lambda\cos(\omega_d t)\hat{y} \] (the reason the factor in front of \(\Delta X_i\) is \(\omega^2\) is due to solving the equation of motion of the harmonic oscillator analytically and getting \(\omega = \sqrt{\frac{k}{m}}\) as the natural frequency in the solution, so we will use that here too)

'Alright, alright,' you may say. 'A non-homogenous second order differential equation‽ How will we solve that?' Good question! We must delve in the world of Numerical Analysis and ask for the assistance of the numerical integrators. We could use Euler's Forward Method but that is not stable enough (yes, it sends everything to everywhere in seconds). How about the famous Runge-Kutta methods? And more specifically, Runge-Kutta 4? Yes, that works! I won't go to the details of how the Runge-Kutta methods works, so I refer to the Wikipedia page and the book Numerical Analysis by Timothy Sauer.

So, we start with that static CSMS, and for every body we do a single Runge-Kutta 4 iteration once each update (FixedUpdate in Unity with fixedDeltaTime set to 0.005s in order to make the calculations easy (fixed increments) and stable (small increments)). We do, however, only add the driving force \(F_\text{driving}\) so the first (leftmost) body, and we will see how the wave propagates. Let us see the results for the following parameters:

THERE! THERE! Do you see that? It is the TRANSVERSE WAVES 🥳! Alright alright, how about some gravity? Let us make

Let us also start with \(\Lambda = 0\) to let gravity do its work in the beginning. Thereafter, we will make \(\Lambda = 500\). We will also start with \(\omega_d = 1\) then increment it by 1 thrice and notice some interesting wave-pattern emerging.

Oh! The wave are standing still in their place rather than propagate forward, and every time we increment the integer values of the frequency the number of peaks (called antinodes) seem to increase by one... interesting 🤔. Anyway, whew! Our mathematical model worked pretty well! So as the famous (maybe not so famous) saying goes:

"The Math is Mathing! The Physics is Physicsing!"
- Anonymous Mathematics & Physics Enjoyer
For the next time, we will look into longitudinal waves by generalising our driving force to be in any of the \(\hat{x}, \hat{y}\) or \(\hat{z}\) direction.

Sources

Update 2 - An Slight Turn of Events

2nd of March 2024
There is nothing permanent except change
- Heraclitus

Well well well, there has been a slight turn of events. I presented my idea and prototypes to the course examiner, and the feedback I got was that in order to get a high grade (hopefully A) I must add a focus on a research-based topic. I am aiming for a high grade and have thus changed my plans slightly. I decided to drop the longitudinal wave visualisation part of the project and focus only on transverse waves. Instead, I will compare the model we discussed in the previous update, which I will henceforth call the Force Based Dynamics model abbreviated as FBD, with a model called Position Based Dynamics shorted to PBD. This model will be based on the paper Position Based Dynamics by Müller, M. et al. with some inspiration from the paper Fast Simulation of Mass-Spring Systems by Liu, T. et al. regarding damping. So in this update I will present how PBD will be implemented in this project and show some results using the same context of the videos in the previous update. So let us get started!

PBD uses two components: vertices and constraints. Without going deep into mathematics here, a vertex is like a body; it has a position \(X\), a velocity \(\dot{X}\), and mass \(m\), whilst a constraint is simple a rule we apply on the positions of a number of vertices. In our case the constraints will only be applied to pairs of vertices, and these constraints will be very similar to Hookean spring forces. They have the form \(C(X_i, X_j) = \kappa(\lVert X_i - X_j\rVert - R\)), where \(R\) is the rest length of the spring, and \(\kappa \in [0, 1]\) is a spring stiffness-like factor. In every step in the simulation we estimate the next position of the vertices, then apply the constraints to optimise the estimations. The way we change these estimations uses some multivariable calculus, but the final form of the change be seen in step 4 of the algorithm below. The optimised position estimations become the vertices' new positions. In summary here is the steps of the algorithm in very high language: after we initialise the \(n\) vertices, we do the following at each step in the simulation:

  1. Update the velocities \(\dot{X}_i\) of vertices \(i=1,2,\ldots,n\) using external forces (gravitational and driving forces)
  2. Damp the velocities using \(\dot{X}_i \gets \alpha \dot{X}_i\) for some \(\alpha \in [0, 1]\)
  3. Estimate the positions as \(\tilde{X}_i = X_i + \Delta t \dot{X}_i\)
  4. For vertices \(j\) and \(j+1\) add \(\Delta \tilde{X}_j = -\frac{\kappa}{2}C(\tilde{X}_j, \tilde{X}_{j+1})\frac{\tilde{X}_j - \tilde{X}_{j+1}}{\lVert\tilde{X}_j - \tilde{X}_{j+1}\rVert}\) to \(\tilde{X}_j\) and \(\Delta \tilde{X}_{j+1} = -\Delta \tilde{X}_j\) to \(\tilde{X}_{j+1}\), for \(j = 1,2,\ldots,n-1\)
  5. Update the vertices thusly: \(\dot{X}_i \gets (\tilde{X}_i - X_i)/\Delta t\) and \(X_i \gets \tilde{X_i}\)
If we are using immovable walls, then we treat them as vertices in step 4 but don't update their positions in step 5. You may have noticed that positions are the main focus of this algorithm. Forces are only used if they are external and are solely used to update the velocity. The rest of the algorithm only uses the positions to update the velocities and position forward in time. You may have also noticed that even though vertices by definition use mass, our algorithm does not. This is due to the fact that the if we assume all the masses are the same and, just like previously, the masses simply cancel out giving us a massless algorithm. Pretty cool!

Alright now, how does this look like in Unity? First let us clean things up. We will separate the FBD code into its own class, and create a new class for the PBD. In the CSMS let us make it possible to choose between FBD and PBD in order to make our comparison later. Now let us see the results! First up is the simulation of transverse waves without gravity.

It seems to work pretty well! It is a bit janky sometimes but overall it is very realistic! Let us add some gravity (add more spring stiffness, and driving force amplitude for the same reason as before). Here is what that looks like

Yeah... the start was a bit interesting but overall the simulation looked very realistic! Things are looking good for the PBD model! Awesome 😎!

In the next update, I will try to compare the two models and perhaps optimise the models further if possible. See you later!

Update 3 - Measure Twice... Cut Twice 😬

9th of March 2024

Hello again! Long time no see! I have been working on the report quite a lot recently and also gathered some data to compare the FBD and PBD models. I have also noticed a small mistake in my Runge-Kutta 4 iteration which significantly changed the motion of the CSMS you saw in the videos from the previous updates.

Let me start by describing the, very silly 😅, mistake. In the FBD, every iteration one should use the positions and velocities from the previous step to calculate the new positions and velocities then after the calculations are done the positions and velocities are updated. See the code below for the correct way to do this.

for b in bodies { do Runge-Kutta 4 step for b store new position and velocity in new_p, new_v } for b in bodies { b.position = new_p b.velocity = new_v }

Instead of updating at the end, I updated the positions and velocity immediately after calculation like so

for b in bodies { do Runge-Kutta 4 step for b store new position and velocity in new_p, new_v b.position = new_p b.velocity = new_v }

And since the next body uses the previous body for spring calculation the position of the previous body have already changed and the forces become unbalanced creating a more 'ropey' effect. The new and more correct FBD simulations look like this now.

Very less ropey and more stiff-springy, especially in the gravity version where I increased the \(\omega^2\) to avoid the spring breaking.

Now, on to do some comparisons. I did a brief study of the motion of both models in the report but I will not do that here because well, you can see the motion in the videos. I will instead show a small performance analysis I did. I ran each models with 50, 100, 250, 500, 750, 1000, 1500, 2000, and 3000 bodies/vertices then measured the time it took to call the step method. This was done classically by storing the time before the call then measuring the difference after the call finished. The results are in the plot below.

A plot of the time-cost per step per number bodies for the FBD and PBD models
A plot of the time-cost per step per number bodies for the FBD and PBD models

As you can see, PBD is a lot faster because it does one 'motion calculation' per step whereas the FBD with Runge-Kutta 4 does four. Therefore we see that the time differs by around a factor of four.

That is it for today. I will probably do another update with the final demonstration and some conclusive thoughts. Until next time 👋!

Update 4 - The Final Update

12th of March 2024

Greetings! This is the final update and for that I have recorded a small demonstration showing everything (I hope) about the two models. Enjoy!

(If you are wondering the music is Danse Macabre by Camille Saint-Saëns)

The models seemed to work very well! Did you also see how the Euler Forward integrator makes everything go CRAZY? Yeah, that is why Runge-Kutta 4 is better in this situation.

Regarding the project, it was honestly amazing! I learned a lot about physics and computer graphics. I have some regrets regarding the process but I learned a lot about planning and researching which will definitely come to use later in life! In summary, my advice is to start focusing on a topic early on rather than try to learn everything about a topic, especially when the timeframe of the project is very limited. It will reduce the risk of burnout!

Finally, I would like to thank the course-responsible and teaching assistants of DD1354 for the numerous feedback opportunities they provided! As a last remark, I will list some references which I found interesting regarding PBD and similar, since that model was especially useful in computer graphics.

Alas, this marks the end of our journey! Thank you for reading my blog! Farewell 🫡!