Creating a new geometry node

From H3D.org

Jump to: navigation, search

Here we will show you some examples of how to extend H3D API with new nodes with new or specialized behaviour which can then easily be used in your x3d scene descriptions. The source code can be obtained from the downloads section named "Various useful nodes".

Torus (new geometry node)

The Torus node
Enlarge
The Torus node

In this example we will create a new geometry node to use, more specifically a torus geometry. Typically when you create a new geometry node you will have to do three things:

  • Perform the OpenGL calls to render the geometry.
  • Calculate an axis-aligned bounding box encompassing geometry.
  • Specify the X3D/Python interface for your node.

I will go through the source code line by line and explain what each part does. So first we need to create a new class for our new node. Since we are creating a geometry node the base class to inherit from is X3DGeometryNode.

 
 class Torus : public X3DGeometryNode {
 public:
 

Next we have the calculation of the bounding box. We want to calculate the bounding box based on the outerRadius and innerRadius fields and the resulting bounding box will reside in the bound member of the node(inherited from X3DGeometryNode). The bounding box should be automatically updated when any of these fields change. In order to do this we define a new specialized field class based on the SFBound field, accepting the radius fields as input and having a BoxBound object as output.

 
/// SFBound is specialized update itself from the innerRadius and
/// outerRadius field of the Torus node.
///
/// routes_in[0] is the innerRadius field of the Torus node.  
/// routes_in[1] is the outerRadius field of the Torus node.  
 
class SFBound: public TypedField< X3DGeometryNode::SFBound,
                                  Types< SFFloat, SFFloat > > {
   /// Update the bound from the radius field. 
   virtual void update() {
      H3DFloat inner_radius = 
          static_cast< SFFloat * >( routes_in[0] )->getValue();
      H3DFloat outer_radius = 
          static_cast< SFFloat * >( routes_in[1] )->getValue();
      BoxBound *bb = new BoxBound;
      bb->size->setValue( Vec3f( (outer_radius+inner_radius)*2, 
                                 (outer_radius+inner_radius)*2,
                                 inner_radius * 2 ) );
      value = bb;
   }
};
 

The TypedField template defines the inputs and outputs of the field. The first argument tells what the base class of the field will be (which is the same as output). In our case it is an SFBound field. We want the value of this field (which is the bounding box) to depend on the two fields innerRadius and outerRadius. Both of these fields are of type SFFloat. The second argument (Types< SFFloat, SFFloat>) specifies a list of field types that have to be routed to it. In our case two SFFloat fields. By specifying the field like this we require two SFFloat fields to be routed to it or there will be an error. In the update()-function that calculates the new value depending on the radius fields we can then assume that the incoming fields are of tye SFFloat and do a static_cast. Now all we have to do is to set up these routes properly and we have a bounding box field that updates automatically.

After this we have the constructor.

 
/// Constructor.
Torus( Inst< SFNode      > _metadata        = 0,
       Inst< SFBound     > _bound           = 0,
       Inst< DisplayList > _displayList     = 0,
       Inst< MFBool      > _isTouched       = 0,
       Inst< MFVec3f     > _force           = 0,
       Inst< MFVec3f     > _contactPoint    = 0,
       Inst< MFVec3f     > _contactNormal   = 0,
       Inst< MFVec3f     > _contactTexCoord = 0,
       Inst< SFBoundTree > _boundTree       = 0,
       Inst< SFFloat     > _innerRadius     = 0,
       Inst< SFFloat     > _outerRadius     = 0,
       Inst< SFInt32     > _nrSides         = 0,
       Inst< SFInt32     > _nrRings         = 0,
       Inst< SFBool      > _solid           = 0 );
 

The first 9 arguments are arguments to the base class, while the rest are for new fields specified in this nodes. For a closer explanation about the Inst<> template and the uses of it see this page. Normally you can just copy the constructor arguments from the base class and add your new fields below. One important thing to notice though is that in the second argument we are using our newly defined SFBound class in the Inst<>-template. This will make the bound member of the node be an instance of our new field type instead of the default type of the X3DGeometryNode base class.

Next are a couple of function definitions. The render() and traverseSG() function are available in all nodes and are used during traversal of the scene-graph. The render() function is used to render the node graphically using OpenGL while the traverseSG() function is used for anything else you want to do while traversing the scene-graph. You have access to things like transformation matrices from and to global/local coordinate spaces and haptics devices among other things.

 
/// Renders the Torus using OpenGL.
virtual void render();
 
/// Traverse the scenegraph. 
virtual void traverseSG( TraverseInfo &ti );  
 


Next comes the specification of all the fields that are specific for this new node. Any values that you want to make publicly available must be declared here as field.

 
    /// The innerRadius field define the inner radius of the torus. 
    ///
    /// <b>Access type:</b> inputOutput
    /// <b> Default value:</b> 0.5
    auto_ptr< SFFloat >  innerRadius;
 
    /// The outerRadius field define the inner radius of the torus. 
    ///
    /// <b>Access type:</b> inputOutput
    /// <b> Default value:</b> 1
    auto_ptr< SFFloat >  outerRadius;
 
    /// The nrSides field define the number of sides for each radial section
    /// of the torus.
    ///
    /// <b>Access type:</b> inputOutput
    /// <b> Default value:</b> 0.5
    auto_ptr< SFInt32 >  nrSides;
 
    /// The nrRings field define the number of radial divisions of the 
    /// torus.
    ///
    /// <b>Access type:</b> inputOutput
    /// <b> Default value:</b> 0.5
    auto_ptr< SFInt32 >  nrRings;
 
    /// The solid field determines whether the Torus is visible when viewed from
    /// the inside. 
    /// <b>Access type:</b>  inputOutput
    auto_ptr< SFBool  >  solid;
 

And finally we have a member that should be part of any node. This is an entry into the internal node database of H3D API, which is a database of all nodes and its fields that are available.

 
    static H3DNodeDatabase database;
 

Ok, that is the header file. Let's move on to the cpp-file.

First we have the node database entry. It will add this node to the node data base, making it possible to use it from X3D and Python. The first argument is the name of the node as a string. This is the name it will have when using it in X3D. The next two arguments are values that are needed for the node database system. When creating a new node, just copy the two arguments and replace the Torus node name with your node name. The last argument is optional and if specified, means that you will inherit the public fields from the database of a base class. If you do not only the fields that are added to the database in this field are available.

 
// Add this node to the H3DNodeDatabase system.
H3DNodeDatabase Torus::database( "Torus", 
                                  &(newInstance<Torus>), 
                                  typeid( Torus ),
                                  &X3DGeometryNode::database );
 

To specify the fields that are to be available from X3D/Python they have to be added to the database of the node. The FIELDDB_ELEMENT is a macro for doing just that. The first argument is the name of the node, the second the name of the field and the third the access type of the field. So now we add the fields we want.

 
// Define the x3d interface
namespace TorusInternals {
  FIELDDB_ELEMENT( Torus, innerRadius, INPUT_OUTPUT );
  FIELDDB_ELEMENT( Torus, outerRadius, INPUT_OUTPUT );
  FIELDDB_ELEMENT( Torus, nrSides, INPUT_OUTPUT );
  FIELDDB_ELEMENT( Torus, nrRings, INPUT_OUTPUT );
  FIELDDB_ELEMENT( Torus, solid, INPUT_OUTPUT );
}
 

The beginning of the constructor is just a call to the base class and initialization of the member field variables.

 
Torus::Torus( 
         Inst< SFNode  >  _metadata,
         Inst< SFBound >  _bound,
	 Inst< DisplayList > _displayList,
	 Inst< MFBool      > _isTouched,
	 Inst< MFVec3f     > _force,
	 Inst< MFVec3f     > _contactPoint,
	 Inst< MFVec3f     > _contactNormal,
	 Inst< MFVec3f     > _contactTexCoord,
	 Inst< SFBoundTree > _boundTree,
         Inst< SFFloat >  _innerRadius,
         Inst< SFFloat >  _outerRadius,
         Inst< SFInt32 >  _nrSides,
         Inst< SFInt32 >  _nrRings,
         Inst< SFBool  >  _solid ) :
  X3DGeometryNode( _metadata, _bound, _displayList, _isTouched, _force, 
		   _contactPoint, _contactNormal, _contactTexCoord, _boundTree ),
  innerRadius    ( _innerRadius ),
  outerRadius    ( _outerRadius ),
  nrSides        ( _nrSides ),
  nrRings        ( _nrRings ),
  solid          ( _solid    ) {
 

Then we initialize the fields of the node and set the type_name. This should be done in all nodes. The type_name is used to give better warning and error messages.

 
  type_name = "Torus";
  database.initFields( this );
 

Now we set the default values of the fields.

 
  // set default values
  innerRadius->setValue( 0.5 );
  outerRadius->setValue( 1 );
  nrSides->setValue( 40 );
  nrRings->setValue( 40 );
  solid->setValue( true );
 

Time to set up internal routes. We need to set up the routes to the bound field as we specified earlier in the SFBound class. We route the innerRadius and outerRadius field.

 
  // set up bound routes
  innerRadius->route( bound );
  outerRadius->route( bound );
 

We now set up routes to the displayList field. All fields that should trigger a re-rendering of the node using the render()-function should be routed to this field. If it is not, the node might not be rendered properly. If you experience that the graphical rendering of a node is not changed even though you change the values of its field, the problem is probably that you have forgotten to set up a route from the field you change to the displayList field.

 
  // set up display list routes(each field that if changed 
  // requires a rerendering of the torus should be routed in here)
  innerRadius->route( displayList );
  outerRadius->route( displayList );
  nrSides->route( displayList );
  nrRings->route( displayList );
  solid->route( displayList );
 


The render function uses glut to render the torus.

 
void Torus::render() {
  glutSolidTorus( innerRadius->getValue(), 
                  outerRadius->getValue(), 
                  nrSides->getValue(),
                  nrRings->getValue() );
}
 


Finally we have the traverseSG function. All it does in our case is to set if back face culling should be enabled or not.

 
void Torus::traverseSG( TraverseInfo &ti ) {
  if( solid->getValue() ) {
    useBackFaceCulling( true );
  } else {
    useBackFaceCulling( false );
  }
  X3DGeometryNode::traverseSG( ti );
}
 

And that is all there is to it. Compile it as part of a shared library or in a viewer as described here and you are ready to use the new node. Here is an example x3d-file you can try

 
 <Shape>
    <Appearance>
	<Material diffuseColor="1 0 0"  shininess="0.4" specularColor="1 1 1"/>
    </Appearance>
    <Torus innerRadius="0.05" outerRadius="0.1" />
 </Shape>
 
Personal tools
go to