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.
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;
We will create an animation using the flow field. Perform the following steps to do so:
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; };
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;
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);
mFlowField
.mFlowField.resize( mRows ); for( int i=0; i<mRows; i++ ){ mFlowField[i].resize( mColumns );
Perlin mPerlin; float mCounter;
setup
method initialize mCounter
to zero.mCounter = 0.0f; }
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 ); } }
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 ); } }
Followers
. Declare the following member:vector<shared_ptr<Follower>> mFollowers;
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 ) ) ); }
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 ); }
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:
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.
3.128.173.53