Irrlicht + FreeType2 examples

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
kornerr
Posts: 245
Joined: Thu Jul 06, 2006 9:57 am
Location: Russia, Siberia, Kemerovo
Contact:

Irrlicht + FreeType2 examples

Post by kornerr »

Simple example of using FreeType2, which you can read in the FreeType2 tutorial.
You better read FreeType2 tutorial, else you won't understand anything.

Makefile for Linux:

Code: Select all

FLAGS = `freetype-config --cflags` -I/usr/local/include/irrlicht
LIBS = `freetype-config --libs` -lIrrlicht

test: main.cpp
        g++ -o test main.cpp $(FLAGS) $(LIBS)

clean:
        rm -f test
main.cpp:

Code: Select all

#include <irrlicht.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <iostream>
#include <vector>
using namespace std;
using namespace irr;
using namespace video;
using namespace scene;

unsigned short NextP2 (unsigned short a) {
        unsigned short b = 1;
        while (b < a)
                b <<= 1;
        return b;
}

int main () {
        int error;
        FT_Library library;
        error = FT_Init_FreeType (&library);
        if (error) 
                cout << "Error occurred during lib init\n";
        FT_Face face;
        error = FT_New_Face (library, "test.ttf", 0, &face);
        if (error == FT_Err_Unknown_File_Format)
                cout << "Unsupported format\n";
        else
                if (error)
                        cout << "Error occurred during face init\n";
        error = FT_Set_Char_Size (face, 0, 32 * 64, 300, 300);
        if (error)
                cout << "Can't set size at given dpi\n";

        IrrlichtDevice *dev = createDevice (EDT_OPENGL, core::dimension2d<s32> (640, 480),
                        32, false, false, false, 0);
        dev->setWindowCaption (L"FreeType2 & Irrlicht");
        IVideoDriver *drv = dev->getVideoDriver ();
        ISceneManager *smgr = dev->getSceneManager ();
        smgr->addCameraSceneNodeFPS ();
        dev->getCursorControl ()->setVisible (false);

        //vector<ITexture*> texs;

        u32 pen_x = 100;
        u32 pen_y = 100;
        string s = "A sample string";
        FT_UInt glyph_index = FT_Get_Char_Index (face, (int)s[0]);
        error = FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT);
        if (error)
                cout << "Can't load glyph\n";
        if (face->glyph->format != FT_GLYPH_FORMAT_BITMAP) {
                //cout << "Non-bitmap format. Converting...\n";
                error = FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL);
                if (error)
                        cout << "Can't convert to bitmap\n";
        }
        FT_GlyphSlot slot = face->glyph;
        FT_Bitmap &bmp = slot->bitmap;
        int top = slot->bitmap_top;
        int left = slot->bitmap_left;
        int width = NextP2 (bmp.width);
        int height = NextP2 (bmp.rows);

        u32 *data = new u32[width * height];

        for (int j = 0; j < height; j++)
                for (int i = 0; i < width; i++)
                        data[(i + j * width)] = 
                                (i >= bmp.width || j >= bmp.rows) ? 0:
                                (bmp.buffer[i + bmp.width * j] << 24) | 0x00ffffff;
        IImage *img = drv->createImageFromData (ECF_A8R8G8B8,
                        core::dimension2d<s32> (width, height), data);
        ITexture *tex = drv->addTexture ("texx", img);

        delete [] data;

        while (dev->run ()) {
                drv->beginScene (true, true, SColor (255, 100, 100, 100));
                smgr->drawAll ();
                drv->draw2DImage (tex, core::position2d<s32> (50, 100), core::rect <s32> (0, 0, width, height), 0, SColor (255, 255, 255, 255), true);
                drv->endScene ();
        }
        dev->drop ();

        FT_Done_Face (face);
        FT_Done_FreeType (library);

        return 0;
It only displays single TTF character
Open Source all the way, baby ;)
OSRPG
kornerr
Posts: 245
Joined: Thu Jul 06, 2006 9:57 am
Location: Russia, Siberia, Kemerovo
Contact:

Post by kornerr »

The second example is all I could code learning two FreeType2 tutorials. The result is not the best, though.
And code has at least three drawbacks:
1) hard-coded amount of characters per font size;
2) since the amount is hard-coded, memory is allocated for all 72 font sizes and (in my case) 1299 characters each. That only contains ints and short ints though, so memory usage is not really high;
3) the more longer strings you display, the lower your FPS is; this is the most awful thing, because looping through all the characters in a string and drawing its corresponding textures is really not the fastest way... but i don't know any other way... except for joining these character textures into single one string texture and then displaying that texture without looping. That would greatly increase FPS, but it doesn't suit for multi-line strings...

Makefile for Linux:

Code: Select all

FLAGS = `freetype-config --cflags` -I/usr/local/include/irrlicht
LIBS = `freetype-config --libs` -lIrrlicht

test: .objs/ttf.o .objs/main.o
        g++ -o test .objs/ttf.o .objs/main.o $(LIBS)

.objs/ttf.o: ttf.*
        g++ -o .objs/ttf.o -c ttf.cpp $(FLAGS)

.objs/main.o: main.cpp
        g++ -o .objs/main.o -c main.cpp $(FLAGS)

clean:
        rm -f test .objs/*.o
char_texs.h:

Code: Select all

#ifndef OSRPG_GAME_CHAR_TEXS_H
#define OSRPG_GAME_CHAR_TEXS_H

#include <irrlicht.h>

typedef struct {
        irr::video::ITexture *tex;
        irr::u16 char_index;
} CharTexs;

#endif // OSRPG_GAME_CHAR_TEXS_H
font_sizes.h:

Code: Select all

#ifndef OSRPG_GAME_FONT_SIZES_H
#define OSRPG_GAME_FONT_SIZES_H

#include "char_texs.h"
#include <vector>

typedef struct {
        std::vector<CharTexs> char_texs;
        irr::u8 font_size; // 1 - 72
} FontSizes;

#endif // OSRPG_GAME_FONT_SIZES_H
string_texs.h:

Code: Select all

#ifndef OSRPG_GAME_STRING_TEXS_H
#define OSRPG_GAME_STRING_TEXS_H

#include <irrlicht.h>

typedef struct {
        irr::video::ITexture *tex;
        irr::u16 x,
                y,
                w,
                h;
} StringTexs;

#endif // OSRPG_GAME_STRING_TEXS_H
cooked_strings.h:

Code: Select all

#ifndef OSRPG_GAME_COOKED_STRINGS_H
#define OSRPG_GAME_COOKED_STRINGS_H

#include "string_texs.h"
#include <vector>

typedef struct {
        std::vector<StringTexs> string_texs;
// x, y are not coords of drawing position...
        irr::u16 x,
                y,
                size;
        irr::video::SColor cl;
} CookedStrings;

#endif // OSRPG_GAME_COOKED_STRINGS_H
ttf.h:

Code: Select all

#ifndef OSRPG_GAME_TTF_H
#define OSRPG_GAME_TTF_H

#include <ft2build.h>
#include FT_FREETYPE_H
#include <freetype/ftglyph.h>
#include <string>
#include <vector>
#include "font_sizes.h"
#include "cooked_strings.h"

class TTF {
        public:
                TTF (irr::video::IVideoDriver *vd, const char *fn, irr::u16 xd,
                                irr::u16 yd);
                ~TTF ();
                irr::u32 CacheString (irr::u8 size, irr::s16 x, irr::s16 y, std::wstring s,
                                irr::video::SColor cl = irr::video::SColor (255, 0, 0, 0));
                void Clear ();
                void DisplayCache (irr::u32 string_index, irr::s16 x, irr::s16 y);
                void RemoveCache (irr::u32 string_index);
                void SetColor (irr::u32 string_index, irr::video::SColor cl);

        private:
                irr::u16 NextP2 (irr::u16 a);
                void SetSize (irr::u8 size);

                irr::video::IVideoDriver *drv;
                FT_Library lib;
                FT_Face face;
                FT_Bool use_kerning;

                std::vector<FT_Vector> pss;
                std::vector<FT_Glyph> ghs;

                std::vector<FontSizes> fss;
                std::vector<CookedStrings> css;

                irr::u16 xdpi,
                        ydpi;
};

#endif // OSRPG_GAME_TTF_H
ttf.cpp:

Code: Select all

#include "ttf.h"
#include <iostream>
using namespace irr;
using namespace video;
using namespace std;

TTF::TTF (IVideoDriver *vd, const char *fn, u16 xd, u16 yd) {
        xdpi = xd;
        ydpi = yd;
        drv = vd;
        if (FT_Init_FreeType (&lib))
                cout << "Can't init lib\n";
        s8 error = FT_New_Face (lib, fn, 0, &face);
        if (error)
                cout << "Unsupported format\n";
        else
                if (error)
                        cout << "Can't init face\n";
        use_kerning = FT_HAS_KERNING (face);
        SetSize (16);
        for (u16 i = 0; i < 72; i++) {
                FontSizes fs;
                // 1299 characters maximum, this includes all cyrillic symbols I may need :P
                // I know this is no good to hard-code number of possible characters
                // It'd be better to create vector with [index, character]
                // May be I'll do it later...
                for (u16 j = 0; j < 1300; j++) {
                        CharTexs ct;
                        ct.tex = 0;
                        ct.char_index = j;
                        fs.char_texs.push_back (ct);
                }
                fs.font_size = i;
                fss.push_back (fs);
        }
}

TTF::~TTF () {
        Clear ();
        FT_Done_Face (face);
        FT_Done_FreeType (lib);
}

u32 TTF::CacheString (u8 size, s16 x, s16 y, wstring s, SColor cl) {
        SetSize (size);
        CookedStrings cooked;
        pss.clear ();
        ghs.clear ();
        u16 len = s.length ();
        u16 glyph_index = 0,
                previous = 0,
                pen_x = 0,
                pen_y = 0;
        for (u16 i = 0; i < len; i++) {
                u16 idx = (u16)s[i];
                glyph_index = FT_Get_Char_Index (face, idx);
                if (use_kerning && previous && glyph_index) {
                        FT_Vector delta;
                        FT_Get_Kerning (face, previous, glyph_index,
                                        FT_KERNING_DEFAULT, &delta);
                        pen_x += delta.x >> 6;
                }
                FT_Vector pos;
                pos.x = pen_x;
                pos.y = pen_y;
                pss.push_back (pos);
                if (FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT))
                        cout << "Can't load glyph\n";
                FT_Glyph gh;
                if (FT_Get_Glyph (face->glyph, &gh))
                        cout << "Can't get glyph\n";
                ghs.push_back (gh);
                pen_x += face->glyph->advance.x >> 6;
                previous = glyph_index;
        }
        for (u16 i = 0; i < len; i++) {
                FT_Glyph image;
                FT_Vector pen;
                image = ghs[i];
                pen.x = x + pss[i].x * 64;
                pen.y = y + pss[i].y;
                if (!FT_Glyph_To_Bitmap (&image, FT_RENDER_MODE_NORMAL, &pen, 0)) {
                        FT_BitmapGlyph bit = (FT_BitmapGlyph)image;
                        FT_Bitmap &bmp = bit->bitmap;
                        u16 w = NextP2 (bmp.width);
                        u16 h = NextP2 (bmp.rows);
                        u16 idx = (u16)s[i];
                        //cout << "char_texs index: " << idx << endl;
                        if (fss[size - 1].char_texs[idx].tex == 0) {
                                u32 *data = new u32[w * h];
                                for (u16 j = 0; j < h; j++)
                                        for (u16 i = 0; i < w; i++)
                                                data[i + j * w] =
                                                        (i >= bmp.width || j >= bmp.rows) ? 0 :
                                                        (bmp.buffer[i + j * bmp.width] << 24) | 0x00ffffff;
                                IImage *img = drv->createImageFromData (ECF_A8R8G8B8,
                                                core::dimension2d<s32> (w, h), data);
                                ITexture *tex = drv->addTexture ("data", img);
                                fss[size - 1].char_texs[idx].tex = tex;
                                img->drop ();
                                delete [] data;
                        }

                        StringTexs st;
                        st.tex = fss[size - 1].char_texs[idx].tex;
                        st.x = bit->left;
                        st.y = y - bit->top;
                        core::dimension2d<s32> dim = st.tex->getSize ();
                        st.w = dim.Width;
                        st.h = dim.Height;
                        cooked.string_texs.push_back (st);
                        FT_Done_Glyph (image);
                }
                else {
                        cout << "ERROR. Can't proccess: " << s[i] << endl;
                }
        }
        cooked.x = x;
        cooked.y = y;
        cooked.cl = cl;
        cooked.size = cooked.string_texs.size ();
        css.push_back (cooked);
        return css.size () - 1;
}

void TTF::Clear () {
        for (u32 i = 0; i < css.size (); i++)
                css.erase (css.begin () + i);
}

void TTF::DisplayCache (u32 string_index, s16 x, s16 y) {
        CookedStrings *cooked = &css[string_index];
        for (u16 i = 0; i < cooked->size; i++)
                drv->draw2DImage (cooked->string_texs[i].tex,
                                core::position2d<s32> (
                                        x + cooked->string_texs[i].x /*- cooked.x*/,
                                        y + cooked->string_texs[i].y - cooked->y),
                                core::rect<s32> (0, 0, cooked->string_texs[i].w,
                                        cooked->string_texs[i].h), 0, cooked->cl, true);
}

