41. Debug rendering

AGX has an internal scheme for rendering rigid bodies, geometries and constraints (including contacts). It is handled by a class agxRender::RenderManager. When debug-rendering is enabled, this class will dispatch calls for rendering and create agxRender::RenderProxy’s. A render proxy is a placeholder for a class that can be rendered in the clients rendering system. By default if AGX is built with OpenSceneGraph, there is an implementation of render proxies for that scene graph.

Each primitive has its own render proxy which need to be implemented at the client side. Responsible for creating these proxies is the agxRender::RenderProxyFactory, a class which is derived to a specialization for each rendering system. The class agxOSG::RenderProxyFactory is one such specialization for OSG.

This rendering system should not be considered to be the rendering system for your simulations in simulators etc. It is merely a system for debugging your simulation, being able to see if bodies are enabled, static, kinematic, sleeping. To see where your constraints are attached to bodies etc. When you need a proper rendering engine, you should connect that directly through the geometries and rigid bodies.

Table 41.1 Classes for rendering

Class

Description

agxRender::RenderManager

Manages the debug rendering system. Called from agxSDK::Simulation::stepForward()

*agxRender::RenderProxyFactory

Abstract base class responsible for creating agxRender::RenderProxy for rendering in a specific rendering system.

agxRender::RenderProxy

Abstract base class for all renderable objects. Derived down to various primitives (sphere, cylinder, etc.)

*SphereProxy, *CylinderProxy, *LineProxy, *TextProxy, *PlaneProxy, *CapsuleProxy, *BoxProxy, *HeightfieldProxy, *TrimeshProxy

Specialization of a RenderProxy for each supported shape.

agxRender::GraphRenderer

An abstract class for rendering graphs related to statistics information.

Classes marked with * in Table 41.1: indicates that they need to be implemented and adopted for your specific rendering engine.

Objects with different properties are rendered differently: geometries belonging to static bodies are rendered in blue, whereas geometries associated to dynamic bodies are rendered in green. Sensors are rendered with a lighter blue.

41.1. RenderManager

agxRender::RenderManager. This class is responsible for updating all the RenderProxy instances that are created during debug rendering. Each render manager need a reference to a RenderProxyFactory, specific for a rendering engine. Several render managers can use the same RenderProxyFactory, so if you are using several agxSDK::Simulation, you can assign the same RenderProxyFactory to those simulations.

// Create a render proxy factory, specific for OpenSceneGraph
agxRender::RenderProxyFactoryRef factory = new agxOSG::RenderProxyFactory;

// Create a simulation
agxSDK::SimulationRef sim = new agxSDK::Simulation;

// Tell this simulation's render manager to use our factory
sim->getRendermanager()->setProxyFactory( factory );

// Create another simulation, use the same factory for the debug rendering
agxSDK::SimulationRef sim2 = new agxSDK::Simulation;
sim2->getRendermanager()->setProxyFactory( factory );

During a Simulation::stepForward(), a call to RenderManager::update() will be called which in turn will query the render proxy factory for spheres, lines, cylinders etc.

There is no cache functionality implemented in the RenderManager, that is, when a render proxy is required, a call to the render proxy factory will be executed. There will be no storage of unused render proxies in the render manager. If caching is required (to achieve better performance for scenes where the number of proxies varies a lot), it has to be done in the render proxy factory implementation

41.2. Scale factor

By default the overall scaling of debug rendering is configured to fit a scene where objects are around 1 m. For a very small or very large scene it might be useful to increase/decrease the size of the rendered constraints, contacts etc. This can be done using the setScaleFactor method:

simulation->getRenderManager()->setScaleFactor(0.1f);

The scale factor can also be controlled with the keyboard in an application based on the agxOSG::ExampleApplication class such as agxViewer. Note that the application/script might have changed the mapping for those keys. For more information see keybindings

41.3. Render flags

There are three methods that control what should be rendered by the render manager:

void setFlags( unsigned int flags );
void enableFlags( unsigned int  flags );
void disableFlags( unsigned int  flags );

