44. Debugging

44.1. 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 44.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 44.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.

44.1.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

44.1.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

44.1.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);

44.1.4. Renderable objects

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

44.1.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.

44.1.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.

44.1.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.

44.1.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.

44.1.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.

44.1.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.

44.1.5. Implementation of custom debug rendering

44.1.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.

44.1.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:

44.1.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.

44.1.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();
}

44.1.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).

44.1.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).

44.1.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.

44.1.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).

44.1.6.7. getAlpha

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

44.1.6.8. 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.

44.2. Remote debugging

Running AGX Dynamics embedded into a simulation framework, it can be sometimes hard to understand what is going on during a simulation. Therefore there is a feature which allows for some remote viewing capabilities.

In short, it is utilizing the serialization of a agxSDK::Simulation over a network port. This means that the whole simulation will be serialized, transmitted over the network and de-serialized and visualized in the agxViewer application.

Note

The Remote debugging feature should not be part of any production code, but rather as a way of looking into a running simulation. Enabling remote debugging will greatly affect the performance.

First, let´s have a look at how this works by using the command arguments for agxViewer.

44.2.1. Debugging agxViewer

Start a command prompt with the correct AGX runtime environment setup.

Next, start another command window (you will use one for the remote viewer and one for running the simulation).

From the first command prompt:

> agxviewer --server --serverTimeStep 0.1 --serverWait 0 data/python/overheadConveyor.agxPy
[Warning: agxSDK::Simulation::setEnableRemoteDebugging] Starting debugging server at port: 9999
Loading scene took 0.705391 sec

and from the second command prompt write:

> agxviewer --connect localhost -p --renderDebug 1
Connecting to remote server at localhost port: 9999
Trying to connect to server...
Connected.
../_images/remote_debugging_1.png

Fig. 44.1 The image to the left show the running simulation and the one to the right show the remote debugger view.

Lets look at the arguments of the two commands. On the server side we have:

  • --server - Indicates that a port should be setup where the server will wait for a connection. The port can be changed using the --serverPort <portnum> argument. Default is 9999

  • --serverTimeStep 0.1 - specifies the rate by which the simulation will be streamed over the network connection in ms. This should be a multiple of the current simulation time step.

  • --serverWait 0 - If this is set to 0 (disabled) the server will not wait for a ready signal from the client and instead just push data onto the network. Hence synchronization between client and server will not be upheld.

  • and finally the script that should be executed.

On the client side we have the following arguments:

  • --connect localhost - This specifies the network ip adress to where the server is running. In this case localhost (127.0.0.1)

  • -p or --startPaused - This is important as it will start agxViewer in paused mode. This will show the data being streamed into the client. If this was omitted, or if you press ‘e’ key to start the simulation, you would no longer stream data into the client, instead you are running the actual simulation locally. This is enabled by the fact that we are doing full de-serialization of the simulation.

  • --renderDebug 1 - This will enable the debug renderer so that we can actually see the parts of the simulation.

44.2.2. Enabling remote debugging in an application

The remote debugging feature can be enabled for any agxSDK::Simulation instance.

agxSDK::SimulationRef simulation = new agxSDK::Simulation();

// Specify the rate by which we will send data over to the server. Should be a multiple of the simulation time step
double serverTimeStep = simulation->getTimeStep() / 2.0;

// Specify the TCP port on which the server will listen to the connection
agx::Int16 serverPort = 12345;

// Should we compress (zip) the data before sending?
bool compressData = false;

// Lets not wait for client to be ready for new data. Just send new packages as fast as we can
bool waitForClient = false;

simulation->setRemoteDebuggingTimeStep(serverTimeStep);

// Enable the remote debugging
simulation->setEnableRemoteDebugging(true, serverPort, compressData, waitForClient);

44.2.3. Performance of remote debugging

Due to the full serialization of the simulation state each time it is being synchronized, the performance can be very much affected. Large objects such as heightfield, triangle meshes and terrains can lead to very slow remote debugging.

In those cases where you cannot achieve acceptable performance you can disable serialization for some of the larger objects. For example if you have a heightfield as a seabed, but you are mostly interested in looking at some mechanical simulation above the heightfield, then you can call the Serializable::setEnableSerialization(bool) method.

// Disable serialization for a agxTerrain::Terrain

terrain->setEnableSerialization(false);

// This will lead to better performance in remote debugging.
// Note that you probably have to loop through your geometries and look for
// triangle meshes/heightfields that you want to disable for serialization.
heightfield_geometry->setEnableSerialization( false );

44.3. Logging system

AGX contains a mechanism for logging messages to file, callbacks and/or console which is implemented through two classes, agx::Logger and agx::Notify. Notify is a class derived from std::ostringstream so it can be treated as any std::ostream using the overloaded << operator. The logging functionality is accessed through a number of macros defined in <agx/Logger.h>.

