📜 ⬆️ ⬇️

OGRE 3D and SDL Duet

Recently, there began to appear articles related to the engine OGRE, incl. and a post on creating a simple program, so I decided not to postpone the matter and write this article. Once, not so long ago, I already wrote a small superficial review of the SDL library, and here, as a continuation, I will give a specific example of using this library, namely, how to make it work together with the engine.

Intro


First, I will answer the question - why? OGRE itself works on most popular platforms, it is able to create a window for visualization, and for handling user input there are many lessons at the office. The site engine is recommended to use the OIS library. This is true, and before that I used the engine in conjunction with this library until I had to write under Linux. Then there were unpleasant moments. For some reason, the OGRE window often refused to respond to dragging the mouse, often the image flickered in it (there was a suspicion of a conflict with Compiz or Emerald); input with the help of OIS could not boast of decent speed, and after using the mouse for a few seconds, I wanted to close the program as soon as possible. Then I decided to try SDL as an alternative to OIS. The library handled it very successfully with its responsibilities - creating a window and processing input, while showing a decent speed.

Program frame


So, we need the OGRE SDK and SDL SDK . I will not describe the process of their installation, because it is different in different systems, and there are a lot of simple lessons on this topic. Let's go straight to the point. The first task is to write the program code in which the SDL will be engaged in creating the window and processing the input, and OGRE will only render to another window for it. We will try to make our code portable at least between Windows and Linux.

To begin with, we initialize the SDL and create a window with it:
#include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  1. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  2. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  3. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  4. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  5. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  6. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  7. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  8. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  9. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  10. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  11. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  12. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  13. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
  14. #include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .
#include <Ogre.h> #include <SDL.h> #ifdef _WINDOWS #include <SDL_syswm.h> #endif ... // SDL SDL_Init(SDL_INIT_VIDEO); // SDL_putenv(( char *) "SDL_VIDEO_CENTERED=true" ); // 800x600@16 OpenGL SDL_SetVideoMode(800, 600, 16, SDL_OPENGL); // SDL_WM_SetCaption( "Demo window" , NULL); * This source code was highlighted with Source Code Highlighter .

Now you need to configure OGRE, install a visualizer for it. How to code this part of the program is purely individual preference of each. Here I reserved the right to manually download only the most necessary plugins and set the necessary parameters. The main thing is not to create a window using OGRE:
  1. // Start the initialization of OGRE by creating a Root object
  2. Ogre :: Root * root = new Ogre :: Root ();
  3. // Load the OpenGL visualization plugin from the current folder (or from another location)
  4. #ifdef _WINDOWS
  5. root-> loadPlugin ( "./RenderSystem_GL.dll" );
  6. #else
  7. root-> loadPlugin ( "./RenderSystem_GL.so" );
  8. #endif
  9. // Create the visualizer itself
  10. Ogre :: RenderSystem * rs = root-> getRenderSystemByName ( "OpenGL Rendering Subsystem" );
  11. // ... make it current
  12. root-> setRenderSystem (rs);
  13. // ... and set up the video mode
  14. rs-> setConfigOption ( "Full Screen" , "no" );
  15. rs-> setConfigOption ( "Video Mode" , "800 x 600 16-bit color" );
  16. // Initialize OGRE, but without automatic window creation
  17. root-> initialise ( false );
* This source code was highlighted with Source Code Highlighter .

