Monday, May 20, 2013

Simulation Class: Midpoint Method and Velocity Verlet

Higher-Order Difference Equations

In our previous class, we were able to fix the instability of our spring system by switching from the never-correct Forward Euler time step to the Euler-Cromer time step, which made our simulation stable, but did not achieve an accurate result over time when compared to the exact answer.

The solutions we've explored so far are primarily explicit time integrations, in that we're only using values from previous points in time to solve for points at future points in time. When we consider, later, implicit time integration, where values at the current time are solved self-referentially, things will get a lot more complex. So, we'd like to continue exploring techniques that are fundamentally explicit in nature.

The approach that all of the higher-order explicit methods will take will be to use lower-order approximations to compute temporary values, which will be used to compute better derivatives that will then be used to integrate forward. The first, and most common higher-order method is called "The Midpoint Method".

The Midpoint Method

Let's approximate the velocity at half-way through our time step, rather than all the way to the new time, just using the forward finite-difference approximation we used with Forward Euler:
\( a( t ) = f( t, x( t ) ) \\ x( t + \frac{\Delta t}{2} ) \approx x( t ) + \frac{\Delta t}{2} v( t ) \\ v( t + \Delta t ) \approx v( t ) + \Delta t a( t ) \\ x( t + \Delta t ) \approx x( t + \frac{\Delta t}{2} ) + \frac{\Delta t}{2} v( t + \frac{\Delta t}{2} ) \)
Now we've got one extra step of computation at each time step, computing a midpoint position. This isn't a huge improvement over what we had before, and here's what it looks like in code:
// Time Step function.
void TimeStep( float i_dt )
{
    // Compute acceleration from current position.
    float A = ( -Stiffness / BobMass ) * State[StatePositionX];

    // Update position half-way.
    State[StatePositionX] += ( i_dt/2.0 ) * State[StateVelocityX];

    // Update velocity based on acceleration.
    State[StateVelocityX] += i_dt * A;
    
    // Update position half-way.
    State[StatePositionX] += ( i_dt/2.0 ) * State[StateVelocityX];

    // Update current time.
    State[StateCurrentTime] += i_dt;
}

And here that is:

So really we've actually made things worse with this simple higher-order approximation. An alternative midpoint-method approach is called Velocity Verlet, and it looks like this:

\( a( t ) = f( t, x( t ) ) \\ v( t + \frac{\Delta t}{2} ) \approx v(t) + \frac{\Delta t}{2} a( t ) \\ x( t + \Delta t ) \approx x( t ) + \Delta t v( t + \frac{\Delta t}{2} ) \\ a( t + \Delta t ) = f( t+\Delta t, x( t + \Delta t ) \\ v( t + \Delta t ) \approx v( t + \frac{\Delta t}{2} ) + \frac{\Delta t}{2} a( t + \Delta t ) \)
And here's what that looks like in code:


// Time Step function.
void TimeStep( float i_dt )
{
    // Compute acceleration from current position.
    float A = ( -Stiffness / BobMass ) * State[StatePositionX];

    // Update velocity half-way.
    State[StateVelocityX] += ( i_dt/2.0 ) * A;

    // Update position.
    State[StatePositionX] += i_dt * State[StateVelocityX];

    // Re-compute acceleration.
    A = ( -Stiffness / BobMass ) * State[StatePositionX];
    
    // Update velocity half-way.
    State[StateVelocityX] += ( i_dt/2.0 ) * A;

    // Update current time.
    State[StateCurrentTime] += i_dt;
}

And here it is running:

Not bad! Now we're cooking with gas! Still not perfect, though. The next class will explore the fourth-order Runge Kutta approach, and begin to consider implicit methods.

Download

All of the files associated with this class are available via a public github repository, found here: https://github.com/blackencino/SimClass

Simulation Class: The Euler-Cromer Time Step

Forward Euler Problems

In the previous class, we observed that a Forward Euler time step quickly produced an unusable solution to our simple spring system. This illustration from the Wikipedia entry on Forward Euler illustrates how the method works, and also why its solutions drift away from the correct result.

In this illustration, you can see that at each point, the slope of the function is used to construct a line segment that estimates the position of the next point. The error in each estimate is accumulated with each subsequent estimate, and the solution veers off course. In order to study these systems better, we first need some debug tools.

Adding Debug Tools to our Spring Simulation

The Forward Euler Time Step that we added to the Spring Simulation in the previous class produced an oscillatory motion, but did so in a way that produced an unstable result, with the mass oscillating wildly off screen in a short period of time.

The instability shown by our simple spring simulation is a constant problem for most simulations. It's easy to see the problem in this simple example (which is why we're using it!), but the problem affects large-scale fluid and cloth sims, rigid body sims, and so on. Strangely, despite the general knowledge that Forward Euler is not a good time integration tool, it is still used in many commercial tools, including Maya and Houdini.

