A basic H3D player with wxWidgets

From H3D.org

Jump to: navigation, search

The H3DViewer that comes bundled with H3DAPI installation is created with the WxWidgets library. In this tutorial we will create a stripped-down version of the H3DViewer: we will write a basic GUI that allows us to open and close an X3D file.

The choice of using wxWidgets falls onto the developer. It is also possible to use other GUI libraries to work with H3D. H3DLoad, the other installation-bundled loader for example, is created with the GLUT library.

Image:Note-tip.pngThis tutorial refers extensively to the source code. It is recommended that you refer to the source files as you proceed through the tutorial. The source code for this tutorial can be downloaded from H3D Downdloads.

Contents

The concept of displaying H3D

Before proceeding to code, let us look at the concept of H3D and the strategy we will employ in creating this GUI. As explained in Beginner X3D and H3DAPI, and also in the manual, H3DAPI has a scene graph based architecture. Hence, the entire 3D scene that is created with H3DAPI (whether in X3D, Python or C++) is represented as a scene graph.

This scene graph is contained by a Scene object. The Scene object is the topmost node in H3D. It holds the scene graph, and the windows in which it will render the scene graph. Each of these window is an object of type H3DWindowNode.

To display the scene in windows created on a certain GUI application framework (like wxWidgets in this case), we will need to implement the H3DWindowNode abstract class. In this tutorial, we will use a the pre-implemented WxWidgetsWindow class that does this. In H3DAPI, H3DWindowNode is also implemented by GLUTWindow for applications using the GLUT framework.

Image:Note-tip.pngThe WxWidgetsWindow class is included in the source for this tutorial, and can be reused in your own H3D-wxWidgets project.

Our general strategy is thus so:

  • Create a frame (from self-defined MyFrame class deriving from wxFrame)
  • Create a window of type WxWidgetsWindow
  • Contain created window in the frame, and use the window as window to the H3D scene
  • Implement the event handlers (when a file opened and closed, and when we quit the application)


Creating a WxWidgets GUI application

When using the WxWidgets library, every new application must derive from the wxApp class. We do this in MyApp.h:

 
// MyApp.h
#include <wx/wx.h>
#include <H3D/Scene.h>
#include "WxWidgetsWindow.h"
 
class MyApp : public wxApp {
  public:
    virtual bool OnInit();
    static H3D::AutoRef< H3D::Scene > h3d_scene;
    static H3D::AutoRef< H3D::WxWidgetsWindow > h3d_window;
};
 

MyApp will override wxApp's OnInit function. OnInit is called to initialize our program. We create two static data members to the class, h3d_scene and h3d_window - each an object of Scene and WxWidgetsWindow respectively. We then define OnInit in MyApp.cpp:

 
// MyApp.cpp
#include "MyApp.h"
#include "MyFrame.h"
#include "WxWidgetsWindow.h"
#include <H3D/Group.h>
 
H3D::AutoRef< H3D::Scene > MyApp::h3d_scene;
H3D::AutoRef< H3D::WxWidgetsWindow > MyApp::h3d_window;
 
IMPLEMENT_APP(MyApp)
 
bool MyApp::OnInit() {
  MyFrame* frame = new  MyFrame( (wxWindow*)NULL );
  h3d_window.reset( new H3D::WxWidgetsWindow( frame ) );
 
  int width, height;
  frame->GetClientSize(&width, &height);
  h3d_window->width->setValue(width);
  h3d_window->height->setValue(height);
 
  h3d_scene.reset( new H3D::Scene );
  h3d_scene->window->push_back( h3d_window.get() );
  h3d_scene->sceneRoot->setValue( new H3D::Group );
 
  frame->Show();
  SetTopWindow( frame );
  return true;
}
 

Note the use of macro IMPLEMENT_APP(MyApp). In wxWidgets the main function is implemented using the IMPLEMENT_APP macro, which creates an application instance and starts our program.

When the program has started, OnInit is called to initialize it. In OnInit, we put our general strategy in code: creating a MyFrame object frame, a WxWidgetsWindow object contained in frame and resetting the reference of h3d_window to this new window.

The next four lines of code set the size of h3d_window to the size of the window in frame.

A new Scene is then created and referred to with h3d_scene. h3d_scene->window->push_back( h3d_window.get() ) sets h3d_window as a window to display h3d_scene. Then we set a Group node as the sceneRoot to display a black background.

The frame is shown, and then set as the top window of our program.

So far, we have already set out what our program will do on start-up. We will now proceed to write classes MyFrame and WxWidgetsWindow and define their properties and behaviour.

Creating the frame