There are four different levels of messages as seen in Table 39.3: Values for Notify level

Table 44.2 Available macros for logging messages

Macro name

Description

LOGGER_DEBUG()

Start new debug message.

LOGGER_INFO()

Start a new information message.

LOGGER_WARNING()

Start a new warning message.

LOGGER_ERROR()

Start a new error message.

LOGGER_END()

Commit the started message (if the message is an error and an exception should be thrown, the actual error is thrown after this call).

LOGGER_ENDL()

Same as LOGGER_END() but add a std::endl to the stream.

LOGGER_STATE()

Change the output state for this message. Determines what is written as a message, line number, current function etc.

Examples of using the LOGGER macros:

LOGGER_WARNING() << "The file: " << filename << " cannot be read" <<
LOGGER_ENDL();

LOGGER_INFO() << LOGGER_STATE(agx::Notify::PRINT_NONE) <<
  "This is a plain message with no extra information " << LOGGER_ENDL();

LOGGER_ERROR() << "A serious problem has occurred" << LOGGER_END();

The last example with LOGGER_ERROR() will throw an exception if LOGGER().setExceptionOnError( true ); is called prior to the call.

44.3.1. Setting Logging level

The level of output/logging can be specified either via Environment variables or via the C++ API:

#include <#include <agx/Logger.h>
// For logging to file/streams opened via NOTIFY().openLog(filename);
// This occurs automatically whenever a Logger::instance() is created and is controlled via the environment variable AGX_LOG_ENABLE
// setLogNotifyLevel specifies what goes into the log file.
//
// The example below will indicate that we only want warning and higher (error) messages written to the log file.
// debug and info will not be displayed in the logfile.
NOTIFY().setLogNotifyLevel( agx::Notify::NOTIFY_WARNING );

If you want to control the output to the console (might or might not be written to the log file depending on the setLogNotifyLevel).

#include <#include <agx/Logger.h>
// For logging to console
// Show only warning and higher (error) messages.
// debug and info will not be displayed in the console
NOTIFY().setNotifyLevel( agx::Notify::NOTIFY_WARNING );

44.3.2. Redirecting Logger messages

Messages that are generated during runtime can contain important information. For example when reading mesh data the constructor of agxCollide::Trimesh can generate warnings about the validity of the mesh. If there is no console available, these messages will not be shown to the user. Either one have to enable the logging to file functionality through Table 39.1 or through the API:

bool overwrite = true; // overwrite if there is already a log file with this name
bool closeAndOpen = true; // If a log file is already open, close it and open the new, otherwise keep the previous one opened
LOGGER().openLogFile( "c://temp/logfile.txt", overWrite, closeAndOpen );

There are also a number of ways to capture the messages written to the LOGGER system.

44.3.2.1. Deriving from a callback

In many situations it is possible to derive from the agx::NotifyCallback class and recieve messages.

Note

Callbacks are triggered from within the std::ostream system in C++. This can cause some issues in certain cases. For example in multi-threaded environments or when used in a C# context such as Unity3D. In those cases we recommend to use the LoggerSubscriber.

To recieve messages through callbacks, one derives a class from agx::NotifyCallback and implements the message method.

class MyNotifyCallback : public agx::NotifyCallback
{
public:
  MyNotifyCallback() {}
  ~NotifyCallback() {}

  /// Virtual method called whenever there is a message ready
  void message( const agx::String& msg, int notifyLevel) override
  {
    // Handle the message
  }
};

agx::ref_ptr<MyNotifyCallback> callback = new MyNotifyCallback();
// Register the callback
LOGGER().addCallback(callback);

// Unregister the callback
LOGGER().removeCallback(callback);

44.3.2.2. Subscribing to messages

When you want to have full control over when messages are handled, the agx::LoggerSubscriber is a better option. It works like a mailbox. You setup the system and poll messages using the poll() method.

// Specify which levels you want to listen to
int levelMask = agx::Notify::NOTIFY_ERROR | agx::Notify::NOTIFY_WARNING | agx::Notify::NOTIFY_INFO;

agx::ref_ptr<agx::LoggerSubscriber> subscriber = new LoggerSubscriber(levelMask);

/*
Run simulation....
*/

// Now we want to check for messages:
if (!subscriber->empty()) {
  std::string messages = subscriber->pop();
  std::cerr << "The message: " << message << std::endl;
}

// There is a "mail queue" for each level, so we can check them individually.
// We can store the messages in an array also:
agx::StringVector messages;

// Are there any warnings?
size_t numMessages = subscriber->pop(messages, agx::Notify::NOTIFY_WARNING);