Animating lots of font objects on 2d paths

Hi,

I’m trying to animate aprox. 200-300 sentences of words on 2d paths.
After lots of unicode font issues, I’ve ended up with ofxFTGL because of its performance.
By the way, I tried ofXPango but no success on OF 8.0 or older versions.

For now, there are just 17 Sentences

I need to update every single word’s parameters(color,scale, effect) according to mouse coordinates. So, I’ve 3 classes Senteces, Words and Letters

In main testApp.cpp,

  1. I get the sentences from a database, parse them to vector<string>
  2. then create Sentence objects,
  3. Sentence object parse the words and create Word objects.
  4. Finally, Every single Word object creates Letter objects and creates a ofxFTGLFont in order to
    control the 2d positions and rotations of every single letter while the curves animating based on ofNoise() method. And the problem is in here to creating tons of ofxFTGLFont object and animating them at the same time.

Any suggestions to improve the app performance are welcome. I’m stuck with the performance issue. I try to draw each frame (in test.cpp) into an fbo but no change at all.

3 Likes

can you show your code?
afaik drawing every frame in an fbo shouldn’t make a big difference, but i think the way you deal with your classes and objects could.

congratulations really nice project by the way

make sure to add a couple of " if " statements inside your drawing loop so that you don’t render items that are outside your screen

it really shouldn’t matter but

Have you tried using ofxFontStash?

also:

create a

struct letter{
char * letter_;
int posx,posy;
}
a vector of letters.
instead of a vector of strings

and just

draw your things,

with a single font rendering object

Thanks for your suggestions.

@atanyimebutnow I change the code hierarchy. Animation points can’t match with the words in sentences. So, in new version there is only Sentence class.

@igiso thanks for ofxFontStash suggestion. It works better than FTGL for me.
Switching string to char vector doesn’t help me. Or I code it in wrong way.

I can draw and animate 40 Sentences according to curve points without ofLine() in 30 FPS.

However, when I draw lines, the FPS goes down 7-8 FPS

Here is the last version of the code.

test.h

#pragma once

#include "ofMain.h"
#include "ofxSQLiteHeaders.h"
#include "ofxFontStash.h"
#include "Sentence.h"

class testApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();

		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
		
    ofxFontStash* font;
    int SentenceNum;
    vector<Sentence*> sentences;
    
    ofFbo fbo;
    
private:
    ofxSQLite* sqlite;
    vector<string> texts;
};

test.cpp

#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup(){
    ofEnableAlphaBlending();
	ofSetVerticalSync(true);
	ofSetFrameRate(30);
	ofBackground(0);
    
    // Read Sentences from database
    sqlite = new ofxSQLite("rizomi.db");
    ofxSQLiteSelect sel = sqlite->select("id, word").from("words");
	sel.execute().begin();
    
	while(sel.hasNext()) {
		int id = sel.getInt();
		std::string name = sel.getString();
		//cout << id << ", " << name << endl;
		sel.next();
        //if(id < 1)
        name = ofUTF8::toUpper(name);
        texts.push_back(name);
	}
    
    
    // load fonts
    font = new ofxFontStash();
    font->setup("mono.ttf"); //load verdana font, set lineHeight to be 130%
    
    
    SentenceNum = texts.size();
    //SentenceNum = 20;
    cout << "Sentence size : "<< SentenceNum << endl;
    for (int j = 0; j < SentenceNum; j++) {
        Sentence *sentence = new Sentence(texts[j],font, 0, 35 + 24*j);
        sentences.push_back(sentence);
    }
    
	fbo.allocate(1024, 768);
}

//--------------------------------------------------------------
void testApp::update(){
    /*fbo.begin();
    ofClear(0);
    
    fbo.end();*/
}

//--------------------------------------------------------------
void testApp::draw(){
    ofSetColor(255);
    ofDrawBitmapString(ofToString(ofGetFrameRate()), 10, 14);
    
    
    //fbo.draw(0, 0);
    font->beginBatch();
    for (int j = 0; j < SentenceNum; j++) {
        
        //font->drawBatch(texts[j], 24, 0, 35+20 * j);
        
        sentences[j]->draw();
        
    }
    font->endBatch();

    
}

Sentence.h

#pragma once

