Page 1 of 2

Drawing custom text

Posted: Mon May 26, 2008 16:27
by Kevin
Hello,

I am attempting to get the CEGUI system to draw custom text that is not necessarily "on" a CEGUI::Window object. I read about the preferred use of a RenderCache, so I have been trying to use that. I first tried:

Code: Select all

CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
CEGUI::Window* wnd = wmgr.createWindow("DefaultWindow", "root");
wnd->getRenderCache().cacheText("this is some text", CEGUI::FontManager::getSingleton().getFont("Commonwealth-10"), (CEGUI::TextFormatting)2, CEGUI::Rect(100, 100, 500, 500), 0, CEGUI::ColourRect(CEGUI::colour(1.0f, 0.0f, 0.0f)));


and then I tried the same thing, except inheriting from CEGUI::Window, and putting the cacheText() call in an overloaded populateRenderCache() function (d_renderCache.cacheText(...); ).

Only the first draws the text (the second appears to do nothing), but there are a few problems. First, it does not draw the text right away - it only draws it when something changes... i.e., I move a window, or I hover over a button, etc. Second, the text is not nearly as "clear" as the other text (such as text on a button), even when using the same font (in this case, Commonwealth-10.font).

Another (minor) problem is when I try CEGUI::TextFormatting::Centred (or any of the other members of CEGUI::TextFormatting), I get the error

Code: Select all

error C2039: 'Centred' : is not a member of 'CEGUI::TextFormatting'


which is why I have put (CEGUI::TextFormatting)2 instead.

Is there a better way to draw text? Or am I just doing it in the wrong way?

Thanks in advance for any help!
Kevin

Posted: Tue May 27, 2008 08:39
by CrazyEddie
Hi, and welcome :)

I'm surprised the first example did anything at all :) In order to make this work consistently, ideally you'd need to call Window::requestRedraw on the window (this will clear the render cache in the next render pass, btw) and then make the calls to populate the cache - the issue is that there is currently no way to hook into the system at an appropriate time :(

So, this means that the second attempt, subclassing Window an overriding the populateRenderCache member, is indeed the correct way to go. I think that the reason it did not draw anything is maybe you need to call Window::requestRedraw on the window (whenever the text that is to be drawn should change).

It would probably be easier to create a simple looknfeel skin and a scheme mapping that applies the skin to a DefaultWindow. Though depending on your exact usage, maybe you do not want to go this route.

For the CEGUI::TextFormatting::Centred issue, I guess you're qualifying the Centred with the Enumeration name; don't forget in C++ enumerations do not introduce scope, so you should just put CEGUI::Centred.

HTH

CE.

Posted: Tue May 27, 2008 16:54
by Kevin
Thanks for the help!

I have now tried (according to my understanding):

Code: Select all

CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
MyWindow* wnd = (MyWindow*)wmgr.createWindow("DefaultWindow", "root");
CEGUI::System::getSingleton().setGUISheet((CEGUI::Window*)wnd);


where the MyWindow class inherits from CEGUI::Window and has only a constructor:

Code: Select all

MyWindow::MyWindow() : CEGUI::Window("DefaultWindow", "root")
{}


and an overridden populateRenderCache:

Code: Select all

void MyWindow::populateRenderCache()
{
  d_renderCache.cacheText("this is some text", CEGUI::FontManager::getSingleton().getFont("Commonwealth-10"), CEGUI::Centred, CEGUI::Rect(100, 100, 500, 500), 0, CEGUI::ColourRect(CEGUI::colour(1.0f, 0.0f, 0.0f)));
  requestRedraw();
}


However, the populateRenderCache above is never called.

The other problem I have had with inheriting from CEGUI::Window is that there is no default constructor for CEGUI::Window. Because of this I need to call a constructor for CEGUI::Window (as I have done above in the MyWindow constructor), and then re-assign it as in wmgr.createWindow() (not calling this causes it to never be drawn). From my understanding, this is creating two windows, but I have been unable to get around this.

Thanks again!
Kevin

Posted: Tue May 27, 2008 18:21
by CrazyEddie
Hi, this is a long-ish post, and you may want to print it out or something :)