In order to analyze our improvements, we first need a measuring stick. To quote my good friend Adam Shand, "You can't improve what you can't measure.". We chose the simple spring because it did have an exact answer. Therefore, we'll add some code to our drawing routine that draws the right answer just above the simulated spring. Here's that code - note that this uses the expression for the explicit position of the spring that we established in the previous class. We'll also add some text to the top of the drawn image to remind us which time integration we're using.


void DrawState()
{
    // Compute end of arm.
    float SpringEndX = PixelsPerMeter * State[StatePositionX];

    // Compute the CORRECT position.
    float sqrtKoverM = sqrt( Stiffness / BobMass );
    float x0 = InitState[StatePositionX];
    float v0 = InitState[StateVelocityX];
    float t = State[StateCurrentTime];
    float CorrectPositionX = ( x0 * cos( sqrtKoverM * t ) ) +
        ( ( v0 / sqrtKoverM ) * sin( sqrtKoverM + t ) );
    
    // Compute draw pos for "correct"
    float CorrectEndX = PixelsPerMeter * State[CorrectPositionX];

    // Draw the spring.
    strokeWeight( 1.0 );
    line( 0.0, 0.0, SpringEndX, 0.0 );
          
    // Draw the spring pivot
    fill( 0.0 );
    ellipse( 0.0, 0.0, 
             PixelsPerMeter * 0.03, 
             PixelsPerMeter * 0.03 );
    
    // Draw the spring bob
    fill( 1.0, 0.0, 0.0 );
    ellipse( SpringEndX, 0.0, 
             PixelsPerMeter * 0.1, 
             PixelsPerMeter * 0.1 );

    // Draw the correct bob in blue
    fill( 0.0, 0.0, 1.0 );
    ellips( CorrectEndX, -PixelsPerMeter * 0.25,
            PixelsPerMeter * 0.1,
            PixelsPerMeter * 0.1 );
}

And here that is:

I also added a "reset" capability to the simple spring code by using Processing's keyRelease function. In response to the key 'r' being released in the view (which has ascii-code 114), we copy the initial state onto the state. (See how handy state vectors are turning out to be!) Because we included the Current Time in our state vector, this is easy:


// Reset function. If the key 'r' is released in the display, 
// copy the initial state to the state.
void keyReleased()
{
    if ( key == 114 )
    {
        for ( int i = 0; i < StateSize; ++i )
        {
            State[i] = InitState[i];
        }
    }  
}

The Euler-Cromer Time Step

When we created the forward finite difference approximation to our ODE equation of motion in the previous class, we had to create two forward difference equations - one for velocity from acceleration, and then one for position from velocity. As you remember, we computed acceleration from the current position, then updated position based on the current velocity, and then finally updated velocity based on the acceleration. What if we updated velocity first, and then updated the position from the current velocity? That would look like this:
\( a( t ) = f( t, x( t ) ) = \frac{-k}{m} x( t ) \\ v( t + \Delta t ) \approx v( t ) + \Delta t a( t ) \\ x( t + \Delta t ) \approx x( t ) + \Delta t v( t + \Delta t ) \)
This seems almost trivially different from our Forward-Euler time step. This method is called many different things: Semi-Implicit Euler Method, Symplectic Euler, Semi-Explicit Euler, Euler-Cromer, and Newton–Størmer–Verlet (NSV) (at least). One of the things I've found in years of writing simulations is that just learning the names of all these different techniques, and learning that the different names often mean exactly the same thing, is a huge part of simplifying the giant mess of understanding.

The code changes to implement Euler Cromer are trivial, we just reorder the time step - literally just swap the velocity update line and the position update line:


// Euler-Cromer Time Step function.
void TimeStep( float i_dt )
{
    // Compute acceleration from current position.
    float A = ( -Stiffness / BobMass ) * State[StatePositionX];

    // Update velocity based on acceleration.
    State[StateVelocityX] += i_dt * A;

    // Update position based on current velocity.
    State[StatePositionX] += i_dt * State[StateVelocityX];

    // Update current time.
    State[StateCurrentTime] += i_dt;
}

And let's see how this ONE LINE (okay, TWO-LINE) change affects the sim:

This is not bad! Our simulation seems to have become stable, which is good news. However, if we watch the simulation for a while, it becomes clear that the simulated positions diverge more and more over time from the correct positions. In order to fix this, we're going to need to explore even higher-order time integration techniques, which is the topic of the next class!