#include "stdio.h"
#include "ofMain.h"
#include "ofxFontStash.h"
#include <set>
#include "ofUTF8.h"

struct Letter{
    char * letter_;
    int posx,posy;
};

class Sentence
{
public:
	Sentence(string str, ofxFontStash *font, int sPosX,int sPosY);
	~Sentence();

	void update();  // update method, used to refresh your objects properties
    void draw();    // draw method, this where you'll do the object's drawing 
 
    float yoff;

	float xPrev;
	float yPrev;
	float zPrev;

	string myString;
    int strLen;
	ofxFontStash* myFont;

	void setup(float);

	float speed;

	float yStart;

	float startShine,stopShine;

	void highLight(float,float);
    
    vector<string> letters;

 private: // place private functions or variables declarations here
};

Sentence.cpp

#include "Sentence.h"

string utf8_substr2(const string &str,int start, int length=INT_MAX)
{
    int i,ix,j,realstart,reallength;
    if (length==0) return "";
    if (start<0 || length <0)
    {
        //find j=utf8_strlen(str);
        for(j=0,i=0,ix=str.length(); i<ix; i+=1, j++)
        {
            unsigned char c= str[i];
            if      (c>=0   && c<=127) i+=0;
            else if (c>=192 && c<=223) i+=1;
            else if (c>=224 && c<=239) i+=2;
            else if (c>=240 && c<=247) i+=3;
            else if (c>=248 && c<=255) return "";//invalid utf8
        }
        if (length !=INT_MAX && j+length-start<=0) return "";
        if (start  < 0 ) start+=j;
        if (length < 0 ) length=j+length-start;
    }
    
    j=0,realstart=0,reallength=0;
    for(i=0,ix=str.length(); i<ix; i+=1, j++)
    {
        if (j==start) { realstart=i; }
        if (j>=start && (length==INT_MAX || j<=start+length)) { reallength=i-realstart; }
        unsigned char c= str[i];
        if      (c>=0   && c<=127) i+=0;
        else if (c>=192 && c<=223) i+=1;
        else if (c>=224 && c<=239) i+=2;
        else if (c>=240 && c<=247) i+=3;
        else if (c>=248 && c<=255) return "";//invalid utf8
    }
    if (j==start) { realstart=i; }
    if (j>=start && (length==INT_MAX || j<=start+length)) { reallength=i-realstart; }
    
    return str.substr(realstart,reallength);
}



Sentence::Sentence(string str, ofxFontStash *_font, int sPosX,int sPosY)
{
	yoff = 0.0;
	xPrev = 0;
	yPrev = 0;
	zPrev = 0;


	myString = str;
    myFont = _font;

	speed = 0.01f+ofRandomf()/100.f;

	yStart = 100 + ofRandom(ofGetHeight()-200);
    
    strLen = myString.length();
    
    for (int i = 0; i < strLen; i++) {
        
        letters.push_back(utf8_substr2(myString,i,1));
     }
}


Sentence::~Sentence()
{
}

void Sentence::update(){
   
}


void Sentence::draw(){
    
	ofSetColor(0,225,0);

	int i = 0;

	float xoff = 0; 
	float zoff = 0; 

	string currentChar; 
	float x = 0;
	float z = 0;
    
	while(x <= ofGetWidth()){
		
		
		// Calculate a y value according to noise, map to 
		float y = ofMap(ofNoise(xoff, yoff), 0, 1, yStart,yStart+100); // Option #1: 2D Noise
		float z = 10*ofNoise(zoff);
    
		if(x > 0){
			//ofLine(xPrev,yPrev,x,y);
            
			if(i < strLen){

				

				ofVec2f tanVec = ofVec2f(xPrev,yPrev) - ofVec2f(x,y);

				float yDiff = tanVec.y;
				float xDiff = tanVec.x;

				float arcTan = ofRadToDeg(atan(yDiff / xDiff));
                
                ofPushMatrix();
				ofTranslate(x,y);
				ofRotateZ(arcTan);
				float scaleVal = ofMap(x, 0, ofGetWidth(), 0.1, 1);
                float alphaVal = ofMap(x,0,ofGetWidth(),10,255);
                ofScale(1-scaleVal,1-scaleVal,1);
                
                ofSetColor(255-(alphaVal));
				if(i > startShine && i< stopShine){
					ofSetColor(225,0,0);
				}else{
					ofSetColor(0,225,0);
				}
                
				myFont->drawBatch(letters[i],24, 0, 0);
                ofPopMatrix();
			}
		
		i++;

		}
		xPrev = x;
		yPrev = y;
		zPrev = z;
		// Increment x dimension for noise
		//xoff += ofMap(mouseX, 0, ofGetWidth(), 0,0.1);
		xoff += 0.008;
		//zoff += 0.008;

		x += 10;	
	}
    
    yoff += speed;
} 

