WxOgre for OGRE v1.4         A simple rendering widget for wxWindows (OGRE V1.4)

wxOgre for OGRE v1.4

This article provides an example of how OGRE and wxWidgets can work together in one application. This can be very useful when writing tools such as 3D world editors or 3D viewers.
The following C++ class is a quick and simple wxControl derived object that can be easily used with the GUI toolkit wxWidgets. It is defined as singleton and currently only supports a single OGRE window.

This uses the latest wxWidgets release (2.8) and the latest OGRE release v1.4. I also leave you notes on how to make this work with OIS as well.

Any trouble, you can discuss it on this thread: http://www.ogre3d.org/phpBB2/viewtopic.php?t=34889

Requirements

  • OGRE 1.4
  • wxWidgets 2.8


Make sure you link to wxCore, wxBase and wxAUI.

Limitations

  • Supports a single OGRE window only
  • Fully tested on Windows OS. Linux support is there but needs testing (if somebody was able to get this to work on MacOS please add your code).

OIS Notes

Make sure to pass the TOPMOST window to ois or else directinput won't run on windows and use the following options.

pl.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_NONEXCLUSIVE")));
    pl.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_FOREGROUND")));
    pl.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_NONEXCLUSIVE")));
    pl.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_FOREGROUND")));


Code



wxOgre.h

#ifndef __WXOGRE_H__
#define __WXOGRE_H__

#include "Ogre.h"
#include "wx/wx.h"

    /**
    @brief WX widget for and Ogre rendering window

    This WX widget is a self-contained SINGLETON Ogre rendering window; 
    meaning it contains all Ogre objects necessary to create a rendering 
    window and currently supports only one rendering window at a time!
    This is due to the limitation of the self contained class.
    
    @usage Simply create a new wxOgre object and pass a wxFrame as its 
    parent window. Then work with it just like ay other wxControl object.
    It can even be passed to an wxAUI pane.
    */
    class wxOgre : 
        public wxControl, 
        public Ogre::Singleton<wxOgre>
    {
        DECLARE_CLASS(wxOgre)
    public:
        /** A new wxOgre must receive a parent frame to which to attach 
        itself to */
        wxOgre (wxFrame* parent, Ogre::RenderSystem* renderSystem = 0);
        ~wxOgre();

        /** Renders a single Ogre frame */
        void update();

        /** Returns the currently used camera */
        inline Ogre::Camera* getCamera() const {return mCamera;}
        /** Sets a new camera for rendering */
        inline void setCamera(Ogre::Camera* camera){mCamera = camera;}

        inline Ogre::SceneManager* getSceneManager() const {return mSceneMgr;}

    protected:
        DECLARE_EVENT_TABLE()

    private:
        /** Creates an ogre rendering window and all other default objects
        such as the Ogre Root, default camera, default scene manager etc */ 
        void createOgreRenderWindow(Ogre::RenderSystem* renderSystem);
        /** Toggles the rendering timer on/off */
        void toggleTimerRendering();

        /** Callback function to a window resize event */
        void OnSize(wxSizeEvent& event);
        /** Callback function to a window paint event */
        void OnPaint(wxPaintEvent& event);
        /** Callback function to an EraseBackground event */
        void OnEraseBackground( wxEraseEvent& );
        /** Callback function to a timer "tick" event */
        void OnRenderTimer(wxTimerEvent& event);

        /* WX members */
        /** Rendering timer */
        wxTimer    mTimer;

        /* Ogre members */
        /** Local Ogre::Root pointer */
        Ogre::Root* mRoot;
        /** Local Ogre::Viewport pointer */
        Ogre::Viewport* mViewPort;
        /** Local Ogre::Camera pointer */
        Ogre::Camera* mCamera;
        /** Local Ogre::SceneManager pointer */
        Ogre::SceneManager* mSceneMgr;
        /** Local Ogre::RenderWindow pointer */
        Ogre::RenderWindow* mRenderWindow;

        /* Set to false until wxOgre is completely initialized */
        bool mIsInitialized;
    public:
        // *****************************************************

        // -----------------------------------------------------
        /**
        @remarks
        Why do we do this? Well, it's because the Singleton
        implementation is in a .h file, which means it gets compiled
        into anybody who includes it. This is needed for the
        Singleton template to work, but we actually only want it
        compiled into the implementation of the class based on the
        Singleton, not all of them. If we don't change this, we get
        link errors when trying to use the Singleton-based class from
        an outside dll.
        @par
        This method just delegates to the template version anyway,
        but the implementation stays in this single compilation unit,
        preventing link errors.
        */
        static wxOgre& getSingleton();
        /**
        @remarks
        Why do we do this? Well, it's because the Singleton
        implementation is in a .h file, which means it gets compiled
        into anybody who includes it. This is needed for the
        Singleton template to work, but we actually only want it
        compiled into the implementation of the class based on the
        Singleton, not all of them. If we don't change this, we get
        link errors when trying to use the Singleton-based class from
        an outside dll.
        @par
        This method just delegates to the template version anyway,
        but the implementation stays in this single compilation unit,
        preventing link errors.
        */
        static wxOgre* getSingletonPtr();

    };