Download

All of the files associated with this class are available via a public github repository, found here: https://github.com/blackencino/SimClass

Sunday, May 19, 2013

Simulation Class: Simple Spring Time Step

Computing Changes in the Simulation State

Now that we've defined the simulation state in the previous class, and made an effort to make sure we can display our simulation state, it's time to get to the interesting bit : simulating a change in the system over time. Note that not all simulations involve change over time - for example the calculation of material stress in a crane supporting a heavy weight. But, for our purposes, we'll focus on examples that involve matter moving in time.

A Simple Spring System

In order to evaluate the behavior of different motion simulation techniques, we need to work with a system that has an exact solution that we can easily calculate, so we can compare our simulation results to the theoretical correct results. A mass connected to a spring, with the spring initially stretched out, provides such a system. We'll create a state and draw routine that looks very similar to the simple the pendulum state from the previous class. We have a stiffness for the spring and a mass for the weight at the end of the string as configuration parameters. We restrict the mass to only move left and right, so we only store positions and velocities in the X dimension.

float Stiffness = 5.0;
float BobMass = 0.5;
int StateSize = 3;
float[] InitState = new float[StateSize];
float[] State = new float[StateSize];
int StateCurrentTime = 0;
int StatePositionX = 1;
int StateVelocityX = 2;

int WindowWidthHeight = 300;
float WorldSize = 2.0;
float PixelsPerMeter;
float OriginPixelsX;
float OriginPixelsY;

void setup()
{
    // Create initial state.
    InitState[StateCurrentTime] = 0.0;
    InitState[StatePositionX] = 0.65;
    InitState[StateVelocityX] = 0.0;

    // Copy initial state to current state.
    // notice that this does not need to know what the meaning of the
    // state elements is, and would work regardless of the state's size.
    for ( int i = 0; i < StateSize; ++i )
    {
        State[i] = InitState[i];
    }

    // Set up normalized colors.
    colorMode( RGB, 1.0 );
    
    // Set up the stroke color and width.
    stroke( 0.0 );
    //strokeWeight( 0.01 );
    
    // Create the window size, set up the transformation variables.
    size( WindowWidthHeight, WindowWidthHeight );
    PixelsPerMeter = (( float )WindowWidthHeight ) / WorldSize;
    OriginPixelsX = 0.5 * ( float )WindowWidthHeight;
    OriginPixelsY = 0.5 * ( float )WindowWidthHeight;
}

// Draw our State, with the unfortunate units conversion.
void DrawState()
{
    // Compute end of arm.
    float SpringEndX = PixelsPerMeter * State[StatePositionX];

    // Draw the spring.
    strokeWeight( 1.0 );
    line( 0.0, 0.0, SpringEndX, 0.0 );
          
    // Draw the spring pivot
    fill( 0.0 );
    ellipse( 0.0, 0.0, 
             PixelsPerMeter * 0.03, 
             PixelsPerMeter * 0.03 );
    
    // Draw the spring bob
    fill( 1.0, 0.0, 0.0 );
    ellipse( SpringEndX, 0.0, 
             PixelsPerMeter * 0.1, 
             PixelsPerMeter * 0.1 );
}

// Processing Draw function, called every time the screen refreshes.
void draw()
{
    background( 0.75 );

    // Translate to the origin.
    translate( OriginPixelsX, OriginPixelsY );

    // Draw the simulation
    DrawState();
}
Here's what that looks running in Processing - this, again, is just a repeat of the previous class's state initialization and drawing ideas, extended a little to include the notion of an initial state as separate from the state itself.

Motion of a Simple Spring

The equation of motion for a simple spring is, um, simple:
\( F = -k x \)
Where F is force, k is a constant representing the stiffness of the spring, and x is the position of the mass. This simple system is known as a Harmonic Oscillator . As we almost always do in physics, unless we're those weird quantum folks, we consult Newton's Laws of Motion, specifically the second one, which as the world's second most famous equation, says:
\( F = m a \)
Where F again, is force, m is mass, in this case the mass of the object at the end of our simple spring, and a is the acceleration of the object. By definition, acceleration is the rate of change of velocity over time, and velocity is the rate of change of position over time. In this simple system, that can be expressed in the following way, using derivatives:
\( v = \frac{dx}{dt} \\ a = \frac{dv}{dt} = \frac{d^2 x}{dt^2} \)
We often use the prime and double-prime notation as a shorthand for differentiation by time, which looks like this:
\( v = x' \\ a = v' = x'' \)
Using the equations above, and substituting for acceleration, the simple spring equation becomes:
\( x'' = \frac{-k}{m} x \)
What we see here is what is known as an Ordinary Differential Equation, or ODE for short. ODE's are equations which relate a variable (in our case the position, x), to one or more derivatives of that variable with respect to a single other variable, (in our case time, t). Most simulations of motion are numerical approximations of ODE's, or more commonly, Partial Differential Equations, in which multiple quantities will be equated to the partial derivatives of those quantities with respect to multiple other quantities, not just time. We call those PDE's for short, of course.

