Advanced H3DAPI programming

From H3D.org

Jump to: navigation, search

Contents

Introduction

Advanced programing with H3DAPI means that a programmer will use C++. This entire section will assume knowledge of C++ and is for advanced users. If something is beyond understanding then first check in a C++ book or tutorial and if that is not sufficient to understand the instructions and examples here ,or there is an obvious fault in this wiki, post in the forum at www.h3d.org if you need help. Otherwise just correct it.

Using H3DAPI from C++

Everything that can be done in X3D can be done in C++. The easiest way is simply to use the createX3DFrom* functions defined in X3D.h and send X3D code to be read by those functions. It is however possible to create scenes without a single line of X3D-code in a way similar to other C++-based scene graph APIs, for example VTK.

To find example on how to use H3DAPI from the C++ levels check the examples/manualExamples/C++ directory that comes with an installation of H3DAPI. H3DLoad and H3DViewer source code are also good examples.

Extending H3DAPI

Extending H3DAPI usually means that new nodes needs to be added to add new functionality. Right now creating nodes is all information that this section will provide.

Creating a new node

The best tip when extending a node is to think through what functionality this node will add. Check if this functionality already exists in other nodes and check which abstract classes these inherit from. Abstract classes in H3D usually have names that start with "H3D" or "X3D". The next thing to do is to check which virtual functions exist in these classes and which of these virtual functions might have to be overridden. The default behaviour of the functions might be sufficient quite often.

For all classes that inherits directly from X3DNode (most nodes do) these are the virtual functions that needs to be considered.

  • Run once when an instance of the node is created.
    void initialize()
  • Override if the node should render something to screen.
    void render()
  • Makes updates not used to render to screen. Such as adding haptic objects. TraverseInfo collects and contains information from the traversal.
    void traverseSG( TraverseInfo &ti )
  • Intersection with a line and the node.
bool lineIntersect( const Vec3f &from, 
                    const Vec3f &to,    
                    LineIntersectResult &result )
  • Closest point on the node to point p.
void closestPoint( const Vec3f &p,
                   NodeIntersectResult &result )
  • Intersection between a moving sphere and the node.
bool movingSphereIntersect( H3DFloat radius,
                            const Vec3f &from, 
                            const Vec3f &to,
                            NodeIntersectResult &result )
  • Sets the name of the field that this node should be added to by default when building up the scene graph. It is "children" by default since a lot of nodes are added as child nodes of group nodes.
    string defaultXMLContainerField()


Following is a simple template-example of a header file of a typical implementation of a Node. This node does not work as it is and has no functionality.

#ifndef __NODETEMPLATE_H__
#define __NODETEMPLATE_H__
 
// Include needed files here
 
namespace H3D {
  class NodeTemplate: public BaseClass{
  public:
    NodeTemplate( Inst< FieldType >  _myFieldName = 0);
 
    // public virtual functions goes here
 
    // Fields are specified like this. Where FieldType is one of the types for fields.
    // these are all defined in the files which names start wit "SF" or "MF"
    auto_ptr< FieldType >  myFieldName;
 
    /// The H3DNodeDatabase for this node.
    static H3DNodeDatabase database;
 
  protected:
    // protected virtual functions goes here.
  };
}

The corresponding source file would look like this:

 
#include "H3D/NodeTemplate.h"
 
using namespace H3D;
 
// The second argument is NULL if this class is abstract.
H3DNodeDatabase X3DNode::database( "NodeTemplate",
                                   &(newInstance<NodeTemplate>),
                                   typeid( NodeTemplate),
                                   &BaseClass::database );
 
namespace X3DNodeInternals {
  // The third argument is one of INPUT_OUTPUT, OUTPUT_ONLY or INPUT_ONLY.
  FIELDDB_ELEMENT( NodeTemplate, myFieldName, INPUT_OUTPUT );
}
 
NodeTemplate::NodeTemplate( 
                 Inst< FieldType >  _myFieldName):
  myFieldName( _myFieldName) {
 
  type_name = "NodeTemplate";
  database.initFields( this );
}
 
// other functions
 


Creating a new geometry node

When creating a new geometry, that is a class that somehow inherits from X3DGeometryNode, there are a few things that usually must be done and other that needs to be taken into consideration. This is in addition to the general hints above. The things that usually must be done are:

  • Create an SFBound which inherits from BaseClass::SFBound where BaseClass is the node that this class inherits from. The SFBound should update the bound in some way. Needed for collision detection and correct display of the scene.
  • Override the virtual function render
  • Route any fields used to define how to render the geometry to the displayList field to correctly create and destroy the display list used for this node.
  • If the geometry node has a solid field used as in the X3D specification traverseSG() needs to be overridden just as in the source code for Box (Box.cpp).

