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.
We will create a word and animate its letters along a ci::Path2d
object. Perform the following steps to do so:
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"
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; };
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; }
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(); }
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; }
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"
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; };
Word.cpp
file and include the Word.h
file:#include "Word.h"
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; } }
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 ); } }
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(); } }
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;
Word * mWord; Path2d mCurve; float mPathLength; Timer mTimer; double mSeconds;
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;
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 );
setup
method:mPathLength = 0.0f; for( int i=0; i<mCurve.getNumPoints()-1; i++ ){ mPathLength += mCurve.getPoint( i ).distance( mCurve.getPoint( i+1 ) ); }
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 ); }
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 );
Declare the keyUp
event handler:
void keyUp( KeyEvent event );
keyUp
event handler:void CurveTextApp::keyUp( KeyEvent event ){ mTimer.start(); }
3.148.104.124