This equation expresses a relationship that holds true at any moment in time, and in this very simple case, there actually is an exact solution to the equation as a calculable function of time t, given an initial condition of position = x0 and velocity = v0:

\( x(0) = x_0 \\ x'(0) = v(0) = v_0 \\ x(t) = x_0 \cos( \sqrt{ \frac{k}{m} } t ) + \frac{v_0}{\sqrt{\frac{k}{m}}} \sin( \sqrt{ \frac{k}{m} } t ) \)
We chose this simple case precisely because it did have an exact solution that we will be able to use to compare our simulation results to. However, it is not usually true that there is an explicit, closed-form equation of time like the one above that can be simply evaluated for the solution to the system at any given time. For example, the equation for the angular acceleration of the pendulum from our previous class is:
\( \theta'' = -\frac{g}{L} \sin( \theta ) \)
This equation does not have a simple solution, and that will be true for anything interesting enough to bother simulating. Therefore, in order to calculate the position of the pendulum at some specific time Tk, we'll have to start at our initial time T0, and slowly iterate the solution forward through small, discrete increments of time, with each new time's solution being based on the previous time's solution. These time increments are usually very small. In the film world we work in 24 frames per second (or 25, or 30, or 48, or 60...), and simulations often require smaller steps in order to compute good results that are stable (a concept we'll explore more later).

Numerical Solutions to Differential Equations

The general idea of solving differential equations by evaluating approximations to the equations on discrete points is generally called Numerical Integration. The family of solution strategies that fall into this category rely on a linear approximation to derivatives and partial derivatives called a Finite Difference Equation.