Specifying frame objects and event handlers

 
// MyFrame.h
#include <wx/wx.h>
class MyFrame : public wxFrame {
  protected:
    wxMenuBar* menuBar;
    wxMenu* menu;
 
    void OnOpen( wxCommandEvent& event );
    void OnClose( wxCommandEvent& event );
    void OnQuit( wxCommandEvent& event );
    void OnIdle( wxIdleEvent& event );
 
  public:
    MyFrame( wxWindow* parent, wxWindowID id = wxID_ANY,
             const wxString& title = wxT("My Player"), 
             const wxPoint& pos = wxDefaultPosition, 
             const wxSize& size = wxSize( 500,300 ), 
             long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
    ~MyFrame();
};
 

MyFrame class basically defines the objects in the frame, and handler functions for events that are triggered on interaction with these objects.


Connecting objects to handlers

For brevity's sake we will skip the settings of the menu bar, menu and menu items (done in MyFrame constructor). These settings are pretty intutitive; interested readers can also obtain more information on how to do this from wxWidgets.org. However, note that the menu items are later connected to the handler functions with Connect:

 
// MyFrame.cpp
MyFrame::MyFrame( ... ) : wxFrame( ... ) {
  ...	
  this->Connect( itemOpen->GetId(), wxEVT_COMMAND_MENU_SELECTED, 
                                    wxCommandEventHandler( MyFrame::OnOpen ) );
  this->Connect( itemClose->GetId(), wxEVT_COMMAND_MENU_SELECTED, 
                                    wxCommandEventHandler( MyFrame::OnClose ) );
  this->Connect( itemQuit->GetId(), wxEVT_COMMAND_MENU_SELECTED, 
                                    wxCommandEventHandler( MyFrame::OnQuit ) );
  this->Connect( wxEVT_IDLE, wxIdleEventHandler( MyFrame::OnIdle ) );
}
 

With these connections, interaction with an item (e.g. itemOpen), will trigger an event handled by the assigned handler (e.g. OnOpen).

Image:Note-info.pngIf you are new to WxWidgets, more information is available at wxWidgets.org.
Image:Note-tip.pngThe wxFormBuilder tool is useful for rapid wxWidgets GUI development. Check it out here.


Defining event handlers

The previous sections, we have already specified and connected the event handlers to the respective items. OnOpen, OnClose and OnQuit are called on events generated from clicking the "Open", "Close" and "Quit" menu items. The OnIdle handle is called when our program is idle. The only task left is to define what these handlers do.

 
// MyFrame.cpp
void MyFrame::OnOpen( wxCommandEvent& event ) {
  wxFileDialog *openFileDialog = new wxFileDialog ( this,
                             wxT("Open file"),
                             "",
                             wxT(""),
                             wxT("*.*"),
                             wxOPEN,
                             wxDefaultPosition) ;
  if (openFileDialog->ShowModal() == wxID_OK) {
    try {
      std::string filename(openFileDialog->GetPath().mb_str());
      MyApp::h3d_scene->sceneRoot->setValue( H3D::X3D::createX3DFromURL( filename.c_str() ) );
    } catch( const Exception::H3DException &e ) {
      // handle exception
    }
  }
}
 

On open, a file dialog box is created and shown with a call to ShowModal(). If a user clicks "OK" on the dialog box, ShowModal returns wxID_OK. When this happens, we get the selected filepath and convert it from wxString type to a multibyte string with mb_str(). Using createX3DFromURL, the topmost node of the scene graph is returned and set as the scene root of H3D scene object. The scene is thus visible.

 
// MyFrame.cpp
void MyFrame::OnClose( wxCommandEvent& event ) {
  MyApp::h3d_scene->sceneRoot->setValue( new H3D::Group );
}
 

On close, the current sceneRoot of h3d_scene replaced by a Group node, which is drawn on the window as an empty black scene.

 
// MyFrame.cpp
void MyFrame::OnQuit( wxCommandEvent& event ) {
	MyApp::h3d_scene.reset( NULL );
  MyApp::h3d_window.reset( NULL );
  Close( true );
}
 

On quit, we clear the existing scene and window, and call Close to delete the frame.

 
// MyFrame.cpp
void MyFrame::OnIdle( wxIdleEvent& event ) {
  for( set< H3D::Scene * >::iterator i = H3D::Scene::scenes.begin();
    i != H3D::Scene::scenes.end();
       i++ ) {
    if( (*i)->isActive() )
      (*i)->idle();
  }
}
 

When the program is idle, the existing Scene instances are iterated through. During iteration, when an active Scene instance is encountered, its idle function is called.

And that was it, we now have a basic H3D player!

References

Personal tools
go to