Creating a flow field with Perlin noise

In this recipe we will learn how we can animate objects using a flow field. Our flow field will be a two-dimensional grid of velocity vectors that will influence how objects move.

We will also animate the flow field using vectors calculated with Perlin noise.

Getting ready

Include the necessary files to work with OpenGL graphics, Perlin noise, random numbers, and Cinder's math utilities.

#include "cinder/gl/gl.h"
#include "cinder/Perlin.h"
#include "cinder/Rand.h"
#include "cinder/CinderMath.h"

Also, add the following useful using statements:

using namespace ci;
using namespace ci::app;
using namespace std;

How to do it…

We will create an animation using the flow field. Perform the following steps to do so:

  1. We will begin by creating a Follower class to define the objects that will be influenced by the flow field.

    Declare the following class before the main application class:

    class Follower{
    public:
     Follower( const Vec2f& pos ){
      this->pos = pos;
     }
     void update( const Vec2f& newVel ){
      vel += ( newVel - vel ) * 0.2f;
      pos += vel;
      if( pos.x < 0.0f ){
       pos.x = (float)getWindowWidth();
       vel = Vec2f();
      }
      if( pos.x > (float)getWindowWidth() ){
       pos.x = 0.0f;
       vel = Vec2f();
      }
      if( pos.y < 0.0f ){
       pos.y = (float)getWindowHeight();
       vel = Vec2f();
      }
      if( pos.y > (float)getWindowHeight() ){
       pos.y = 0.0f;
       vel = Vec2f();
      } 
     }
     void draw(){
      gl::drawSolidCircle( pos, 5.0f );
      gl::drawLine( pos, pos + ( vel * 20.0f ) );
     }
     Vec2f pos, vel;
    };
  2. Let's create the flow field. Declare a two-dimensional std::vector to define the flow field, and variables to define the gap between vectors and the number of rows and columns.
    vector< vector< Vec2f> > mFlowField;
    Vec2f mGap;
    float mCounter;
    int mRows, mColumns;
  3. In the setup method we will define the number of rows and columns, and calculate the gap between each vector.
    mRows = 40;
    mColumns = 40;
    mGap.x = (float)getWindowWidth() / (mRows-1);
    mGap.y = (float)getWindowHeight() / (mColumns-1);
  4. Based on the number of rows and columns we can initialize mFlowField.
    mFlowField.resize( mRows );
    for( int i=0; i<mRows; i++ ){
      mFlowField[i].resize( mColumns );
  5. Let's animate the flow field using Perlin noise. To do so declare the following members:
      Perlin mPerlin;
    float mCounter;
  6. In the setup method initialize mCounter to zero.
      mCounter = 0.0f;
    }
  7. In the update method we will increment mCounter and iterate mFlowField using a nested for loop, and use mPerlin to animate the vectors.
    for( int i=0; i<mRows; i++ ){
     for( int j=0; j<mColumns; j++ ){
      float angle= mPerlin.noise( ((float)i)*0.01f + mCounter,
       ((float)j)*0.01f ) * M_PI * 2.0f;
      mFlowField[i][j].x = cosf( angle );
      mFlowField[i][j].y = sinf( angle );
     } 
    }
  8. Now iterate over mFlowField and draw a line indicating the direction of the vectors.

    Add the following code snippet inside the draw method:

    for( int i=0 i<mRows; i++ ){
     for( int j=0; j<mColumns; j++ ){
      float x = (float)i*mGap.x;
      float y = (float)j*mGap.y;
      Vec2f begin( x, y );
      Vec2f end = begin + ( mFlowField[i][j] * 10.0f );
      gl::drawLine( begin, end );
     }
    }
  9. Let's add some Followers. Declare the following member:
    vector<shared_ptr<Follower>> mFollowers;
  10. In the setup method we will initialize some followers and add them at random positions in the window.
    int numFollowers = 50;
    for( int i=0; i<numFollowers; i++ ){
     Vec2f pos( randFloat( getWindowWidth() ), 
      randFloat(getWindowHeight() ) );
     mFollowers.push_back( 
      shared_ptr<Follower>( new Follower( pos ) ) );
    }
  11. In the update we will iterate mFollowers and calculate the corresponding vector in mFlowField based on its position.

    We will then update the Follower class using that vector.

    for( vector<shared_ptr<Follower> >::iterator it = 
     mFollowers.begin(); it != mFollowers.end(); ++it ){
     shared_ptr<Follower> follower = *it;
     int indexX= ci::math<int>::clamp(follower->pos.x / mGap.x,0,
      mRows-1 );
     int indexY= ci::math<int>::clamp(follower->pos.y / mGap.y,0, 
      mColumns-1 );
     Vec2f flow = mFlowField[ indexX ][ indexY ];
     follower->update( flow );
    }
  12. Finally, we just need to draw each Follower class.

    Add the following code snippet inside the draw method:

    for( vector< shared_ptr<Follower> >::iterator it = 
     mFollowers.begin(); it != mFollowers.end(); ++it ){
     (*it)->draw();
    }

    The following is the result:

How to do it…

How it works…

The Follower class represents an agent that will follow the flow field. In the Follower::update method a new velocity vector is passed as a parameter. The follower object will interpolate its velocity into the passed value and use it to animate. The Follower::update method is also responsible for keeping each agent inside the window by warping its position whenever it is outside the window.

In step 11 we calculated the vector in the flow field that will influence the Follower object using it's position.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.173.53