Ok. A couple of points I have to make first, then an example of how to actually do this - which is, I believe, the first time I think I have demonstrated this here on the forum.

In CEGUI you need each windows sub-class to have an associated WindowFactory registered with the system; this is the system we use, and if you don't do this, things can quickly go wrong - especially when it comes to destroying things again.

The requestRedraw call didn't ought to go in the populateRenderCache member, since it effectively nullifies the gains achieved through caching.

Anyway, on to an example. This is generally based off of the code and what have you that you already posted.

First you need a window class, all windows have a name and a type; the base Window class takes this information in its constructor and the idiom is that the window factory will pass this information to you. Note that this is not required to be done this way, but in keeping with accepted norms, this is how I have arranged this example also.

Class declaration:

Code: Select all

class MyWindow : public CEGUI::Window
{
public:
    MyWindow(const CEGUI::String& type, const CEGUI::String& name);
    ~MyWindow() {}

protected:
    void populateRenderCache();
};


The implementations for the two members here are as follows:

In the constructor we pass our parameters onto the parent constructor, and we also re-default from a (0,0) size to a size that covers the entire parent element (i.e. the screen):

Code: Select all

MyWindow::MyWindow(const CEGUI::String& type, const CEGUI::String& name) : Window (type, name)
{
    setSize(CEGUI::UVector2(CEGUI::UDim(1,0), CEGUI::UDim(1,0)));
}


The populateRenderCache member is basically your code verbatim, except without the call to requestRedraw:

Code: Select all

void MyWindow::populateRenderCache()
{
    d_renderCache.cacheText("this is some text",
                            CEGUI::FontManager::getSingleton().getFont("Commonwealth-10"),
                            CEGUI::Centred,
                            CEGUI::Rect(100, 100, 500, 500), 0,
                            CEGUI::ColourRect(CEGUI::colour(1.0f, 0.0f, 0.0f)));
}


As stated, you need a WindowFactory based object that can be registered with the system to create instances of the window for you. A simple declaration of a WindowFactory class might look like this:

Code: Select all

class MyWindowFactory : public CEGUI::WindowFactory
{
public:
    MyWindowFactory();
    CEGUI::Window* createWindow(const CEGUI::String& name);
    void destroyWindow(CEGUI::Window* window);
};


The implementation of the WindowFactory members is really simple...

The constructor just passes a type string to the parent class:

Code: Select all

MyWindowFactory::MyWindowFactory() : CEGUI::WindowFactory("MyWindow")
{}


The createWindow member just creates an instance of the MYWindow class passing the type 'd_type' which is a field in the WindowFactory base class initialised earlier, and a name that we are passed by the system:

Code: Select all

CEGUI::Window* MyWindowFactory::createWindow(const CEGUI::String& name)
{
    return new MyWindow(d_type, name);
}


The destroyWindow member is trivial, and its purpose obvious:

Code: Select all

void MyWindowFactory::destroyWindow(CEGUI::Window* window)
{
    delete window;
}


Ok. Now that's over, before using the new window class in the system we have to add the factory so CEGUI knows about it. This is simple, but you need an instance of the factory class somewhere. In the core lib, these are kept around as static objects, in my test code I have a global instance - how you do it is up to you ;)

Code: Select all

// global factory instance
MyWindowFactory G_My_Window_Factory;


Finally, in your code you just need to register the factory, create an instance of the window, and ask for it to be redrawn:

Code: Select all

// register new window factory with the system
CEGUI::WindowFactoryManager::getSingleton().addFactory(&G_My_Window_Factory);

// create an instance of the new window type, named "root"
CEGUI::Window* root = CEGUI::WindowManager::getSingleton().createWindow("MyWindow", "root");
// set this window as the GUI root sheet
System::getSingleton().setGUISheet(root);

// request that the window be redrawn in the next rendering pass
root->requestRedraw();


HTH

CE

Posted: Tue May 27, 2008 19:21
by Kevin
Thanks a lot! That makes a lot of sense! It's a nice touch that the creator / owner takes the time and effort to help people out!

Kevin