Suppose we have a function of time, \(f(t)\). We can approximate the value of the derivative \(f'(t)\) in the following manner:

\( f'(t) \approx \frac{f(t+\Delta t) - f(t)}{\Delta t} \)
Suppose, as in a case like ours, we have an expression that we can evaluate for a derivative, but we cannot evaluate the function directly. (This is the general problem of dynamic simulation). We can rearrange the equation above so that we have an expression for the function at the new time, written in terms of the values at the current time:
\( f(t+\Delta t) \approx f(t) + \Delta t f'(t) \)
Assuming we know the value at just one single time t0, for the function f, the "initial value", we can use the equation above to find the next value, and then the next, and so on, like so:
\( f(t_0) = f_0 \\ f(t_0+\Delta t) = f_0 + \Delta t f'(t_0) \\ f(t_0+2\Delta t) = f(t_0+\Delta t) + \Delta t f'(t_0 + \Delta t) \\ ... \)
This approximation technique is called The Euler Method, or sometimes Forward Euler, because the derivative in the original expression was calculated from a point forward in time. We can apply it to our spring problem by applying it twice, since we have a second derivative. First, we calculate our acceleration from our current position, which we can do from the simple spring equation of motion we broke down above. Then, we update our position based on our current velocity, and finally we update our velocity based on the acceleration we calculated.
\( a(t) = \frac{-k}{m} x(t) \\ x(t+\Delta t) \approx x(t) + \Delta t\ v(t) \\ v(t+\Delta t) \approx v(t) + \Delta t\ a(t) \)
That's all we need to calculate new positions, so let's give it a try in our code.

Adding a Time Step to our Sim Code

Let's make a few small code additions to support time integration. Below the DrawState function, we'll add a new function called TimeStep, which will make the calculations above, and will also set the current time, based on a time increment DT. DT is a code-friendly way of writing \(\Delta t\), and is common practice.

// Time Step function.
void TimeStep( float i_dt )
{
    // Compute acceleration from current position.
    float A = ( -Stiffness / BobMass ) * State[StatePositionX];

    // Update position based on current velocity.
    State[StatePositionX] += i_dt * State[StateVelocityX];

    // Update velocity based on acceleration.
    State[StateVelocityX] += i_dt * A;

    // Update current time.
    State[StateCurrentTime] += i_dt;
}
Then, we simply update our draw function to call the TimeStep function right as it begins. We assume that each draw refresh is a single frame of time integration, which we'll call 1/24 of a second. Here's the new draw function:

void draw()
{
    // Time Step.
    TimeStep( 1.0/24.0 );

    // Clear the display to a constant color.
    background( 0.75 );

    // Translate to the origin.
    translate( OriginPixelsX, OriginPixelsY );

    // Draw the simulation
    DrawState();
}
And that's it! Here's the whole thing, now with motion!

Hey, we've got oscillating motion! But wait.... what the hell? Why is the ball shooting off the screen? Why is it getting faster and faster? Our sim is unstable! We're going to need a better approximation, because as it turns out, Forward Euler approximations are unconditionally unstable - meaning they will ALWAYS blow up sooner or later, unless the results are manipulated.

That's the subject of our next class!

Download

All of the files associated with this class are available via a public github repository, found here: https://github.com/blackencino/SimClass

Monday, May 13, 2013

Simulation Class: Smoke Solver State

Eulerian vs. Lagrangian Simulations

The types of physical simulations we do in compute graphics, visual effects, animation and games can be very coarsely categorized as either "Eulerian" or "Lagrangian". In simple terms, Eulerian Simulations are ones where the locations in space at which the simulation is being performed do not move, though mass may pass through these points. In our simple state examples, the water height field is an Eulerian simulation - it has a grid, with a height at each point, and every time we draw the grid we draw each rectangle in the same horizontal location - the waves will move through the sim points, even as the sim points stay in place. The shorthand is "Eulerian = Grids"

By contrast, a Lagrangian simulation is one in which the physical locations at which the sim is calculated (and at which knowledge about the system resides) move with the simulation as it is calculated. So, basically, a particle system, in which the particle locations change from frame to frame, and the physics are performed by looking at the relationships between the particles. The shorthand is "Lagrangian = Particles".

Finally, "Hybrid" simulations are ones which use both moving and fixed elements, such as using particles and grids simultaneously. The Particle-Level-Set methods are good examples of a hybrid system, as is the popular "Flip" fluid simulation method.

An Eulerian Smoke Grid

We're going to create an Eulerian Grid simulation of smoke, in which a 2D grid of cells will contain velocities and densities. Just like in the simple state example where we aggregated our state, we'll create a single state object and use integer pointers to access different parts of it.

Smoke will require current and previous values for velocity and density, arrays to hold input data gathered from the mouse, and finally some scratch space to use while calculating. Here's what it looks like - it's not much different from our simple state example:



// Grid resolution per side. Rectangular
int NX = 62;
int NY = 62;

// The size of the sim, in "world" units.
float LX = 100.0;

// Size, in "world" units, of a grid cell.
// Our cells are uniform (square) so DX & DY are the same.
float DXY = LX / ( float )NX;

// Y size, keeping square cells.
float LY = DXY * ( float )NY;

// The size of each grid cell, in pixels.
// This is for drawing
int CellPixels = 8;

// The length of all of our (one-dimensional)
// arrays. We use 1d arrays rather than matrices
// mostly for efficiency reasons.
int GridArraySize = GX*GY;

// Our State Arrays
int NUM_ARRAYS = 12;
float[][] State = new float[NUM_ARRAYS][GridArraySize];
int GridPrevU = 0;
int GridU = 1;
int GridPrevV = 2;
int GridV = 3;
int GridPrevDensity = 4;
int GridDensity = 5;
int GridInputU = 6;
int GridInputV = 7;
int GridInputDensity = 8;
int GridTemp0 = 9;
int GridTemp1 = 10;
int GridTemp2 = 11;

float VstrokeAlpha = 0.5;
That's all for now!

Download

All of the files associated with this class are available via a public github repository, found here: https://github.com/blackencino/SimClass

Simulation Class: Visualizing the State

Visualizing the Simulation State

Once we've established the state vector for our system, the very first thing that a simulation designer should do is to establish a visualization method for as many parts of the simulation state as possible. The better the visualization tools, the better the simulation, and the less time you will spend tracking down bugs or chasing misbehaving physics.

In fact, good visualization tools will allow you to learn and understand how your simulation is working at a deep level, and you'll begin to establish an intuitive relationship with the measurements that form your simulation state.

Drawing the Pendulum

The first thing we do when preparing to draw is to define a transformation from the simulation coordinate system to the viewing coordinate system. This is a standard part of graphics pipelines and will be familiar to anyone who has used OpenGL - we're essentially defining the transformation and projection matrices. In our pendulum case (and indeed in many of our examples), we simply need to define a relationship between the working units of the sim, in this case meters, and the pixels of the display. We define these variables in the header:

int WindowWidthHeight = 300;
float WorldSize = 2.0;
float PixelsPerMeter;
float OriginPixelsX;
float OriginPixelsY;
and then in the setup function, we calculate their values. In this case I'm explicitly deciding to put the origin at the midpoint in X, and a quarter of the way down in Y.

PixelsPerMeter = (( float )WindowWidthHeight ) / WorldSize;
OriginPixelsX = 0.5 * ( float )WindowWidthHeight;
OriginPixelsY = 0.25 * ( float )WindowWidthHeight;
It is typical to try to construct viewing transformation, such as scaling and translating the view, independently of modeling transformations. However, processing.js has a bug with concatenated transformations, so unfortunately our code has to accomodate the transformation from sim coordinates in meters to pixel coordinates by itself.

We define a DrawState function that we call from processing's 'draw' function, and draw the elements of our state. In this case we have a pendulum arm, a pendulum bob (the weight at the end), and the pivot point at the base of the pendulum. We use the angle of the pendulum from the state to determine the endpoint of the arm for drawing, thus visualizing our state:


// The DrawState function assumes that the coordinate space is that of the
// simulation - namely, meters, with the pendulum pivot placed at the origin.
// Draw the arm, pivot, and bob!
// There is currently a bug in processing.js which requires us to do the
// pixels-per-meter scaling ourselves.
void DrawState()
{
    // Compute end of arm.
    float ArmEndX = PixelsPerMeter * PendulumLength * sin( StatePendulumTheta );
    float ArmEndY = PixelsPerMeter * PendulumLength * cos( StatePendulumTheta );
  
    // Draw the pendulum arm.
    strokeWeight( 1.0 );
    line( 0.0, 0.0, ArmEndX, ArmEndY );
          
    // Draw the pendulum pivot
    fill( 0.0 );
    ellipse( 0.0, 0.0, 
             PixelsPerMeter * 0.03, 
             PixelsPerMeter * 0.03 );
    
    // Draw the pendulum bob
    fill( 1.0, 0.0, 0.0 );
    ellipse( ArmEndX, ArmEndY, 
             PixelsPerMeter * 0.1, 
             PixelsPerMeter * 0.1 );
}
And that's all we need to display the state our our simple pendulum simulation! Here it is running in processing. Note that there's no movement yet, as we haven't added any time integration into the code - we've just defined our state vector and a display mechanism for it.

Drawing the Wave Field

Drawing our other simple state example, a 1D Wave Height Field, is extremely similar to drawing the pendulum state. We have a DrawState function, which again due to bugs in Processing, does its own coordinate space transformations.

In this case, we just draw a rectangle in the world for each column of the height field state. We leave a stroke around the rectangles so we can see each individual state location. Here's what that draw function looks like:


void DrawState()
{
    float OffsetY = 0.5 * ( float )WindowHeight;
    for ( int i = 0; i < ArraySize; ++i )
    {
        float SimX = DX *  ( float )i;
        float PixelsX = ( float )( i * PixelsPerCell );
        float SimY = StateHeight[i];
        float PixelsY = SimY * (( float )PixelsPerCell ) / DX;
        float PixelsMinY = OffsetY - PixelsY;
        float PixelsHeight = (( float )WindowHeight) - PixelsMinY;

        fill( 0.0, 0.0, 1.0 );   
        rect( PixelsX, OffsetY - PixelsY, PixelsPerCell, PixelsHeight );
    }
}
And here's what it looks like in processing. Again, no movement yet - we're just working on creating a visualization for the state.


Download

All of the files associated with this class are available via a public github repository, found here: https://github.com/blackencino/SimClass

Simulation Class: The Simulation State

What is the Simulation State?

When we are creating a simulation of a phenomenon, the first and most important step in our design is how we choose to encode a description of what we're simulating as a set of numbers. The simulation state is a set of numbers that describes the "STATE" of a physical system at a moment in time.

Simulation states come in many sizes and shapes. Generally, one endeavors to represent a system with as few numbers as possible, because each separate number is a measurement of the system that will have to have computational work done to update its value for the next moment in the time evolution of the simulation.

An Extremely Simple State

A very simple simulation we could create is one of a pendulum in two dimensions. In this very simple example, the simulation state consists of just two numbers: the angle of deviation of the pendulum from its rest position at the current time, and the rate of change, or angular velocity, of that angle. Here is a diagram showing a simple pendulum from Wikipedia:

Here's a simple way of representing this simulation state in code:


// Length of the pendulum arm, in meters. This is constant, and therefore not
// really part of the simulation state
float PendulumLength;

// Angle of the pendulum at the current time, in radians
float StatePendulumTheta;

// Rate of change of the pendulum angle with respect to time, at the current
// time, in radians.
float StatePendulumDthetaDt;

Note that the code takes care to define what units it is working in. As a rule, it is a good idea to insist that your simulation work in SI units, with conversion to and from other units done as part of scene translation and export. In this case, we're using meters to specify our pendulum length, and radians to specify our angles.

A Second, Simple State

The previous pendulum state is extremely simple, and to illustrate a different type of state, let's imagine that we have a horizontal cross-section of an ocean wave, and we want to represent the height of the wave at each point horizontally. This example requires us to consider the notion of spatial resolution - the higher resolution a simulation state is, the finer the details it can represent, but generally the more computational work and resources it will require. Let's imagine that our wave cross section covers ten meters of space, and that we'd like to have a measurement of the wave's height every ten centimeters. Using meters as a representational unit, our state description for just the height part of the state will look like this:

// Size of the world, in meters.
float WorldSize = 10.0;

// Distance between two adjacent measurements of height, in meters:
// (.1 meters is 10 centimeters)
float DX = 0.1;

// The array of heights representing the wave in this 10-meter
// world, at a given time. 
int ArraySize = ( int )ceil( WorldSize / DX );

// Allocating the Height Array.
float[] StateHeightArray = new float[ArraySize];

Notice that in this second state example, the size of the state arrays is actually calculated from the size of the world we're simulating and the resolution we've chosen to simulate at. Simulation code usually offers multiple ways of determining the ultimate state size - either specifying the number of elements in the state, corresponding to a given world size, which will determine the spacing and resolution of the state, or alternatively specifying the spacing resolution, which determines the number of elements in the state, as in the above example. Here's what the code would look like if we were specifying the number of elements in the array directly, instead of the spacing:

// Size of the world, in meters.
float WorldSize = 10.0;

// Array Size
int ArraySize = 100;

// Distance between two adjacent measurements of height, in meters:
float DX = WorldSize / ( float )ArraySize;

// Allocating the Height Array.
float[] StateHeightArray = new float[ArraySize];

Two More Complex State Examples

The above two examples are both extremely simple, and have a small number of state variables. Here is an example of a more complex state - in this case, representing a number of particles which have a number of attributes per particle - position, mass, velocity, etc. Some, but not all, of these attributes have values at the current time AND at the previous simulated point in time, all of which is stored as part of the simulation state. In this example, there is a fixed maximum number of particles, and a number of them that are considered currently alive.

// Max number of particles.
int MAX_NUM_PARTICLES = 1024;
int ArraySize = MAX_NUM_PARTICLES;

// Number of active particles.
int N;

// Particle data arrays. Note that we make a separate array for each
// piece of particle data, separating even the vector components.
// This illustrates the simulation design idiom: 
// "structs of arrays, rather than arrays of structs".
float[] StateMass       = new float[ArraySize];
float[] StateRadius     = new float[ArraySize];
float[] StatePosX       = new float[ArraySize];
float[] StatePosY       = new float[ArraySize];
float[] StateVelX       = new float[ArraySize];
float[] StateVelY       = new float[ArraySize];
float[] StatePrevPosX   = new float[ArraySize];
float[] StatePrevPosY   = new float[ArraySize];
This second state example represents a height field in 1D, like above, with multiple measurements at each point.

// Size of the world, in meters.
float WorldSize = 10.0;

// Distance between two adjacent measurements of height, in meters:
// (.1 meters is 10 centimeters)
float DX = 0.1;

// The array of heights representing the wave in this 10-meter
// world, at a given time. 
int ArraySize = ( int )ceil( WorldSize / DX );

// Allocating the Height Array.
float[] StateHeight       = new float[ArraySize];
float[] StateDheightDt    = new float[ArraySize];
float[] StatePressure     = new float[ArraySize];
float[] StateFloorHeight  = new float[ArraySize];
float[] StatePrevHeight   = new float[ArraySize];

Design Idioms and Best Practices

The examples have a lot of things in common, and indeed, these form the basis for what I would call "good simulation design practices". Each example above is representing a different physical system - first a simple pendulum, then a simple height field, then finally a system of many particles with multiple measurements per particle, and finally a more complex wave example. However, they all use the same code conventions. Each one calls its array sizes "ArraySize", each one prefaces the variable names of its simulation state with the word "State". Each of the examples that uses a grid of values uses the same conventions for its world description: WorldSize, DX, and so on. This is an extremely important part of simulation code design - coming up with a very consistent standard for naming of arrays and variables, and rigorously adhering to the standard. This will ultimately allow you to reuse code easily, and also to rely on template patterns to apply common operations across different simulation implementations. The lion's share of simulation coding involves defining and employing these standards.

The State Vector

In each of the above examples, we've created a separate, specifically named array to store each separate simulation measurement. Taken together, these measurements collectively form what we consider the entire "state". Most likely, your simulation will also feature some storage that is temporary, representing quantities that are computed as part of the calculation of permanent state properties - as an example, on the road to computing pressure in a smoke simulation, a temporary measurement called "divergence" is computed, but it is not usually considered formally part of the simulation state because it can be wholly derived from other parts.

Suppose, instead of explicitly naming each separate field of our simulation state, we instead created a single pool of storage for the state, with each part of it concatenated together. We could use indexing expressions to get to the right part of the large array for each individual property, and then we would be able to see more clearly that our Simulation State was in fact a single representational object.

The name of this single array of storage for a simulation state is the "State Vector". Here's an example of how this might work in code for a state consisting of several distinct fields, and using a multi-dimensional array to represent the concatenation of the fields together.


int ArraySize = 1024;
int NumStateArrays = 8;
float[][] State = new float[NumStateArrays][ArraySize];
int StateIndexMass = 0;
int StateIndexRadius = 1;
int StateIndexPosX = 2;
int StateIndexPosY = 3;
int StateIndexVelX = 4;
int StateIndexVelY = 5;
int StateIndexPrevPosX = 6;
int StateIndexPrevPosY = 7;

// To access the x-position of particle i:
float xPos = State[StateIndexPosX][i];

Download

All of the files associated with this class are available via a public github repository, found here: https://github.com/blackencino/SimClass

Simulation Class: Intro and Prerequisites

Introduction

The goal of this four-week course is to teach the basic structure of physical simulation engines, from a coding perspective. At the end of this class, if you have chosen to follow along with the coursework at home, you will have constructed a simple 2D navier-stokes fluid solver, which will run in a web browser.

This class will be taught with a programming focus, and will use source code to illustrate the basic concepts that most simulation engines have in common. The field of physical simulation is vast, and rather than trying to encompass the whole field in a scattershot way, we will attempt to learn an introduction by focusing on a simple simulation.

By the end of these four weeks, we'll have built a small smoke solver that responsds to mouse input and runs in a web broswer. The final version of the solver code that we'll build is running right here in this post. Click and drag in the view below to try it out! Use the 'v' key to toggle display of velocity vectors.


This whole solver is 795 lines long, and we'll build those up from scratch as we go through these four classes.

Prerequisites and Tools

We will use the Processing programming language, as ported into the web browser via the javascript library Processing.js. Processing is a C-like language, specifically geared towards small graphics applications. We will be working in the processing language directly, rather than in Javascript.

To try out the example code in our tutorials, you have a few options. Firstly, you can download the Processing execution environment, which will run outside a web-browser, and will offer a more robust development environment. This is the easiest way to work, especially when trying to figure out where you've made errors in your code. The way I worked to put this class together was to build the examples in the processing UI until they worked, and then put them into HTML using processing.js. Amazingly, in many cases the web versions run as fast or faster than the Processing UI.

A great website that allows for coding HTML that uses Processing directly is JS Fiddle. To use it, create an account and log in - then, from the uppermost menu on the left that says "Frameworks & Extensions", select "Processing.js 1.4.1". Then, in the upper left quadrant, simply enter your processing code surrounded by an HTML5 canvas element and an HTML script wrapper. To see your code run, press the "Run" button in the top menu, and your result will appear in the lower right quadrant. Here's a link to a simple processing.js starting point in jsfiddle: JS Fiddle Processing Hello World. Thank you MTF for showing me this amazing website! It's amazing how powerful this site is - here's the whole processing fluid solver running in it: JS Fiddle Processing Smoke Solver.

Github

All of the source code for this class, including the HTML for the web pages, and all of the processing code, is stored publicly on GitHub, and you are welcome to fork the repository to start working on your own code! Here is the link to the github repo: Encino Sim Class on GitHub

Acknowledgements

The solver code that this class is based around was built in processing by the inimitable Allen Hemberger, and my changes to his code are largely cosmetic, expanding for the purposes of teaching. Allen's solver was in turn based on the amazing paper, Real Time Fluid Dynamics for Games, written by Jos Stam. This paper includes an entire fluid solver source code, written in C, that was able to run on a mobile phone in 2003. This paper, and Stam's Stable Fluids paper before it provide a template for solver construction that is mostly followed to this day!

Download

All of the files associated with this class are available via a public github repository, found here: https://github.com/blackencino/SimClass