Animating text around curves

In this recipe, we will learn how we can animate text around a user-defined curve.

We will create the Letter and Word classes to manage the animation, a ci::Path2d object to define the curve, and a ci::Timer object to define the duration of the animation.

Getting ready

Create and add the following files to your project:

  • Word.h
  • Word.cpp
  • Letter.h
  • Letter.cpp

How to do it…

We will create a word and animate its letters along a ci::Path2d object. Perform the following steps to do so:

  1. In the Letter.h file, include the necessary to use the text, ci::Vec2f, and ci::gl::Texture files.

    Also add the #pragma once macro

    #pragma once
    
    #include "cinder/vector.h"
    #include "cinder/text.h"
    #include "cinder/gl/Texture.h"
  2. Declare the Letter class with the following members and methods:
    class Letter{
    public:
        Letter( ci::Font font, conststd::string& letter );
    
        void draw();
        void setPos( const ci::Vec2f& newPos );
    
        ci::Vec2f pos;
        float rotation;
        ci::gl::Texture texture;
        float width;
    };
  3. Move to the Letter.cpp file to implement the class.

    In the constructor, create a ci::TextBox object, set its parameters, and render it to texture. Also, set the width as the texture's width plus a padding value of 10:

    Letter::Letter( ci::Font font, conststd::string& letter ){
        ci::TextBoxtextBox;  
        textBox = ci::TextBox().font( font ).size( ci::Vec2i( ci::TextBox::GROW, ci::TextBox::GROW ) ).text( letter ).premultiplied();
        texture = textBox.render();
        width = texture.getWidth() + 10.0f;
    }
  4. In the draw method, we will draw the texture and use OpenGL transformations to translate the texture to its position, and rotate according to the rotation:
    void Letter::draw(){
        glPushMatrix();
        glTranslatef( pos.x, pos.y, 0.0f );
        glRotatef( ci::toDegrees( rotation ), 0.0f, 0.0f, 1.0f );
        glTranslatef( 0.0f, -texture.getHeight(), 0.0f );
        ci::gl::draw( texture );
        glPopMatrix();
    }
  5. In the setPos method implementation, we will update the position and calculate its rotation so that the letter is perpendicular to its movement. We do this by calculating the arc tangent of its velocity:
    void Letter::setPos( const ci::Vec2f&newPos ){
        ci::Vec2f vel = newPos - pos;
        rotation = atan2( vel.y, vel.x );
        pos = newPos;
    }
  6. The Letter class is ready! Now move to the Word.h file, add the #pragma once macro, and include the Letter.h file:
    #pragma once
    #include "Letter.h"
  7. Declare the Word class with the following members and methods:
    class Word{
    public:
        Word( ci::Font font, conststd::string& text );
    
        ~Word();
    
        void update( const ci::Path2d& curve, float curveLength, float  progress );
        void draw();
    
          std::vector< Letter* > letters;
          float length;
    };
  8. Move to the Word.cpp file and include the Word.h file:
    #include "Word.h"
  9. In the constructor, we will iterate over each character of text and add a new Letter object.We will also calculate the total length of the text by calculating the sum of widths of all the letters:
    Word::Word( ci::Font font, conststd::string& text ){
      length = 0.0f;
      for( int i=0; i<text.size(); i++ ){
      std::string letterText( 1, text[i] );
              Letter *letter = new Letter( font, letterText );
      letters.push_back( letter );
      length += letter->width;
        }
    }

    In the destructor, we will delete all the Letter objects to clean up memory used by the class:

    Word::~Word(){
      for( std::vector<Letter*>::iterator it = letters.begin(); it != letters.end(); ++it ){
      delete *it;
        }
    }
  10. In the update method, we will pass a reference to the ci::Path2d object, the total length of the path, and the progress of the animation as a normalized value from 0.0 to 1.0.

    We will calculate the position of each individual letter along the curve taking into account the length of Word and the current progress:

    void Word::update( const ci::Path2d& curve, float curveLength,   float progress ){
      float maxProgress = 1.0f - ( length / curveLength );
      float currentProgress = progress * maxProgress;
      float progressOffset = 0.0f;
      for( int i=0; i<letters.size(); i++ ){
            ci::Vec2f pos = curve.getPosition
            ( currentProgress + progressOffset );
            letters[i]->setPos( pos );
            progressOffset += ( letters[i]->width / curveLength );
        }
    }
  11. In the draw method, we will iterate over all letters and call the draw method of each letter:
    void Word::draw(){
      for( std::vector< Letter* >::iterator it = letters.begin(); it != letters.end(); ++it ){
            (*it)->draw();
        }
    }
  12. With the Word and Letter classes ready, it's time to move to our application's class source file. Start by including the necessary source files and adding the helpful using statements:
    #include "cinder/Timer.h"
    #include "Word.h"
    
    using namespace ci;
    using namespace ci::app;
    using namespace std;
  13. Declare the following members:
    Word * mWord;
    Path2d mCurve;
    float mPathLength;
    Timer mTimer;
    double mSeconds;
  14. In the setup method, we will start by creating std::string and ci::Font and use them to initialize mWord. We will also initialize mSeconds with the seconds we want our animation to last for:
    string text = "Some Text";
    Font font = Font( "Arial", 46 );
    mWord = new Word( font, text );
    mSeconds = 5.0;
  15. We now need to create the curve by creating the keypoints and connecting them by calling curveTo:
    Vec2f curveBegin( 0.0f, getWindowCenter().y );
    Vec2f curveCenter = getWindowCenter();
    Vec2f curveEnd( getWindowWidth(), getWindowCenter().y );
    
    mCurve.moveTo( curveBegin );
    mCurve.curveTo( Vec2f( curveBegin.x, curveBegin.y + 200.0f ), Vec2f( curveCenter.x, curveCenter.y + 200.0f ), curveCenter );
    mCurve.curveTo( Vec2f( curveCenter.x, curveCenter.y - 200.0f ), Vec2f( curveEnd.x, curveEnd.y - 200.0f ), curveEnd );
  16. Let's calculate the length of the path by summing the distance between each point and the one next to it. Add the following code snippet inside the setup method:
    mPathLength = 0.0f;
    for( int i=0; i<mCurve.getNumPoints()-1; i++ ){
      mPathLength += mCurve.getPoint( i ).distance( mCurve.getPoint( i+1 ) );
        }
  17. We need to check if mTimer is running and calculate the progress by calculating the ratio between the elapsed seconds and mSeconds. Add the following code snippet inside the update method:
    if( mTimer.isStopped() == false ){
      float progress;
      if( mTimer.getSeconds() >mSeconds ){
        mTimer.stop();
        progress = 1.0f;
            } else {
      progress = (float)( mTimer.getSeconds() / mSeconds );
            }
    mWord->update( mCurve, mPathLength, progress );
        }
  18. In the draw method, we will need to clear the background, enable alpha blending, draw mWord, and draw the path:
    gl::clear( Color( 0, 0, 0 ) ); 
    gl::enableAlphaBlending();
    mWord->draw(); 
    gl::draw( mCurve );
  19. Finally, we need to start the timer whenever the user presses any key.

    Declare the keyUp event handler:

    void keyUp( KeyEvent event );
  20. And the following is the implementation of the the keyUp event handler:
    void CurveTextApp::keyUp( KeyEvent event ){
    mTimer.start();
    }
  21. Build and run the application. Press any key to begin the animation.
    How to do it…
..................Content has been hidden....................

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