#endif // __WXOGRE_H__


wxOgre.cpp

#include "wxOgre.h"

#ifdef __WXGTK__
#include <gdk/gdk.h>
#include <gtk/gtk.h> // just this should suffice as it should include gdk.h itself
/* Seems to be needed under Linux */
#include <wx/gtk/win_gtk.h>
#include <gdk/gdkx.h>
#include <GL/glx.h>
#endif

// Required for the timer
const long ID_RENDERTIMER = wxNewId();

// Required for WX
IMPLEMENT_CLASS(wxOgre, wxControl)

// Required for WX
BEGIN_EVENT_TABLE(wxOgre, wxControl)
    EVT_SIZE(wxOgre::OnSize)
    // EVT_PAINT(wxOgre::OnPaint) // Produces flickers and runs too fast!
    EVT_ERASE_BACKGROUND( wxOgre::OnEraseBackground )
    EVT_TIMER( ID_RENDERTIMER, wxOgre::OnRenderTimer )
END_EVENT_TABLE()

wxOgre::wxOgre(wxFrame* parent, Ogre::RenderSystem* renderSystem) : 
        wxControl(parent, -1),
        mTimer(this, ID_RENDERTIMER),
        mRoot(0),
        mViewPort(0),
        mCamera(0),
        mSceneMgr(0),
        mRenderWindow(0)
{
    mIsInitialized = false;
    
    // Create all Ogre objects
    createOgreRenderWindow(renderSystem);
    // Start the rendering timer
    toggleTimerRendering();

    mIsInitialized = true;
}

void wxOgre::createOgreRenderWindow(Ogre::RenderSystem* renderSystem)
{
    // See if an Ogre::Root already exists
    mRoot = Ogre::Root::getSingletonPtr();
    // If not, create one
    if(!mRoot)
    {
        mRoot = new Ogre::Root();
    }
    
    // If we got an Ogre::RenderSystem, we'll use that
    if(renderSystem)
    {
        mRoot->setRenderSystem(renderSystem);
    }
    // If not, maybe the Root already has one
    else if(!mRoot->getRenderSystem())
    {
        // At this point there are no rendersystems, so we
        // can try to load in the previous configuration
        if(!mRoot->restoreConfig())
        {
            // That failed so we have to show the Dialog
            if(!mRoot->showConfigDialog())
            {
                // If the user canceled that there's nothing else we can do!
                OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS, "No RenderSystem chosen", "wxOgre::createOgreRenderWindow");
            }
        }
    }

    mRenderWindow = mRoot->initialise(false);

    // --------------------
    // Create a new parameters list according to compiled OS
    Ogre::NameValuePairList params;
    Ogre::String handle;
#ifdef __WXMSW__
    handle = Ogre::StringConverter::toString((size_t)((HWND)GetHandle()));