void TTF::RemoveCache (irr::u32 string_index) {
        css.erase (css.begin () + string_index);
}

void TTF::SetColor (u32 string_index, SColor cl) {
        CookedStrings *cooked = &css[string_index];
        cooked->cl = cl;
}

u16 TTF::NextP2 (u16 a) {
        u16 b = 1;
        while (b < a)
                b <<= 1;
        return b;
}

void TTF::SetSize (u8 size) {
        if (size > 72 && size < 1) {
                cout << "Incorrect font size\n";
                return;
        }
        if (FT_Set_Char_Size (face, 0, size * 64, xdpi, ydpi))
                cout << "Can't set font size\n";
}
main.cpp:

Code: Select all

#include "ttf.h"
using namespace irr;
using namespace scene;
using namespace video;
#include <iostream>
using namespace std;

//#define SINGLE_CYCLE

int main () {
	IrrlichtDevice *dev = createDevice (EDT_OPENGL, core::dimension2d<s32> (800, 600),
			32, false, false, false, 0);
	IVideoDriver *drv = dev->getVideoDriver ();
	ISceneManager *smgr = dev->getSceneManager ();
	//dev->setWindowCaption (L"Irrlicht 1.2 & FreeType2 2.2");
	ICameraSceneNode *cam = smgr->addCameraSceneNode ();
	cam->setInputReceiverEnabled (false);

	TTF *ttf = new TTF (drv, "arial.ttf", 120, 120);
	//string s = "John is hiding far from me AVI Micro$oft $uX OSRPG Rulez!";
	wstring s = L"Лестер, будешь заниматься проектом OSRPG? I can see through you ;)";
	//wstring s = L"Real short string";
	// The X, Y here mean where char glyph will be created and taken at
	// I.e., if it will be created outside ViewPort, char texture will be empty
	// This is the second drawback of this TTF code
	u32 john10 = ttf->CacheString (10, 10, 50, s);
	u32 john14 = ttf->CacheString (14, 0, 50, s);
	u32 john16 = ttf->CacheString (16, 0, 50, s);
	u32 some10 = ttf->CacheString (10, 0, 50, L"KoЯn - Somebody, someone :D");
	u32 irrft2 = ttf->CacheString (18, 0, 50, L"#Irrlicht & #FreeType2 Example$",
			SColor (255, 50, 100, 0));
	// Just to show the reuse of a handle
	ttf->RemoveCache (irrft2);
	irrft2 = ttf->CacheString (24, 0, 50, L"Irrlicht & FreeType2 eXample",
			SColor (255, 84, 0, 0));
	// A bunch of strings
	wstring sarr [] = {
		L"A, B, C, D, E, F, G.",
		L"John is hiding far from me.",
		L"Looking here, looking there",
		L"I can't see him anywhere.",
	};
	u32 en_verses[4];
	for (u8 i = 0; i < 4; i++)
		en_verses[i] = ttf->CacheString (14, 50, 50, sarr[i],
				SColor (255, 20, 50, 20));

	int last_fps = -1;
#ifdef SINGLE_CYCLE
	dev->run ();
#else
	while (dev->run ()) {
#endif
		drv->beginScene (true, true, SColor (255, 255, 255, 255));
		smgr->drawAll ();
		ttf->DisplayCache (john16, 10, 30);
		ttf->DisplayCache (john14, 10, 70);
		ttf->DisplayCache (john10, 10, 110);
		ttf->DisplayCache (some10, 10, 150);
		ttf->DisplayCache (irrft2, 10, 190);
		for (u8 i = 0; i < 4; i++) ttf->DisplayCache (en_verses[i], 14, 220 + i * 20);
		drv->endScene ();
		int fps = drv->getFPS ();
		if (last_fps != fps) {
			core::stringw s = L"Irrlicht 1.2 & FreeType2 2.2  FPS: ";
			s += fps;
			dev->setWindowCaption (s.c_str ());
			last_fps = fps;
		}
#ifdef SINGLE_CYCLE
#else
	}
#endif
	dev->drop ();
	// All string handles will be removed when you delete TTF instance
	// Memory will be freed as well, of course. It should be so at least
	delete ttf;

	return 0;
}
Result should look like this
Open Source all the way, baby ;)
OSRPG
lester
Posts: 86
Joined: Mon Jan 29, 2007 3:33 pm

