Difference between revisions of "Using CEGUI with Producer and OpenGL"
m (Robot: Cosmetic changes) |
|||
(One intermediate revision by one other user not shown) | |||
Line 1: | Line 1: | ||
− | [http://www.andesengineering.com/Producer/ Producer] is a cross platform toolkit, best explained on its website. | + | [http://www.andesengineering.com/Producer/ Producer] is a cross platform toolkit, best explained on its website. I use it for many things, but creating a window, or Render Surface, is the most fundamental task. CEGUI requires knowledge of the window, mouse, and keyboard interaction. I will show how you can do that with some example code. |
=== Defining a Producer Viewer === | === Defining a Producer Viewer === | ||
− | The first step is to create some sort of "viewer" with Producer, basically make a Producer::RenderSurface and a Producer::KeyboardMouse. | + | The first step is to create some sort of "viewer" with Producer, basically make a Producer::RenderSurface and a Producer::KeyboardMouse. Both of these classes are necessary for using CEGUI correctly. Your job is just to create these so they can be used later. There is no CEGUI code involved at this point. So, here is some example viewer code: |
#include "Viewer.h" | #include "Viewer.h" | ||
Line 37: | Line 37: | ||
} | } | ||
− | The InitGL and InitCEGUI functors are both specific to using CEGUI with Producer, and are quite important steps. | + | The InitGL and InitCEGUI functors are both specific to using CEGUI with Producer, and are quite important steps. Much of these initializations are OpenGL specific, and therefore must occur within a thread with the right OpenGL context. The Producer::RenderSurface can execute one-time initializations if you add them to be called upon window realization, with the Producer::RenderSurface::addRealizeCallback function which takes an instance to a class that derives from Producer::RenderSurface::Callback. Both InitGL and InitCEGUI are classes which derive from this Callback. I'll show what they need to do later in the tutorial. |
− | Actually, a lot (more) is happening in the Application's constructor. | + | Actually, a lot (more) is happening in the Application's constructor. Much of this is specific to using Producer. I recommend reading the examples in Producer's distribution, but this example might sumarize some things for those of you new to using it. The Producer::Camera class needs a 'SceneHandler' to perform draw calls. This is the heart of the application's rendering. I am showing an application that is named 'TeapotSim'. It is just an example showing OpenGL's famous teapot spinning. The SceneHandler instance must be set, and that is passed as a pointer to the Producer::Camera::addSceneHandler function. The Camera instance will call the SceneHandler::draw function as part of the render loop. |
− | Another point in the render loop is the post-draw stage. | + | Another point in the render loop is the post-draw stage. I used this stage to do more drawing, just like what my simulation would be doing, but in this stage I will be drawing the CEGUI, so that it will always be drawn last, and on top of the other graphics. |
=== Initializing CEGUI for Producer === | === Initializing CEGUI for Producer === | ||
Line 72: | Line 72: | ||
} | } | ||
− | Producer uses OpenGL for rendering, so that is the renderer we must choose. | + | Producer uses OpenGL for rendering, so that is the renderer we must choose. Luckily, CEGUI provides this. If you want your application to use a CEGUI::ScriptModule, you will need to provide a when initializing the CEGUI::System, but that is for another lesson. A default font needs to be provided to the System, and during initialization is a good time to provide that, so my functor has required it. |
=== Rendering CEGUI with Producer === | === Rendering CEGUI with Producer === | ||
− | The application code showed a post draw callback being set. | + | The application code showed a post draw callback being set. Producer will execute this little callback at the right time, within the correct OpenGL context for OpenGL calls. Here is what needs to happen for CEGUI to appear on your screen each frame: |
void RenderCEGUI::operator ()(const Producer::Camera& c) | void RenderCEGUI::operator ()(const Producer::Camera& c) | ||
Line 83: | Line 83: | ||
=== Injecting Input to CEGUI via Producer === | === Injecting Input to CEGUI via Producer === | ||
− | This is where the real magic happens. | + | This is where the real magic happens. You must inject inputs into CEGUI because it does not poll the operating system for you. That is the job of a tool like Producer. For the most part, the following code works. There is a small bug that is only seen when resizing the Producer::RenderSurface (window). However, I think it provides insight into how injections need to be done for CEGUI to react to user input: |
/** \author John K. Grant | /** \author John K. Grant | ||
Line 285: | Line 285: | ||
return 0; | return 0; | ||
} | } | ||
+ | |||
+ | [[Category:Tutorials]] |
Latest revision as of 16:20, 26 February 2011
Producer is a cross platform toolkit, best explained on its website. I use it for many things, but creating a window, or Render Surface, is the most fundamental task. CEGUI requires knowledge of the window, mouse, and keyboard interaction. I will show how you can do that with some example code.
Contents
Defining a Producer Viewer
The first step is to create some sort of "viewer" with Producer, basically make a Producer::RenderSurface and a Producer::KeyboardMouse. Both of these classes are necessary for using CEGUI correctly. Your job is just to create these so they can be used later. There is no CEGUI code involved at this point. So, here is some example viewer code:
#include "Viewer.h" Viewer::Viewer() : _camera(new Producer::Camera()), _rs(0), _kbm(0) { _rs = _camera->getRenderSurface(); // a Camera creates a RenderSurface, grab it _kbm = new Producer::KeyboardMouse(_rs.get()); // make a new KeyboardMouse }
Initializing the Viewer in an Application
And an application might use this viewer, doing some important initializations:
Application::Application(const std::string& fontfile): _status(RUNNING), _viewer(new Viewer()), _sim(new TeapotSim()), _scheme_loader("MouseArrow"), _layout_loader(), _script_module(new ApplicationEventHandler()) { // OpenGL context specific initializations Producer::ref_ptr<Producer::RenderSurface> rs = _viewer->GetRenderSurface(); rs->addRealizeCallback(new InitGL() ); rs->addRealizeCallback(new InitCEGUI(fontfile,_script_module) ); rs->setWindowName( "Producer and CEGUI example" ); rs->setWindowRectangle(20,50,800,600); // rendering stuff _viewer->GetCamera()->setSceneHandler( _sim.get() ); // draw the simulation graphics _viewer->GetCamera()->addPostDrawCallback(new RenderCEGUI() ); // renders the GUI over scene // device input stuff Producer::ref_ptr<Producer::KeyboardMouse> kbm = _viewer->GetKeyboardMouse(); prce::Injector* injector = new prce::Injector( kbm.get() ); kbm->setCallback( injector ); kbm->startThread(); }
The InitGL and InitCEGUI functors are both specific to using CEGUI with Producer, and are quite important steps. Much of these initializations are OpenGL specific, and therefore must occur within a thread with the right OpenGL context. The Producer::RenderSurface can execute one-time initializations if you add them to be called upon window realization, with the Producer::RenderSurface::addRealizeCallback function which takes an instance to a class that derives from Producer::RenderSurface::Callback. Both InitGL and InitCEGUI are classes which derive from this Callback. I'll show what they need to do later in the tutorial.
Actually, a lot (more) is happening in the Application's constructor. Much of this is specific to using Producer. I recommend reading the examples in Producer's distribution, but this example might sumarize some things for those of you new to using it. The Producer::Camera class needs a 'SceneHandler' to perform draw calls. This is the heart of the application's rendering. I am showing an application that is named 'TeapotSim'. It is just an example showing OpenGL's famous teapot spinning. The SceneHandler instance must be set, and that is passed as a pointer to the Producer::Camera::addSceneHandler function. The Camera instance will call the SceneHandler::draw function as part of the render loop.
Another point in the render loop is the post-draw stage. I used this stage to do more drawing, just like what my simulation would be doing, but in this stage I will be drawing the CEGUI, so that it will always be drawn last, and on top of the other graphics.
Initializing CEGUI for Producer
Now, I want to show an example of initializing CEGUI:
#include <CEGUISystem.h> #include <renderers/OpenGLGUIRenderer/openglrenderer.h> #include <CEGUIScriptModule.h> #include <CEGUIFontManager.h> #include "InitCEGUI.h" InitCEGUI::InitCEGUI(const std::string& font, CEGUI::ScriptModule* s): _sm(s), _font(font) { } InitCEGUI::~InitCEGUI() { } void InitCEGUI::operator ()(const Producer::RenderSurface& rs) { if( _sm ) new CEGUI::System( new CEGUI::OpenGLRenderer(0), _sm ); else new CEGUI::System( new CEGUI::OpenGLRenderer(0) ); CEGUI::FontManager* fm = CEGUI::FontManager::getSingletonPtr(); CEGUI::Font* f = fm->createFont( _font ); }
Producer uses OpenGL for rendering, so that is the renderer we must choose. Luckily, CEGUI provides this. If you want your application to use a CEGUI::ScriptModule, you will need to provide a when initializing the CEGUI::System, but that is for another lesson. A default font needs to be provided to the System, and during initialization is a good time to provide that, so my functor has required it.
Rendering CEGUI with Producer
The application code showed a post draw callback being set. Producer will execute this little callback at the right time, within the correct OpenGL context for OpenGL calls. Here is what needs to happen for CEGUI to appear on your screen each frame:
void RenderCEGUI::operator ()(const Producer::Camera& c) { CEGUI::System::getSingleton().renderGUI(); }
Injecting Input to CEGUI via Producer
This is where the real magic happens. You must inject inputs into CEGUI because it does not poll the operating system for you. That is the job of a tool like Producer. For the most part, the following code works. There is a small bug that is only seen when resizing the Producer::RenderSurface (window). However, I think it provides insight into how injections need to be done for CEGUI to react to user input:
/** \author John K. Grant * \date July, 14 2005. * This code is public domain, free for use, with the author having no responsibility or liability. */ #include <CEGUISystem.h> #include "SpecialKeyMapFunctor.h" #include "Injector.h" using namespace prce; Injector::Injector(Producer::KeyboardMouse* kbm): _kbm(kbm) { prce::SpecialKeyMapFunctor specials; _keys = specials(); } Injector::~Injector() { } /** changes from producer coordinates to normalized CE coordinates */ void Injector::_producer_to_cegui(float& mx, float& my) { // --- horizontal math // ph = 1.0 - -1.0 = 2.0 // total horizontal distance in producer // ch = 1.0 - 0.0 = 1.0 // total horizontal distance in ce // --- vertical math // pv = 1.0 - -1.0 = 2.0 // total vertical distance in producer // cv = 0.0 - 1.0 = -1.0 // total vertical distance in ce // cex = cx + px * (ch/2 / ph/2) = 0.5 + px * ( 0.5/1.0) = 0.5 + px*0.5 // cey = cy + py * (cv/2 / pv/2) = 0.5 + py * (-0.5/1.0) = 0.5 - py*0.5 // where cx is the "center" x value in ce // and cy is the "center" y value in ce float cex(0.5 + mx*0.5); float cey(0.5 - my*0.5); mx = cex; my = cey; } /** takes normalized CEGUI coordinates and converts * into pixel values for the screen. */ void Injector::_convert_to_screen(float& mx, float& my) { unsigned int width( _kbm->getRenderSurface()->getWindowWidth() ); unsigned int height( _kbm->getRenderSurface()->getWindowHeight() ); // the above yields the same results as the code below //int wx(0), wy(0); //unsigned int width(0), height(0); //_kbm->getRenderSurface()->getWindowRectangle(wx,wy,width,height); float pixel_x = (float)width * mx; float pixel_y = (float)height * my; mx = pixel_x; my = pixel_y; } void Injector::mouseMotion(float px,float py) { _producer_to_cegui(px,py); _convert_to_screen(px,py); CEGUI::System::getSingleton().injectMousePosition(px, py); } void Injector::passiveMouseMotion(float px,float py) { _producer_to_cegui(px,py); _convert_to_screen(px,py); CEGUI::System::getSingleton().injectMousePosition(px, py); } void Injector::buttonPress(float px,float py,unsigned int button) { _producer_to_cegui(px,py); _convert_to_screen(px,py); CEGUI::System::getSingleton().injectMousePosition(px, py); if( button == 1 ) // left CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton); else if( button == 2 ) // middle CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::MiddleButton); else if( button == 3 ) // right CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::RightButton); } void Injector::buttonRelease(float px,float py,unsigned int button) { _producer_to_cegui(px,py); _convert_to_screen(px,py); CEGUI::System::getSingleton().injectMousePosition(px, py); if( button == 1 ) // left CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::LeftButton); else if( button == 2 ) // middle CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::MiddleButton); else if( button == 3 ) // right CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton); } void Injector::doubleButtonPress(float px,float py,unsigned int button) { /** currently is implemented to only inject a single press. * CEGUI handles its own detection of the double click via * the "time pulse", which controls other things as well. */ _producer_to_cegui(px,py); _convert_to_screen(px,py); CEGUI::System::getSingleton().injectMousePosition(px,py); if( button == 1 ) // left CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton); else if( button == 2 ) // middle CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::MiddleButton); else if( button == 3 ) // right CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton); } void Injector::mouseScroll(enum Producer::KeyboardMouseCallback::ScrollingMotion motion) { switch( motion ) { case Producer::KeyboardMouseCallback::ScrollDown: { CEGUI::System::getSingleton().injectMouseWheelChange( -1 ); } break; case Producer::KeyboardMouseCallback::ScrollUp: { CEGUI::System::getSingleton().injectMouseWheelChange( 1 ); } break; default: // Producer::KeyboardMouseCallback::ScrollNone break; }; } void Injector::keyPress(Producer::KeyCharacter key) { CEGUI::System::getSingleton().injectKeyDown( static_cast<CEGUI::uint>(key) ); CEGUI::System::getSingleton().injectChar( static_cast<CEGUI::utf32>( key ) ); } void Injector::keyRelease(Producer::KeyCharacter key) { CEGUI::System::getSingleton().injectKeyUp( static_cast<CEGUI::uint>(key) ); } void Injector::specialKeyPress(Producer::KeyCharacter prkey) { KeyMap::iterator iter = _keys.find( prkey ); if( iter != _keys.end() ) { CEGUI::Key::Scan cekey = (*iter).second; CEGUI::System::getSingleton().injectKeyDown( static_cast<CEGUI::uint>(cekey) ); } CEGUI::System::getSingleton().injectChar( static_cast<CEGUI::utf32>( prkey ) ); } void Injector::specialKeyRelease(Producer::KeyCharacter prkey) { KeyMap::iterator iter = _keys.find( prkey ); if( iter != _keys.end() ) { CEGUI::Key::Scan cekey = (*iter).second; CEGUI::System::getSingleton().injectKeyUp( static_cast<CEGUI::uint>(cekey) ); } }
Using it all together
Here is what a simple main() will look like:
int main(unsigned int argc, char* argv[]) { if( argc < 4 ) { PrintUsage(); return 1; } Producer::ref_ptr<example::Application> app = new example::Application(argv[1]); app->GetViewer()->GetRenderSurface()->realize(); app->LoadCEGUIScheme( argv[2] ); app->LoadCEGUILayout( argv[3] ); while( !app->IsFinished() ) { app->GetViewer()->GetCamera()->frame(); } return 0; }