Posted: Wed May 28, 2008 21:46
by Kevin
Hello again,

Thanks to your help, I am a lot closer than I was! I have successfully got it to draw text in the way you have described!

I guess I will be more specific as to what I am doing now:

I have a CEGUI::ScrollablePane, and in this pane I want to draw some text and some other things that are well-suited to OpenGL (lines, polygons, etc). Of course, it would be possible to draw the text as I am currently doing and draw the other things in plain OpenGL, translate and clip as necessary, but this seems like a bit of a hack, especially since what the scrollable pane does (i.e. moves its contents as the scrollbars move) is exactly what I want... for the text and the other things.

I have been experimenting a little bit with d_renderCache.cacheImage(...), but there are a number of problems. Of course, the cacheImage(...) function needs an Image object... but it doesn't seem possible to create an Image in the traditional sense. I tried:

- drew what I wanted in OpenGL
- saved it to memory using glReadPixels(...)
- created an OpenGLTexture using Texture::loadFromMemory(...)
- created a CEGUI::Imageset using this texture
- created an Image using this Imageset
- and finally, cached the image using d_renderCache.cacheImage(...)

and, as it likely obvious to you, this doesn't work - the constructors for both OpenGLTexture and Imageset are private.

So I guess my question is this: Is it possible to take pixels in memory and get CEGUI to draw them as a texture? And if so, how? :)

Thanks again!
Kevin

Posted: Thu May 29, 2008 08:37
by CrazyEddie
Hi,

In actual fact your Image creation algorithm is generally correct; due to various factors it is the way you'd currently have to approach this.

As far as protected constructors go, this is because mostly all these resources are managed by CEGUI and so are created via some other object in order that the system can keep track of things...

As such a CEGUI::Texture can be created via the Renderer object, and an Imageset is created via the ImagesetManager (I'm guessing you maybe have this part working already?).

If you already used these methods to create the Texture and Imageset, I'm not entirely sure why nothing is showing up - posting the actual code might be of help (though maybe not!).

Is the Image you create for the whole area of the Texture or just a portion? If it's for just a portion, is the data getting loaded to the CEGUI::Texture correctly (you might test it by setting an image to use the whole texture and seeing if anything shows up, and if so does it look right?

CE.

Posted: Thu May 29, 2008 19:48
by Kevin
Hello,

Thanks again! The problem was I was calling the constructors for OpenGLTexture and Imageset, which are, as you mentioned, protected, so it wouldn't even run. I have changed them to CEGUI::System::getSingleton().getRenderer()->createTexture(); and CEGUI::ImagesetManager::getSingleton().createImageset(...); respectively and now it runs. I even got it to display something, but not what it's supposed to display...

Just to test it, I am trying to get it to draw a white square with a blue diagonal line through it. I tried using GL_INT and both GL_RGB and GL_RGBA (one after the other) in glReadPixels(...), and the corresponding PF_RGB or PF_RGBA in Texture::loadFromMemory(...), and in both cases I get alternating turquoise and black (or transparent?) vertical stripes. I then discovered that apparently Texture::loadFromMemory(...) expects bytes, so I changed it to GL_BYTE in glReadPixels(...) (and changed the data type of the pixel array), and in this case I get nothing showing up at all. I researched a little and found that glReadPixels(...) is supposed to save each pixel in the order RGB or RGBA (the same order that Texture::loadFromMemory(...) expects), so I don't know what the problem is.

The other problem is if I don't get it to destroy the Imageset before creating it (e.g. CEGUI::ImagesetManager::getSingleton().destroyImageset(...); ), I get a CEGUI::AlreadyExistsException. There doesn't seem to be another option since it doesn't appear possible to modify the texture within an Imageset. However, it seems a little wasteful to have to destroy and re-create it every time the texture changes.

Thanks again!
Kevin

Posted: Fri May 30, 2008 00:01
by daves
Kevin wrote:Thanks a lot! That makes a lot of sense! It's a nice touch that the creator / owner takes the time and effort to help people out!

Kevin


Omg I so agree!!! You dont know how often I've been so thankful for the responses that I get from CE and the entire CEGUI team; always helpful, and always patient.

Posted: Fri May 30, 2008 00:11
by daves
Kevin wrote:Hello again,


I guess I will be more specific as to what I am doing now:

Kevin


Can you provide a picture showing the kind of "graphic" that you are interested in?

Posted: Fri May 30, 2008 08:53
by CrazyEddie
Hi

I'm not sure what the imagery issue is at the moment. Is the image you are defining on the imageset for the size of the whole texture or is it just a portion? If it's not the whole thing, can you try doing that just to be sure the image is not flipped or something.

The other problem is if I don't get it to destroy the Imageset before creating it (e.g. CEGUI::ImagesetManager::getSingleton().destroyImageset(...); ), I get a CEGUI::AlreadyExistsException. There doesn't seem to be another option since it doesn't appear possible to modify the texture within an Imageset. However, it seems a little wasteful to have to destroy and re-create it every time the texture changes.

So long as the CEGUI::Texture object does not change, you will be able to modify both the underlying GL texture content, and also make calls to the CEGUI::Texture to reload the texture from a buffer or file or anything else, and the Imageset will not care a jot ;) If the size of the image changes you will have to destroy and recreate the definition for that, but you should not need to tear the whole imageset / texture down and restart from scratch each time.

