inlib, exlib / sg

The inlib/exlib comes with a set of classes permitting to build "scene graphs" (sg) to do visualization with OpenGL-ES. It is well known that even visualizing a cube with OpenGL is tedious, and something on top of it is needed. You need some kind of "scene manager" with which you can easily specify : here I want a blue cube, here a rotated green cylinder, here I put a light and I want a camera at such position looking in this direction, etc... In the 1980's Silicon Graphics had created Open Inventor to do that. But for the moment there is no free Inventor implementation over OpenGL-ES only and available for iOS and Android. Then to move on with our applications, we have decided to create our own scene manager (inexlib/sg) fulfilling these specifications.

Usage

The way to use inexlib/sg is similar to Inventor. You create "nodes" that you combine in "scene graphs" by using node containers as the inlib/sg/separator one. When done you create a "exlib::sg::viewer" to which you declare your scene graph. Then you arrange to create a (X11, Cocoa, Windows, iOS or Android) window aware of OpenGL-ES and then at each "expose event" you arrange to execute the viewer.render() method.

Visualizing a cube

On a desktop, you can have an idea by building the exlib sg_cube_X11.cpp that visualizes a cube by using inexlib/sg :

    cd <path to exlib>
    cd examples/cpp
    ./build sg_cube_X11.cpp
    Linux> ./bin_gnu/sg_cube_X11

You should see :

exlib_exa_sg_0.png

You can have a look to sg_cube_X11.cpp and see that the code is rather simple and easy to customize...

// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file exlib.license for terms.

//exlib_build_use exlib inlib GL GLX X11

// should compile with : build -inlib_gl -x11_no_gl

#include <inlib/mem>

#include <inlib/mathf>
#include <inlib/colorfs>

#include <inlib/sg/separator>
#include <inlib/sg/ortho>
#include <inlib/sg/matrix>
#include <inlib/sg/color>
#include <inlib/sg/cube>

#ifdef EXLIB_NO_GL
#include <exlib/X11/base_session>
#include <exlib/X11/viewer>
#else
#include <exlib/X11/session>
#include <exlib/sg/viewer>
#endif

#include <iostream>
#include <cstdlib>

int main(int,char**) {

#ifdef INLIB_MEM
  inlib::mem::set_check_by_class(true);{
#endif

  //////////////////////////////////////////////////////////
  /// create scene graph ///////////////////////////////////
  //////////////////////////////////////////////////////////
  inlib::sg::separator* sep = new inlib::sg::separator;

  inlib::sg::ortho* camera = new inlib::sg::ortho;
  camera->position.value(inlib::vec3f(0,0,4));    
  camera->height.value(2);    
  camera->znear.value(0.1);
  camera->zfar.value(100);
  sep->add(camera);

 {inlib::sg::matrix* m = new inlib::sg::matrix;
  m->set_rotate(0,1,0,inlib::fhalf_pi()/2.0f);
  m->mul_rotate(1,0,0,inlib::fhalf_pi()/2.0f);
  sep->add(m);} //done first.

  inlib::sg::color* color = new inlib::sg::color();
  color->rgb = inlib::colorf_green();
  sep->add(color); //sg takes ownership of color.

  inlib::sg::cube* node = new inlib::sg::cube();
  node->width.value(1.0f);
  node->height.value(1.0f);
  node->depth.value(1.0f);
  sep->add(node);

  //////////////////////////////////////////////////////////
  /// create window, attach to the viewer, steer ///////////
  //////////////////////////////////////////////////////////

#ifdef EXLIB_NO_GL
  exlib::X11::base_session x11(std::cout);
#else
  exlib::X11::session x11(std::cout);
#endif  
  if(!x11.display()) return EXIT_FAILURE;

  Window win = x11.create_window("win 1",0,0,400,200);
  if(win==0L) return EXIT_FAILURE;

  //////////////////////////////////////////////////////////
  /// create the viewer, set the scene graph ///////////////
  //////////////////////////////////////////////////////////
#ifdef EXLIB_NO_GL
  exlib::X11::viewer viewer(std::cout,x11.display(),win,400,200);
#else  
  exlib::sg::viewer viewer(std::cout,400,200);
#endif  
  viewer.sg().add(sep); //give sep ownership to the viewer.
  //////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////

  
  x11.show_window(win);

  Atom atom = ::XInternAtom(x11.display(),"WM_DELETE_WINDOW",False);

  while(true) { 
      XEvent xevent;
      ::XNextEvent(x11.display(),&xevent);
      //std::cout << xevent.type << std::endl;
      //if((xevent.type==Expose)||(xevent.type==ConfigureNotify)) {
      if(xevent.type==ClientMessage) {
        if(xevent.xclient.data.l[0]==(long)atom) break;        
      } else if(xevent.type==Expose) {
        int width,height;
        x11.window_size(win,width,height);

        viewer.set_size(width,height);

#ifdef EXLIB_NO_GL
        viewer.render();
#else
        if(::glXMakeCurrent(x11.display(),win,x11.context())==False){
          std::cout << "glXMakeCurrent failed." << std::endl;
          break;
        }
        viewer.render();
        ::glXSwapBuffers(x11.display(),win);
#endif

      } else if(xevent.type==ButtonPress && xevent.xbutton.button==1) {
        break;
      }

  }


  x11.delete_window(win);

#ifdef INLIB_MEM
  }inlib::mem::balance(std::cout);
#endif

  return EXIT_SUCCESS;
}