The things that needs to be taken into consideration are:

  • Could the collision detection functions be overridden (lineIntersect, closestPoint, movingSphereIntersect) so that they are optimized. The default function behaviour will use the triangles/points/lines rendered by OpenGL to detect collisions. One example where it is smarter to override these functions is the Sphere node. See source code.
  • Is the haptic shape very simple. In that case override traverseSG more or less for the same reason as in the previous point. Haptics sensation is by default created from the triangles rendered by OpenGL. Again see the source code for the Sphere node. See Custom made Haptics Shape in the HAPI section to get information on how to create a custom made haptic shape that can be used in H3DAPI.

Creating a new haptics surface node

A haptics surface node in H3DAPI is a class inheriting from H3DSurfaceNode and such a class is more or less just a wrapper around a HAPISurfaceObject with the addition of node and fields. See the section Custom made Haptics Surface for information on how to subclass HAPISurfaceObject.

Provided that a subclass to HAPISurfaceObject exists all that is needed is to override the initialize() function of the base class. The initialize function should look like this.

void MyOwnSurface::initialize() {
  BaseSurfaceClass::initialize();
  // Set hapi_surface auto_ptr to a new instance of
  // the corresponding HAPI surface.
  hapi_surface.reset(
    new HAPI::MyOwnSurface( stiffness->getValue(),
                            damping->getValue() ) );
}

In order to change values in the surface on the fly, if that is desired, a number of fields might have to be specialized. See the source code for SmoothSurface for an example on how to do this.

Creating a new haptics renderer node

A haptics renderer is a class that takes care of the haptics renderering based on geometry in the scene. That is how to calculate the forces returned when the haptic stylus is touching (intersects with) geometry in the scene. A new haptics renderer node should inherit from H3DHapticsRendererNode and is basically just a wrapper around the corresponding renderer implemented in HAPI but with fields, see the section Custom made Haptics Renderer.

Provided that a haptics renderer class inheriting implemented as in the HAPI section exists all that is needed for the haptics renderer node to function is to override the pure virtual function:

HAPI::HAPIHapticsRenderer *getNewHapticsRenderer()
If the HAPI renderer have properties that can be set specialized fields needs to be implemented to be able to set these values. See the source found in HapticsRenderer.cpp for examples on how this is done.

Creating a new haptic force effect node

A haptic force effect node is a class that calculates a force to be rendered on the haptics device, in H3DAPI this class inherits from the class H3DForceEffect. The important part for this node is to override the traverseSG function so that it adds an instance of a subclass to HAPIForceEffect to the TraverseInfo struct provided. To get information about how to implement a subclass to HAPIForceEffect see section Custom made Haptics Force Effect. If the instance added to the TraverseInfo struct is not an instance unique for that scene graph traversal (graphics frame) the node might not be possible to be used with the DEF/USE statement to be put in several different places in the scene graph. A good example of how to add a haptic force effect can be found in ForceField.h. The traverseSG function of the ForceField node looks like this:

/// Adds a HapticForceField effect to the TraverseInfo.
virtual void traverseSG( TraverseInfo &ti ) {
  if( ti.hapticsEnabled() ) {
    ti.addForceEffectToAll( new HAPI::HapticForceField( 
      ti.getAccForwardMatrix().getRotationPart() *force->getValue() ) );
  }
}

Adding support for a new haptics device

A haptics device class in H3DAPI interfaces with a haptics device. A new haptics device class should inherit from H3DHapticsDevice and is basically a node wrapper around the corresponding class in HAPI. See the section Adding support for new Haptics Device in HAPI for information on how to implement support through HAPI.

Provided that a HAPI haptics device class is provided all that is needed to create a new haptics device node is to subclass H3DHapticsDevice and override the intialize() function. In some cases other virtual functions like initDevice() and releaseDevice() have to be overridden to. See implementation of device nodes in H3DAPI for examples.

Creating a new field

There is an excellent guide in the H3DAPI manual on how to create new fields. For a list of useful field templates see this link.


H3DViewer plugin

This section basically explains what is needed for H3DViewer to register a library as a plugin which will be loaded at startup. Let us call our new library Foo

Somewhere in your system include H3D/LibraryInfo and then declare the following function
#include <H3D/LibraryInfo.h>

// Function to extract information about the library.
extern "C" Foo_API H3D::LibraryInfo getLibraryInfo();
Then somewhere else define the function as
H3D::LibraryInfo getLibraryInfo() {
  H3D::LibraryInfo r = H3D::LibraryInfo::createEmptyInfo();
  std::stringstream s;
  s << Foo_MAJOR_VERSION << "."
    << Foo_MINOR_VERSION << " (build "
    << Foo_BUILD_VERSION << ")";

  strcpy( r.name, "Foo" );
  strcpy( r.version, s.str().c_str() );
  strcpy( r.web, "url_to_Foo_homepage" );
  strcpy( r.developer, "FooDeveloperName" );
  strcpy( r.developer_web, "FooDeveloperWeb" );
  strcpy( r.info, "Foo is for some reason used when writing templates to explain programming." );
  return r;
}
Personal tools
go to