#elif defined(__WXGTK__)
    // TODO: Someone test this. you might to use "parentWindowHandle" if this
    // does not work.  Ogre 1.2 + Linux + GLX platform wants a string of the
    // format display:screen:window, which has variable types ulong:uint:ulong.
    GtkWidget* widget = GetHandle();
    gtk_widget_realize( widget );    // Mandatory. Otherwise, a segfault happens.
    std::stringstream handleStream;
    Display* display = GDK_WINDOW_XDISPLAY( widget->window );
    Window wid = GDK_WINDOW_XWINDOW( widget->window );    // Window is a typedef for XID, which is a typedef for unsigned int
    /* Get the right display (DisplayString() returns ":display.screen") */
    std::string displayStr = DisplayString( display );
    displayStr = displayStr.substr( 1, ( displayStr.find( ".", 0 ) - 1 ) );
    /* Put all together */
    handleStream << displayStr << ':' << DefaultScreen( display ) << ':' << wid;
    handle = handleStream.str();
#else
    #error Not supported on this platform.
#endif
    params["externalWindowHandle"] = handle;

    // Get wx control window size
    int width;
    int height;
    GetSize(&width, &height);
    // Create the render window
    mRenderWindow = Ogre::Root::getSingleton().createRenderWindow("OgreRenderWindow", width, height, false, &params);

    // --------------------
    // Create the SceneManager, in this case a generic one
    mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC, "ExampleSMInstance");
    mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5f, 0.5f, 0.5f));

    // --------------------
    // Create the camera
    mCamera = mSceneMgr->createCamera("PlayerCam");
    // Position it at 500 in Z direction
    mCamera->setPosition(Ogre::Vector3(0,0,500));
    // Look back along -Z
    mCamera->lookAt(Ogre::Vector3(0,0,-300));
    mCamera->setNearClipDistance(5);
    // Set the viewport
    mViewPort = mRenderWindow->addViewport(mCamera); 
    // Set the background to match the wxWindow background color
    mViewPort->setBackgroundColour(Ogre::ColourValue(212.0f/255.0f, 208.0f/255.0f, 200.0f/255.0f, 1.0f)); 
}

void wxOgre::toggleTimerRendering()
{
    // Toggle Start/Stop
    if (mTimer.IsRunning())
        mTimer.Stop();
    mTimer.Start(10);
}

wxOgre::~wxOgre()
{
    // destroy Viewport and RenderWindow
    if (mViewPort)
    {
        mRenderWindow->removeViewport(mViewPort->getZOrder());
        mViewPort = 0;
    }

    Ogre::Root::getSingleton().detachRenderTarget(mRenderWindow);
    mRenderWindow = 0;
        delete mRoot;

}

void wxOgre::OnSize(wxSizeEvent& event)
{
    if(mIsInitialized){
        // Setting new size;
        int width;
        int height;
        GetSize(&width, &height);
        mRenderWindow->resize( width, height );
        // Letting Ogre know the window has been resized;
        mRenderWindow->windowMovedOrResized();
        // Set the aspect ratio for the new size;
        if (mCamera)
            mCamera->setAspectRatio(Ogre::Real(width) / Ogre::Real(height));
    
        update();
    }
}
void wxOgre::OnPaint(wxPaintEvent& event)
{
    if(mIsInitialized){
    //    update(); // Produces flickers and runs too fast!
    }
}
void wxOgre::OnEraseBackground( wxEraseEvent& )
{
    if(mIsInitialized){
        update();
    }
}
void wxOgre::OnRenderTimer(wxTimerEvent& event)
{
    if(mIsInitialized){
        update();
    }
}

void wxOgre::update()
{
    Ogre::Root::getSingletonPtr()->renderOneFrame();
}

template<> wxOgre* Ogre::Singleton<wxOgre>::ms_Singleton = 0;
wxOgre& wxOgre::getSingleton()
{
    return ( *ms_Singleton );
}

wxOgre* wxOgre::getSingletonPtr()
{
    return ms_Singleton;
}

Notes



Under Ubuntu 7.04, I wasn't able to make it work. Searching through various forum posts, I stumbled with this solution, that actually works here:

#elif defined(__WXGTK__)
  SetBackgroundStyle(wxBG_STYLE_CUSTOM);

  GtkWidget *widget = m_wxwindow;
  gtk_widget_set_double_buffered (widget, FALSE);
  gtk_widget_realize( widget );

  // grab the window object
  GdkWindow *gdkWin = GTK_PIZZA (widget)->bin_window;
  Display* display = GDK_WINDOW_XDISPLAY(gdkWin);
  Window wid = GDK_WINDOW_XWINDOW(gdkWin);

  std::stringstream str;

  // display
  str << (unsigned long)display << ':';

  // screen (returns "display.screen")
  std::string screenStr = DisplayString(display);
  std::string::size_type dotPos = screenStr.find(".");
  screenStr = screenStr.substr(dotPos+1, screenStr.size());
  str << screenStr << ':';

  // XID
  str << wid << ':';

  // retrieve XVisualInfo
  int attrlist[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_DEPTH_SIZE, 16, GLX_STENCIL_SIZE, 8, None };
  XVisualInfo* vi = glXChooseVisual(display, DefaultScreen(display), attrlist);
  str << (unsigned long)vi;

  handle = str.str();
#else


In addition to it, seems that the GTK2 development packages are required to build it. Download them with

sudo apt-get install libgtk2.0-dev


If someone confirms these notes are right, they should be merged into the article in order to support Linux.

<div align="right">Kencho 12:34, 29 October 2007 (GMT)</div>

Usage - Example with wxAUI

MainFrame.h

#ifndef __MAINFRAME_H__
#define __MAINFRAME_H__

#include "wx/wx.h"
#include "wx/aui/aui.h"
#include "wxOgre.h"

class MainFrame : public wxFrame
{
public:
    MainFrame(wxWindow* parent) : wxFrame(parent, -1, _("wxAUI Test"),
        wxDefaultPosition, wxSize(800,600),
        wxDEFAULT_FRAME_STYLE)                              
    {
        // notify wxAUI which frame to use
        m_mgr.SetFrame(this);

        // create several text controls
        wxTextCtrl* text1 = new wxTextCtrl(this, -1, _("Pane 1 - sample text"),
            wxDefaultPosition, wxSize(200,150),
            wxNO_BORDER | wxTE_MULTILINE);

        wxTextCtrl* text2 = new wxTextCtrl(this, -1, _("Pane 2 - sample text"),
            wxDefaultPosition, wxSize(200,150),
            wxNO_BORDER | wxTE_MULTILINE);

        wxTextCtrl* text3 = new wxTextCtrl(this, -1, _("Main content window"),
            wxDefaultPosition, wxSize(200,150),
            wxNO_BORDER | wxTE_MULTILINE);

        // add the panes to the manager
        m_mgr.AddPane(text1, wxLEFT, wxT("Pane Number One"));
        m_mgr.AddPane(text2, wxBOTTOM, wxT("Pane Number Two"));
        m_mgr.AddPane(text3, wxTOP);

        // ************************
        wxOgrePane = new wxOgre(this);
        m_mgr.AddPane(wxOgrePane, wxCENTER, wxT("Ogre Pane"));

        // tell the manager to "commit" all the changes just made
        m_mgr.Update();
    }

    ~MainFrame()
    {
        // deinitialize the frame manager
        m_mgr.UnInit();
    }

    void UpdateOgre()
    {
        wxOgrePane->update();
    }

    

private:
    wxAuiManager m_mgr;
    wxOgre* wxOgrePane;
};

// our normal wxApp-derived class, as usual
class MyApp : public wxApp
{
public:
    MainFrame* frame;
    bool OnInit()
    {
        frame = new MainFrame(NULL);
        SetTopWindow(frame);
        frame->Show();
        frame->UpdateOgre();
        return true;                    
    }
};
IMPLEMENT_APP_NO_MAIN(MyApp);

int main(int argc, char **argv) 
{
        return wxEntry(argc, argv);
}

#endif //__MAINFRAME_H__

Alternative versions

There's an alternative version, wxOgreRenderWindow, based on wxOgre but decoupling it as most as possible from Ogre, letting total freedom in the Ogre usage and the number of such windows.