daves wrote:
Kevin wrote:Thanks a lot! That makes a lot of sense! It's a nice touch that the creator / owner takes the time and effort to help people out!

Kevin


Omg I so agree!!! You dont know how often I've been so thankful for the responses that I get from CE and the entire CEGUI team; always helpful, and always patient.

Though sometimes it may not have seemed like it (maybe not to you guys, but maybe to others) we all really do try our best :)

CE

Posted: Fri May 30, 2008 19:44
by Kevin
Hello again,

I'm not sure exactly if the image is the size of the whole texture or not since I can't find anywhere what units the Rect expects (i.e. The thing I'm trying to get it to draw is 100 x 100 pixels, so I'm not sure if it wants 0 ... 1, or 0 ... 100). Anyways, here is the relevant code that I have put in the populateRenderCache() function...

Code: Select all

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glColor3f(1.0f, 1.0f, 1.0f);

glBegin(GL_QUADS);
  glVertex3f(100, 100, 0.0f);
  glVertex3f(100, 200, 0.0f);
  glVertex3f(200, 200, 0.0f);
  glVertex3f(200, 100, 0.0f);
glEnd();

glColor3f(1.0f, 0.0f, 0.0f);
glLineWidth(5);

glBegin(GL_LINES);
  glVertex3f(100, 100, 0.0f);
  glVertex3f(200, 200, 0.0f);
glEnd();

char* pixels = new char[40000];
glReadPixels(100, 100, 100, 100, GL_RGBA, GL_BYTE, pixels);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

CEGUI::OpenGLTexture* tex = (CEGUI::OpenGLTexture*)CEGUI::System::getSingleton().getRenderer()->createTexture();
tex->loadFromMemory(pixels, 100, 100, CEGUI::Texture::PF_RGBA);
CEGUI::ImagesetManager::getSingleton().destroyImageset("imset");
CEGUI::Imageset* imset = CEGUI::ImagesetManager::getSingleton().createImageset("imset", tex);
imset->defineImage("im", CEGUI::Rect(0, 0, 100, 100), CEGUI::Point(0, 0));

d_renderCache.cacheImage(imset->getImage("im"), CEGUI::Rect(100, 100, 200, 200), 0, CEGUI::ColourRect(CEGUI::colour(1.0f, 1.0f, 1.0f)));


The resut of this is nothing showing up at all with a black background, and a solid square slightly darker than the background colour for any other colour. I have tried CEGUI::Rect(0, 0, 1, 1) and CEGUI::Rect(0, 0, 100, 100) in the imset->defineImage(...) call, and they both seem to do the same thing.

Thanks again for all the help!
Kevin

Posted: Sat May 31, 2008 07:48
by CrazyEddie
Hi,

I'm going to have to run a couple of tests so that I have more of an idea of what I'm talking about, since the code generally looks like it should be working.