void Sentence::setup(float speed){
   
	
}

void Sentence::highLight(float _startShine, float _stopShine){
   
	startShine = _startShine;
	stopShine = _stopShine;
}

myFont->drawBatch("_",24,0,0); instead of ofLine might help you just a tinytiny bit…

the moment you draw line or whatever you double the particles you have in your screen

I might be wrong but it seems that you need to dive into the GPU if you really want to tackle this…

40 sentences X strLen X 2 = cpu limit

if you draw the lines by modifying the GPUparticleSystemexample
you might be able to achieve a much better performance

:smile: hope you have lots of patience!!

drawing lines one segment at a time this way is very inefficient. it makes a draw call each time, and the driver overhead adds up quickly. I would suggest building an ofPath object and calling draw on it once instead of drawing every segment.

1 Like

@igiso I haven’t enough knowledge about shader world :slight_smile: But, it seems there is no way to handle thousands of letters without using GPU.

@TimS thanks for the ofPath but I’m not sure about it. Does it make any difference? Because, I’m animating curves points also?

Here is a little working demo video

@alp if you use plain ofTrueTypeFont you have the option to do:

ofVboMesh mesh;
mesh = font.getStringMesh("sentence",x,y);

you can do that for every sentence and then modify that mesh which will be composed by 4 vertices per letter so you just need to move those vertices to move a letter up and down.

then to draw it:

font.getFontTexture().bind();
mesh.draw();
font.getFontTexture().unbind();

you can even draw several sentences in between the same bind/unbind if you are using the same font for all of them.

You probably want to keep a copy of the original mesh and modify it into a copy for every frame, instead of doing translate, rotate, scale… just apply those transformations to every vertex it should be much faster than trying to render the font for every letter every frame

3 Likes

Also what Tim says is true, in general try to do as few GL calls as possible, every call to ofLine, ofTranslate… is a gpu call that needs an infividual upload to the graphics card, you want to generate everything in as few vbo’s as possible and then upload everything to the graphics card with a few draw calls

for the sentences you can even append every sentence in 1 vbo and draw only that like:

for every sentence
    for every letter //4 vertices
         modify vertices
    mesh.append(sentence.mesh)
mesh.draw()

where mesh is an ofVboMesh and sentence.mesh is an ofMesh that you get from the font with font.getStringMesh("sentence",x,y)

1 Like

Also something else that is really slow is how you are calculating the position of each letter, instead of calculating a noise value and then the angle for each letter with the previous i would use a polyline using noise to move the control points of a bezier and then drawing the sentences along those lines that will remove tons of sin/cos (for the rotation) and tan calculations which are really slow

1 Like

@arturo thanks for all of the suggestions. I try to change the way I’m generating curve points…
But the unicode letters are the biggest issue for me. Because of this, I’m using FontStash or FTGL. Both of these addons don’t have the methods like .getStringMesh() or .getFontTexture(). I can’t draw Turkish letters with ofTrueTypeFont(). Is it possible to draw unicode letters with ofTrueType()?

This is a modified version of the ofxTTypefontUC addon to support utf8 in both pc and mac

try it , this might workutf8ofxTTfontUC.zip (9.6 KB)

though we might have to add the bind, getofTexture function etc… :frowning:

Hi @arturo, I have followed this thread and successfully implemented the getFontTexture() plus ofVboMesh approach — this is great!
I would now like to create a sort of “animation cue” using a Polyline with control points and Bezier, as you suggested. Could you expand on how to do this, maybe with a snippet, or maybe pointing to an existing example that shows this?
That would be amazing — thank you!

just use ofPolyline to create the curve you want to use once, then access it’s vertices using an index that you can increment every frame which will serve as the position for anything you want to move along that curve.