Boolean operations on solids

As a curiosity you can have a look to the sg_polyhedron_X11.cpp example that shows a boolean operation over solids.

    cd <path to exlib>
    cd examples/cpp
    ./build sg_polyhedron_X11.cpp
    Darwin> ./bin_clang/sg_polyhedron_X11

You should see :

exlib_exa_sg_1.png

The code of sg_polyhedron_X11.cpp is similar to the sg_cube_X11 one. We show here only the boolean operation over solids part :

  #include <exlib/sg/polyhedron>
  ...
  // A Tube with a transvers hole :
  inlib::hep::polyhedronTubs tubs_1(0.7,1.5,2,0,inlib::two_pi());
  inlib::hep::polyhedronTubs tubs_2(  0,0.5,4,0,inlib::two_pi());
  tubs_2.Transform(inlib::rotd(inlib::vec3d(0,1,0),inlib::half_pi()),
                   inlib::vec3d(0,0,0));
  inlib::hep::polyhedron op = tubs_1.subtract(tubs_2);

  exlib::sg::polyhedron* node = new exlib::sg::polyhedron();
  //node->ph = 
  //  inlib::hep::polyhedronSphere(0.9,1,0,inlib::two_pi(),0,inlib::pi());
  node->ph = op;
  //node->solid = false;
  //node->reduced_wire_frame = false;
  sep->add(node);
  ...

GUI done with scene graphs !

In our apps, in order to have the same look and feel on desktops, phones and tablets, we have decided to handle the GUI by using also OpenGL-ES for the rendering. Then inexlib/sg contains nodes as "button, list" that permit to build a graphical user interface by using the same tools used for the visualization of data.

Obviously, being able to handle a GUI in this way simplifies a lot the situation for Android and iOS since we don't have to coope too much with the problem of mixing java or Obj-C with C++. (The only things that we ask to java on Android is to give us an OpenGL-ES window, and the same with Obj-C on iOS). The drawback is that for the moment our GUIs do not look so great, but at least the functionality is here and it is workable.

As a "button example" you can build sg_button_X11.cpp :

    cd <path to exlib>
    cd examples/cpp
    ./build sg_button_X11.cpp
    cp ../../data/arialbd.ttf .
    Linux> ./bin_gnu/sg_button_X11
 ( Darwin> ./bin_gnu/sg_button_X11 )
and on Windows :
    ./build sg_button_Windows.cpp
    cygwin> ./bin_visual/sg_button_Windows.exe

You should see :

exlib_exa_sg_button.png

Clicking inside the button should display the message :

    hello inlib/sg/text_button callback.

You exit the application by clicking the window close button.

// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file exlib.license for terms.

//exlib_build_use exlib inlib inlib_glutess freetype GL GLX X11

#include <inlib/mem>

#include <inlib/sg/separator>
#include <inlib/sg/ortho>
#include <inlib/sg/matrix>
#include <inlib/sg/color>
#include <inlib/sg/text_button>

#include <exlib/sg/text_freetype>
#include <exlib/sg/viewer>
#include <exlib/X11/session>

#include <iostream>
#include <cstdlib>