The Imageset::defineImage function is expecting that the Rect describes an area in pixels; currently most things in the CEGUI public interface are in pixels, so if you're ever in doubt that would be a fair assumption.

I'll get back to you about the main part of the issue a little later.

CE.

Posted: Sat May 31, 2008 09:07
by CrazyEddie
A bit quicker than expected, but I have tested this out and the reason that nothing is showing up is because the gl drawing ops are not doing what you're expecting them to (but the (CEGUI side of thing is fine!) ;)

Basically the issue is because no gl state is being setup (the gl setup that CEGUI uses has not happened when the populateRenderCache call is made - so you'll have to do some setup prior to drawing anything. Depending on your needs this could be a one time thing (CEGUI will save and restore the state under OpenGL).

CE.

Posted: Sun Jun 01, 2008 17:35
by Kevin
Hello,

I'm not exactly sure what you mean. If I use the same gl code that draws the square and line elsewhere (i.e. not saving it as a texture, etc) it shows up fine. I can also get it to display properly various gui elements (like a framewindow, a button and the scrollbars of a scrollablepane).

In case it helps, the very first things the program does when it starts are these three functions (in this order):

Code: Select all

void initSDL()
{
  atexit (SDL_Quit);
  SDL_Init (SDL_INIT_VIDEO);
  SDL_WM_SetCaption("My Window", 0);
  SDL_Surface* screen = SDL_SetVideoMode (800, 600, 0, SDL_OPENGL);
  if (screen == NULL)
  {
      fprintf (stderr, "Error: %s\n", SDL_GetError ());
      exit (1);
  }
  SDL_ShowCursor (SDL_DISABLE);
  SDL_EnableUNICODE (SDL_ENABLE);
  SDL_EnableKeyRepeat (SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
}

void initGL()
{
  glViewport(0, 0, 800, 600);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0, 800, 600, 0, 1, -1);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClearDepth(1.0f);
  glDepthFunc(GL_LEQUAL);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);
  glShadeModel(GL_SMOOTH);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}

void initCEGUI()
{
  CEGUI::OpenGLRenderer * renderer = new CEGUI::OpenGLRenderer (0, 800, 600);
  new CEGUI::System (renderer);

  CEGUI::DefaultResourceProvider* rp = (CEGUI::DefaultResourceProvider*)(CEGUI::System::getSingleton().getResourceProvider());

  rp->setResourceGroupDirectory("schemes", "./datafiles/schemes/");
  rp->setResourceGroupDirectory("imagesets", "./datafiles/imagesets/");
  rp->setResourceGroupDirectory("fonts", "./datafiles/fonts/");
  rp->setResourceGroupDirectory("layouts", "./datafiles/layouts/");
  rp->setResourceGroupDirectory("looknfeels", "./datafiles/looknfeel/");
  rp->setResourceGroupDirectory("lua_scripts", "./datafiles/lua_scripts/");

  CEGUI::Font::setDefaultResourceGroup("fonts");
  CEGUI::Imageset::setDefaultResourceGroup("imagesets");
  CEGUI::WindowManager::setDefaultResourceGroup("layouts");
  CEGUI::WidgetLookManager::setDefaultResourceGroup("looknfeels");
  CEGUI::ScriptModule::setDefaultResourceGroup("lua_scripts");
  CEGUI::Scheme::setDefaultResourceGroup("schemes");

  CEGUI::Imageset* windowsImages = CEGUI::ImagesetManager::getSingleton().createImageset("WindowsLook.imageset");
  CEGUI::System::getSingleton().setDefaultMouseCursor(&windowsImages->getImage("MouseArrow"));
  CEGUI::FontManager::getSingleton().createFont("Commonwealth-10.font");
  CEGUI::WidgetLookManager::getSingleton().parseLookNFeelSpecification("WindowsLook.looknfeel");
  CEGUI::SchemeManager::getSingleton().loadScheme("WindowsLook.scheme");

  CEGUI::WindowFactoryManager::getSingleton().addFactory(&myWF);
}


Thanks again for the help! It really is appreciated!
Kevin