setFlags will override the current set of flags with the specified one. enableFlags will enable the specified flags and leave the rest untouched. disableFlags will disable the specific flags and leave the rest untouched.

As an example, to specify that the default set of flags should be used, plus the rendering of bounding volumes (which is not enabled by default) the following call can be done:

// Render default, plus the bounding volumes
simulation->getRenderManager()->setFlags(agxRender::RENDER_DEFAULT|
                                                                        agxRender::RENDER_BOUNDING_VOLUMES);

41.4. Renderable objects

There are various types of objects that will be rendered in the debug rendering:

41.4.1. Shapes

All shapes (agxCollide::Shape) for all enabled geometries will be rendered. Every time a new shape is added/removed from a geometry part of the simulation, it will be added/removed from the debug rendering system (if it is enabled). A proxy will be associated for each shape. When the shape is deleted/removed, the proxy will get a call to onChange(REMOVE) (see below). For each time step, the state (color, alpha) and shape (size/form) will be synchronized with the corresponding AGX shape. For most of the time, only transform changes will be updated for these shapes, which should be efficiently handled by the rendering engine. The rendering of shapes will occur of the flag RENDER_GEOMETRIES is enabled in the render manager.

41.4.2. Constraints

The constraints in AGX has a virtual render method. This method will be called whenever the RENDER_CONSTRAINTS flag is enabled in the render manager. These render methods, will query the render manager for various shapes such as cylinder, spheres or lines. These queries will be dispatched to the specific RenderProxyFactory.

41.4.3. Renderables

There is a special class, agx::Renderable, which can be used for some specific rendering. It has a virtual method render which will be called from the RenderManager if the RENDER_RENDERABLES flag is enabled in the render manager.

41.4.4. Bodies

For each enabled RigidBody a sphere proxy will be rendered. These sphere proxies will be acquired via the render manager to the specific render proxy factory. This will occur if the RENDER_BODIES flag is enabled.

41.4.5. Contacts

Contacts will be rendered analogous to bodies. A sphere and a line will be acquired from the render manager. This occurs if the flag RENDER_CONTACTS is enabled.

41.4.6. Statistics

Statistics involve both the textual information (as collected by the agx::Statistics class) and graph drawing, showing some of the statistics as visual graphs on the screen. This occurs if the RENDER_STATISTICS is enabled.

41.5. Implementation of custom debug rendering

41.5.1. agxRender::RenderProxyFactory

This class need to be specialized for any specific rendering engine. In <agxOSG/RenderProxy.h> an implementation for OpenSceneGraph can be found. The virtual methods that need to be implemented are:

/// Interface for creating and returning a SphereProxy
virtual SphereProxy *createSphere( float radius ) = 0;

/// Interface for creating and returning a BoxProxy
virtual BoxProxy *createBox( const agx::Vec3& halfExtents ) = 0;

/// Interface for creating and returning a LineProxy
virtual LineProxy *createLine( const agx::Vec3& p1, const agx::Vec3& p2 ) = 0;

/// Interface for creating and returning a CylinderProxy
virtual CylinderProxy *createCylinder( float radius, float height ) = 0;

/// Interface for creating and returning a ConeProxy
virtual ConeProxy *createCone( float radius, float height ) = 0;

/// Interface for creating and returning a CapsuleProxy
virtual CapsuleProxy *createCapsule( float radius, float height ) = 0;

/// Interface for creating and returning TextProxy
virtual TextProxy *createText( const agx::String& text, const agx::Vec3& pos ) = 0;

/// Interface for creating and returning PlaneProxy
virtual PlaneProxy *createPlane( const agx::Vec3& normal, agx::Real distance ) = 0;

/// Interface for creating and returning HeightfieldProxy
virtual HeightFieldProxy *createHeightfield( const agxCollide::HeightField *hf ) = 0;

/// Interface for creating and returning TrimeshProxy
virtual TrimeshProxy *createTrimesh( const agxCollide::Trimesh *mesh ) = 0;

Each of these methods are responsible for returning a RenderProxy of a specific type based on the in-data. The RenderProxy is then responsible for holding this renderable reference into the render system in use. As an example, the code for creating a sphere OpenSceneGraph looks like this:

agxRender::SphereProxy* RenderProxyFactory::createSphere( float radius )
{
  ShapeData<osg::Sphere> sphereData;
  sphereData.shape = new osg::Sphere();
  sphereData.shape->setRadius( radius );
  sphereData.geode = createGeode( sphereData.shape, &sphereData.shapeDrawable );
  sphereData.geode->setName("SphereGeode");

  // Create a new Sphere proxy including the rendering representation in OSG
  agxOSG::SphereProxy *proxy = new agxOSG::SphereProxy( radius, sphereData, this );

  addChild( proxy->getNode() ); // Add the node to the scenegraph (hold by the factory)

  return proxy; // Return the proxy
}

The specific data required will differ between rendering engines.

41.6. agxRender::RenderProxy

Each subclass (primitive type) of RenderProxy need to be implemented for the specific render engine. The virtual methods that can/should be implemented are:

41.6.1. onChange

This method need to be implemented (pure virtual method) in your representation of a RenderProxy. It will receive calls from each of the set methods described below. Whenever a proxy is required to change color, shape, alpha or transform, this method will be called. Based on the event-type described in the enum agxRender::RenderProxy::EventType, you should update your rendering representation with the new current value. For the OpenSceneGraph implementation a code snipped looks like the following:

void onChange( RenderProxy::EventType type) {
  switch( type ){
      case (RenderProxy::ENABLE):
      setEnableOSG(getEnable());
      break;
      case (RenderProxy::ALPHA):
      setAlphaOSG(getAlpha());
      break;
      case (RenderProxy::TRANSFORM):
      setTransformOSG(getTransform());
      break;
...

So based on the event type different calls are done to the rendering API to reflect the changes into the rendering implementation.

41.6.2. updateShape

This method need to be implemented (pure virtual method) need to be implemented in your representation of a RenderProxy. It is specific for each shape. When this method is called (probably from your specific implementation of onChange(SHAPE)) the render proxy implementation need to update the rendering representation to reflect changes, such as changing radius for a sphere, change the triangle mesh structure or changing the size of a box.

A code snippet from the OpenSceneGraph representation illustrate how it can be done:

void SphereProxy::updateShape( )
{
  // Get the agx representation of the shape associated to this proxy
  const agxCollide::Sphere *sphere = getCastShape();
  if (sphere) // If there IS a shape associated, then read the radius from that
    m_radius = sphere->getRadius();

  // If radius is the same, then just dont do anything
  if (agx::_equivalent((float)m_radius, m_data.shape->getRadius()))
    return;

  // Change the radius of the osg-sphere
  m_data.shape->setRadius(m_radius);
  m_data.geode->getDrawable(0)->dirtyDisplayList();
}

41.6.3. setTransform

virtual void RenderProxy::setTransform( const agx::AffineMatrix4x4& transform );

This method will set the m_transform member of the RenderProxy, then it will call onChange( TRANSFORM ) which indicates that the transform is changed. If m_transform == transform, no call to onChange will occur (to avoid unnecessary state changes. If you override this method, store transformation in m_transform and call onChange(TRANSFORM).

41.6.4. setColor

Store a color in m_color and call onChange(COLOR). If color == m_color, no call to onChange will occur to reduce state changes. If you override this method, update m_color and call onChange(COLOR).

41.6.5. getColor

This method return the color of the proxy, the value in m_color. You can override this method to return the color as stored somewhere else, just make sure you sync with the m_color member attribute.

41.6.6. setAlpha

Set the transparency value of the proxy. The implementation will store alpha in m_alpha and call onChange(ALPHA). If you override this implementation, you need to update m_alpha, and call onChange(ALPHA).

41.6.7. getAlpha

Return the value of the m_alpha member attribute. If you override setAlpha you might want to also override this method.

41.7. agxRender::GraphRenderer

This class is specific for each rendering engine. An implementation for OpenSceneGraph can be found in <agxOSG/Graphrenderer.h> and corresponding .cpp file.

For more information, see the doxygen generated documentation for the class.