Post by lester »

What can I say about this? I've allready implemented truetype feature but in a c# wrapper to irrlicht, irrlicht.netcp. The result can be seen here

http://i16.tinypic.com/4bh25n4.png

P.S. And you, korrner, are one named 'scuko' =)
Last edited by lester on Tue Feb 13, 2007 1:05 pm, edited 1 time in total.
kornerr
Posts: 245
Joined: Thu Jul 06, 2006 9:57 am
Location: Russia, Siberia, Kemerovo
Contact:

Post by kornerr »

Your support sucks as much as GUITTFont in another thread, because i can see incorrect distance between glyphs.
Mine also sucks, but not that explicitly :)
Open Source all the way, baby ;)
OSRPG
lester
Posts: 86
Joined: Mon Jan 29, 2007 3:33 pm

Post by lester »

Nope, that is not the CGUITTFont problem. That is, the font I'm using consists of japan glyphs, and, what is a great surprise, cyrillic chars there whence have undertaken, but with incorrect kerning. So that is the font problem, not engine.
kornerr
Posts: 245
Joined: Thu Jul 06, 2006 9:57 am
Location: Russia, Siberia, Kemerovo
Contact:

Post by kornerr »

I'm talking about "i" and "l" letters.
You must be blind if you can't see GREAT distance there where they are used :P
Open Source all the way, baby ;)
OSRPG
lester
Posts: 86
Joined: Mon Jan 29, 2007 3:33 pm

Post by lester »

I'm saying to you, since i'm using the japan font, the latin and cyrillic glyphs are not supposed to be ideal. I'll show you the other screenshot but later. You will see.
Post Reply