int main(int,char**) {

#ifdef INLIB_MEM
  inlib::mem::set_check_by_class(true);{
#endif

  //////////////////////////////////////////////////////////
  /// create scene graph ///////////////////////////////////
  //////////////////////////////////////////////////////////
  inlib::sg::separator* sep = new inlib::sg::separator;

  inlib::sg::ortho* camera = new inlib::sg::ortho;
  camera->position.value(inlib::vec3f(0,0,4));    
  camera->height.value(2);    
  camera->znear.value(0.1f);
  camera->zfar.value(100);
  sep->add(camera);

  inlib::sg::color* color = new inlib::sg::color();
  color->rgb = inlib::colorf_green();
  sep->add(color); //sg takes ownership of color.

  exlib::sg::text_freetype ttf;

 {inlib::sg::text_button* b = new inlib::sg::text_button(ttf);
  b->width = 3;
  b->height = 1;
  b->font = inlib::sg::font_arialbd_ttf();
  b->front_face = inlib::sg::winding_cw;

  b->back_area::color = inlib::colorf_orange();
  b->color = inlib::colorf_black();
  b->arm_color = inlib::colorf_yellow();

  b->strings.add("click me !");
  b->confine = true;

  class cbk : public inlib::sg::bcbk {
    typedef inlib::sg::bcbk parent;
  public:
    virtual inlib::sg::return_action action() {
      std::cout << "hello inlib::sg::text_button callback." << std::endl;
      return inlib::sg::return_none;     
    }
    virtual inlib::sg::bcbk* copy() const {return new cbk(*this);}
  public:
    cbk():parent(){}
    virtual ~cbk(){}
  private:
    cbk(const cbk& a_from):parent(a_from)/*,m_gv(a_from.m_gv)*/{}
    cbk& operator=(const cbk& a_from){
      parent::operator=(a_from);
      return *this;
    }
  };

  b->add_callback(new cbk());
  sep->add(b);}

  //////////////////////////////////////////////////////////
  /// create the viewer, set the scene graph ///////////////
  //////////////////////////////////////////////////////////
  unsigned int ww = 400;
  unsigned int wh = 200;
  
  exlib::sg::viewer viewer(std::cout,ww,wh);
  viewer.sg().add(sep); //give sep ownership to the viewer.

  //////////////////////////////////////////////////////////
  /// create window, attach to the viewer, steer ///////////
  //////////////////////////////////////////////////////////

  exlib::X11::session x11(std::cout);
  if(!x11.display()) return EXIT_FAILURE;

  Window win = x11.create_window("win 1",0,0,ww,wh);
  if(win==0L) return EXIT_FAILURE;
  x11.show_window(win);

  Atom atom = ::XInternAtom(x11.display(),"WM_DELETE_WINDOW",False);

  while(true) { 
      XEvent xevent;
      ::XNextEvent(x11.display(),&xevent);
      //std::cout << xevent.type << std::endl;
      //if((xevent.type==Expose)||(xevent.type==ConfigureNotify)) {
      if(xevent.type==ClientMessage) {
        if(xevent.xclient.data.l[0]==(long)atom) break;        
      } else if(xevent.type==Expose) {
        int width,height;
        x11.window_size(win,width,height);

        viewer.set_size(width,height);

        if(::glXMakeCurrent(x11.display(),win,x11.context())==False){
          std::cout << "glXMakeCurrent failed." << std::endl;
          break;
        }

        viewer.render();

        ::glXSwapBuffers(x11.display(),win);
        ::glXMakeCurrent(x11.display(),None,NULL);

      } else if(xevent.type==ButtonPress && xevent.xbutton.button==1) {

        int width,height;
        x11.window_size(win,width,height);

        int x = xevent.xbutton.x;
        int y = height-xevent.xbutton.y;

        float hsize = 2;
        float l = x-hsize; //could be negative.
        float r = x+hsize;
        float b = y-hsize; //could be negative.
        float t = y+hsize;
        inlib::sg::pick_action action(std::cout,width,height,l,r,b,t);
        action.set_stop_at_first(true);

        sep->pick(action); //can trigger button callback.
        if(!action.end()) {
	  std::cout << "main :" 
                    << " bad pick_action end."
                    << std::endl;
          return EXIT_FAILURE;
        }

        //execute callbacks of picked button :
        inlib::sg::node* node = action.node();
        if(node) {
          if(inlib::sg::base_button* btn = 
             inlib::safe_cast<inlib::sg::node,inlib::sg::base_button>(*node)){
            const std::vector<inlib::sg::bcbk*>& cbks = btn->cbks().callbacks();
            std::vector<inlib::sg::bcbk*>::const_iterator it;
            for(it=cbks.begin();it!=cbks.end();++it) (*it)->action();
          }
        }

      }

  }


  x11.delete_window(win);

#ifdef INLIB_MEM
  }inlib::mem::balance(std::cout);
#endif

  return EXIT_SUCCESS;
}