Monday, May 13, 2013

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

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.