The most interesting thing is to tell OGRE that the OpenGL context has already been received, and it needs to be rendered:
  1. // Set the parameters for creating the OGRE window.
  2. // Actually no window is created. We just point OGRE where
  3. // make it visualize
  4. Ogre :: NameValuePairList params ;
  5. #ifdef _WINDOWS
  6. SDL_SysWMinfo info;
  7. SDL_VERSION (& info.version);
  8. SDL_GetWMInfo (& info);
  9. params [ "externalWindowHandle" ] = Ogre :: StringConverter :: toString (reinterpret_cast <size_t> (info.window));
  10. params [ "externalGLContent" ] = Ogre :: StringConverter :: toString (reinterpret_cast <size_t> (info.hglrc));
  11. params [ "externalGLControl" ] = Ogre :: String ( "True" );
  12. #else
  13. // Under Linux a little easier
  14. params [ "currentGLContext" ] = Ogre :: String ( "True" );
  15. #endif
  16. // Create a "virtual" window with the selected parameters
  17. Ogre :: RenderWindow * wnd = root-> createRenderWindow ( "Main window" , 800, 600, false , & params );
  18. wnd-> setVisible ( true );
* This source code was highlighted with Source Code Highlighter .

It's time to finish with the initialization and go to the working part:
  1. // Here we will add the next event for processing
  2. SDL_Event evt;
  3. // Yes, the loop should go on
  4. bool running = true ;
  5. // The main rendering cycle
  6. while (running)
  7. {
  8. // Read the system events and input events received since the previous frame
  9. while (SDL_PollEvent (& evt))
  10. {
  11. // If the user closed the window, stop the cycle
  12. if (evt.type == SDL_QUIT)
  13. running = false ;
  14. }
  15. // Draw one frame
  16. root-> renderOneFrame ();
  17. // For some reason, the need to call SDL_GL_SwapBuffers (); There is only in Windows.
  18. // On Linux, it works fine without it and is buggy if you use it.
  19. #ifdef _WINDOWS
  20. SDL_GL_SwapBuffers ();
  21. #endif
  22. }
* This source code was highlighted with Source Code Highlighter .

At the end of the program, do not forget to clean up after you:
  1. // Delete the main OGRE object, freeing up all the resources used by the engine
  2. delete root;
  3. // Close SDL
  4. SDL_Quit ();
* This source code was highlighted with Source Code Highlighter .

The code is rather short, comments take up half the space. After compiling and running the program, a window appears with incomprehensible content:
image
This is an expected result, because at this stage not a single viewport or a single camera was created from which OGRE could render. Therefore, all that is displayed in the window is arbitrary data that is in the video memory.
')

Input manager


Now it would be nice to make a small convenient system for catching events and sending them to listeners. We first describe the listener interfaces:
  1. // Input.h
  2. #include <list>
  3. #include <SDL.h>
  4. /// Keyboard Event Listener Interface
  5. class KeyboardListener
  6. {
  7. public :
  8. virtual ~ KeyboardListener () {}
  9. virtual void onKeyboardEvent ( const SDL_KeyboardEvent & evt) = 0;
  10. };
  11. /// Mouse Event Listener Interface
  12. class MouseListener
  13. {
  14. public :
  15. virtual ~ MouseListener () {}
  16. virtual void onMouseMotion ( const SDL_MouseMotionEvent & evt) = 0;
  17. virtual void onMouseBtn ( const SDL_MouseButtonEvent & evt) = 0;
  18. };
  19. /// Listener interface for all other types of events
  20. class EventListener
  21. {
  22. public :
  23. virtual ~ EventListener () {}
  24. virtual void onEvent ( const SDL_Event & evt) = 0;
  25. };
* This source code was highlighted with Source Code Highlighter .

By defining listener interfaces, you can write the input manager class itself:
  1. class InputManager
  2. {
  3. private :
  4. typedef std :: list <KeyboardListener *> KeyboardListenersList;
  5. typedef std :: list <MouseListener *> MouseListenersList;
  6. typedef std :: list <EventListener *> OtherEventsListenersList;
  7. private :
  8. KeyboardListenersList kb_listeners;
  9. MouseListenersList mouse_listeners;
  10. OtherEventsListenersList other_listeners;
  11. public :
  12. InputManager () {}
  13. ~ InputManager () {}
  14. /// Captures and handles events
  15. void capture ();
  16. /// Registers the event listener keyboard
  17. void regKeyboardListener (KeyboardListener * l);
  18. /// Unregisters the keyboard event listener
  19. void unregKeyboardListener (KeyboardListener * l);
  20. /// Registers a mouse event listener.
  21. void regMouseListener (MouseListener * l);
  22. /// Unregisters the mouse event listener.
  23. void unregMouseListener (MouseListener * l);
  24. /// Registers a general event listener
  25. void regEventListener (EventListener * l);
  26. /// Unregisters an event listener of any type
  27. void unregEventListener (EventListener * l);
  28. };
* This source code was highlighted with Source Code Highlighter .

We now turn to the implementation of the class. The main interest is the capture () method:
  1. SDL_Event evt;
  2. while (SDL_PollEvent (& evt))
  3. {
  4. switch (evt.type)
  5. {
  6. // keyboard event
  7. case SDL_KEYUP: case SDL_KEYDOWN:
  8. {
  9. KeyboardListenersList :: const_iterator kb_it = kb_listeners.begin (), kb_end = kb_listeners.end ();
  10. for (; kb_it! = kb_end; ++ kb_it)
  11. (* kb_it) -> onKeyboardEvent (evt.key);
  12. break ;
  13. }
  14. // Mouse event
  15. case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: case SDL_MOUSEMOTION:
  16. {
  17. MouseListenersList :: const_iterator m_it = mouse_listeners.begin (), m_end = mouse_listeners.end ();
  18. for (; m_it! = m_end; ++ m_it)
  19. {
  20. if (evt.type == SDL_MOUSEBUTTONDOWN || evt.type == SDL_MOUSEBUTTONUP)
  21. (* m_it) -> onMouseBtn (evt.button);
  22. if (evt.type == SDL_MOUSEMOTION)
  23. (* m_it) -> onMouseMotion (evt.motion);
  24. }
  25. break ;
  26. }
  27. // All other events
  28. default :
  29. OtherEventsListenersList :: const_iterator other_it = other_listeners.begin (), other_end = other_listeners.end ();
  30. for (; other_it! = other_end; ++ other_it)
  31. (* other_it) -> onEvent (evt);
  32. }
  33. }
* This source code was highlighted with Source Code Highlighter .

This method retrieves events from the queue and sends them to the right listeners. The code is easy to adapt to listen to different specific types of events. In fact, you can create quite a few highly specialized listeners, in this case I gave only examples of mouse and keyboard listeners.
It remains quite a bit - to implement methods for registering listeners. Everything is simple, so I will give the implementation code only for the methods of registering keyboard listeners, the rest are implemented by analogy:
  1. void InputManager :: regKeyboardListener (KeyboardListener * l)
  2. {
  3. if (l && std :: find (kb_listeners.begin (), kb_listeners.end (), l) == kb_listeners.end ())
  4. kb_listeners.push_back (l);
  5. }
  6. void InputManager :: unregKeyboardListener (KeyboardListener * l)
  7. {
  8. if (l)
  9. kb_listeners.remove (l);
  10. }
* This source code was highlighted with Source Code Highlighter .

Now we have a decent input manager, not ideal of course, but giving room for imagination in terms of further improvement. Using the input manager, the main program loop can be rewritten:
  1. InputManager * imgr = new InputManager ();
  2. while (! stop)
  3. {
  4. imgr-> capture ();
  5. root-> renderOneFrame ();
  6. ...
  7. }
  8. delete imgr;
* This source code was highlighted with Source Code Highlighter .

For event handling, you will need to create classes that implement the appropriate interfaces. At a minimum, it is worth catching the SDL_QUIT event to correctly terminate the program :)

At last


I must say that I don’t find it necessary to use SDL, but the library is nonetheless very enjoyable to use. The API, although fully written in C, is not inferior in terms of convenience to the object-oriented OIS interface. Binding to OGRE also does not bring any particular inconvenience. OGRE, in turn, showed once again its performance in conditions of extreme use :)

Source: https://habr.com/ru/post/86207/


All Articles