Open Inventor Release 2024.2.0
 
Loading...
Searching...
No Matches
Creating a Highlight in the Overlay Planes

The following two examples show a class derived from SoGLRenderAction that renders the selected objects as 2D rectangles in the overlay planes. This example illustrates several techniques you will probably use when deriving any new highlight class:

  • Using function “stubs” to call the path and pathList forms of the apply() method of the parent class
  • Caching the path to the selection node In addition, the OverlayHighlightRenderAction class performs some work that is class-specific. For efficiency, its constructor sets up a scene graph that contains a 2D rectangle. Later, the apply() method changes the values of the rectangle so that it appears in the correct position for the highlighted object. The updateBbox() method is specific to this highlight class. It projects the 3D bounding box for the selected object onto the screen and renders it as a 2D rectangle.

When the constructor creates an SoGLRenderAction, it passes in a dummy viewport region. The SoXtRenderArea will pass the real viewport region to to SoGLRenderAction before the render action's apply() method is called.

The example below shows the class declaration found in the include file OverlayHighlightRenderAction.h.

Example : OverlayHighlightRenderAction.h

#include <Inventor/actions/SoGLRenderAction.h>
class SoCamera;
class SoCoordinate3;
class SoOrthographicCamera;
class SoSeparator;
class OverlayHighlightRenderAction : public SoGLRenderAction
{
SO_ACTION_HEADER( OverlayHighlightRenderAction );
public:
// Constructor and destructor
OverlayHighlightRenderAction();
~OverlayHighlightRenderAction();
// Applies action to the graph rooted by a node,
// only drawing selected objects.
virtual void apply( SoNode* node );
// Applies action to the graph defined by a path or path
// list.
// These simply invoke the parent class apply() methods.
// These do NOT highlight the path, whether selected or not.
// They are implemented to keep the compiler happy.
virtual void apply( SoPath* path );
virtual void apply( const SoPathList& pathList, SbBool obeysRules = FALSE );
// This must be called before this class is used.
static void initClass();
static void exitClass();
private:
void updateBbox( SoPath* p, SoCamera* c );
// Local scene graph
SoSeparator* localRoot;
SoOrthographicCamera* orthoCam;
SoCoordinate3* coords;
// We will cache the path to the first selection node
SoPath* selPath;
};

The example below shows the class definition, found in the source file.

Example : OverlayHL.cxx

#include <Inventor/SbBox.h>
#include <Inventor/SoNodeKitPath.h>
#include <Inventor/SoPath.h>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/nodekits/SoBaseKit.h>
#include <Inventor/nodes/SoColorIndex.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoFaceSet.h>
#include <Inventor/nodes/SoLightModel.h>
#include <Inventor/nodes/SoOrthographicCamera.h>
#include <Inventor/nodes/SoPickStyle.h>
#include <Inventor/nodes/SoSelection.h>
#include <Inventor/nodes/SoSeparator.h>
#include <limits.h>
#include <math.h>
#include "OverlayHighlightRenderAction.h"
SO_ACTION_SOURCE( OverlayHighlightRenderAction );
// Initializes the OverlayHighlightRenderAction class.
void
OverlayHighlightRenderAction::initClass()
{
SO_ACTION_INIT_CLASS( OverlayHighlightRenderAction, SoGLRenderAction );
}
void
OverlayHighlightRenderAction::exitClass()
{
SO_ACTION_EXIT_CLASS( OverlayHighlightRenderAction );
}
// Constructor
OverlayHighlightRenderAction::OverlayHighlightRenderAction()
: SoGLRenderAction( SbVec2s( 1, 1 ) ) // pass a dummy
// viewport region
{
SO_ACTION_CONSTRUCTOR( OverlayHighlightRenderAction );
selPath = NULL;
// Set up the local rendering graph
localRoot = new SoSeparator;
localRoot->ref();
SoPickStyle* pickStyle = new SoPickStyle;
pickStyle->style = SoPickStyle::UNPICKABLE;
localRoot->addChild( pickStyle );
// Set up camera to look at 0 <= x,y <= 1
orthoCam = new SoOrthographicCamera;
orthoCam->position.setValue( .5, .5, 1. );
orthoCam->height = 1.0;
localRoot->addChild( orthoCam );
SoLightModel* lmodel = new SoLightModel;
lmodel->model = SoLightModel::BASE_COLOR;
localRoot->addChild( lmodel );
SoColorIndex* color = new SoColorIndex;
color->index = 1;
localRoot->addChild( color );
SoDrawStyle* drawStyle = new SoDrawStyle;
drawStyle->style = SoDrawStyle::LINES;
drawStyle->lineWidth = 3;
drawStyle->linePattern = 0xffff;
localRoot->addChild( drawStyle );
coords = new SoCoordinate3;
coords->point.setNum( 0 );
localRoot->addChild( coords );
SoFaceSet* fset = new SoFaceSet;
fset->numVertices = 4;
localRoot->addChild( fset );
}
// Destructor
OverlayHighlightRenderAction::~OverlayHighlightRenderAction()
{
localRoot->unref();
if ( selPath != NULL )
selPath->unref();
}
// Update the bbox to surround the projected bounding box of
// the path.
// Use: protected
void
OverlayHighlightRenderAction::updateBbox( SoPath* p, SoCamera* camera )
{
coords->point.deleteValues( 0 ); // clear them all out
if ( camera == NULL )
return;
// Compute the 3d bounding box of the passed path
SoGetBoundingBoxAction bba( getViewportRegion() );
bba.apply( p );
SbVec3f min, max;
bba.getBoundingBox().getBounds( min, max );
// Project points to (0 <= x,y,z <= 1) screen coordinates
SbViewVolume vv = camera->getViewVolume();
SbVec3f screenPoint[8];
vv.projectToScreen( SbVec3f( min[0], min[1], min[2] ), screenPoint[0] );
vv.projectToScreen( SbVec3f( min[0], min[1], max[2] ), screenPoint[1] );
vv.projectToScreen( SbVec3f( min[0], max[1], min[2] ), screenPoint[2] );
vv.projectToScreen( SbVec3f( min[0], max[1], max[2] ), screenPoint[3] );
vv.projectToScreen( SbVec3f( max[0], min[1], min[2] ), screenPoint[4] );
vv.projectToScreen( SbVec3f( max[0], min[1], max[2] ), screenPoint[5] );
vv.projectToScreen( SbVec3f( max[0], max[1], min[2] ), screenPoint[6] );
vv.projectToScreen( SbVec3f( max[0], max[1], max[2] ), screenPoint[7] );
// Find the encompassing 2d box (0 <= x,y <= 1)
SbBox2f bbox2;
for ( int i = 0; i < 8; i++ )
bbox2.extendBy( SbVec2f( screenPoint[i][0], screenPoint[i][1] ) );
if ( !bbox2.isEmpty() )
{
float xmin, ymin, xmax, ymax;
bbox2.getBounds( xmin, ymin, xmax, ymax );
// Set up the coordinate node
coords->point.set1Value( 0, xmin, ymin, 0 );
coords->point.set1Value( 1, xmax, ymin, 0 );
coords->point.set1Value( 2, xmax, ymax, 0 );
coords->point.set1Value( 3, xmin, ymax, 0 );
}
}
// beginTraversal - render highlights for our selection node.
void
OverlayHighlightRenderAction::apply( SoNode* renderRoot )
{
// Do not render the scene - only render the highlights
// Is our cached path still valid?
if ( ( selPath == NULL ) || ( selPath->getHead() != renderRoot ) || ( !selPath->getTail()->isOfType( SoSelection::getClassTypeId() ) ) )
{
// Find the selection node under the render root
SoSearchAction sa;
sa.setFind( SoSearchAction::TYPE );
sa.setInterest( SoSearchAction::FIRST );
sa.setType( SoSelection::getClassTypeId() );
sa.apply( renderRoot );
// Cache this path
if ( selPath != NULL )
selPath->unref();
selPath = sa.getPath();
if ( selPath != NULL )
{
selPath = selPath->copy();
selPath->ref();
}
}
if ( selPath != NULL )
{
// Make sure something is selected.
SoSelection* sel = ( SoSelection* )selPath->getTail();
if ( sel->getNumSelected() == 0 )
return;
// Keep the length from the root to the selection
// as an optimization so we can reuse this data.
int reusablePathLength = selPath->getLength();
// For each selection path, create a new path rooted
// under our localRoot.
for ( int j = 0; j < sel->getNumSelected(); j++ )
{
// Continue the path down to the selected object.
// No need to deal with p[0] since that is the sel
// node.
SoPath* p = sel->getPath( j );
SoNode* pathTail = p->getTail();
if ( pathTail->isOfType( SoBaseKit::getClassTypeId() ) )
{
// Find the last nodekit on the path.
SoNode* kitTail = ( ( SoNodeKitPath* )p )->getTail();
// Extend the selectionPath until it reaches this
// last kit.
SoFullPath* fp = ( SoFullPath* )p;
int k = 0;
do
{
selPath->append( fp->getIndex( ++k ) );
} while ( fp->getNode( k ) != kitTail );
}
else
{
for ( int k = 1; k < p->getLength(); k++ )
selPath->append( p->getIndex( k ) );
}
// Find the camera used to render the selected object.
SoNode* camera;
SoSearchAction sa;
sa.setFind( SoSearchAction::TYPE );
sa.setInterest( SoSearchAction::LAST );
sa.setType( SoCamera::getClassTypeId() );
sa.apply( selPath );
camera = ( sa.getPath() == NULL ? NULL : sa.getPath()->getTail() );
// Get the bounding box of the object and update the
// local highlight graph.
updateBbox( selPath, ( SoCamera* )camera );
// Make sure the box has some size.
if ( coords->point.getNum() == 0 )
{
#ifdef DEBUG
SoDebugError::post("OverlayHighlightRenderAction::apply", "selected object has no bounding box - cannot render a highlight");
#endif
}
else
{
// Render the highlight.
SoGLRenderAction::apply( localRoot );
}
// Restore selPath for reuse.
selPath->truncate( reusablePathLength );
}
}
}
// Function stubs: we do not highlight paths and pathLists.
void
OverlayHighlightRenderAction::apply( SoPath* path )
{
SoGLRenderAction::apply( path );
}
void
OverlayHighlightRenderAction::apply( const SoPathList& pathList, SbBool obeysRules )
{
SoGLRenderAction::apply( pathList, obeysRules );
}

Main Program Using OverlayHighlightRenderAction shows a main program that uses this new highlight class. The highlight is drawn in the overlay planes, and the scene itself is drawn in the normal planes by a different render action.

Example : Main Program Using OverlayHighlightRenderAction

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <Inventor/SoDB.h>
#include <Inventor/SoInput.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <Inventor/nodes/SoSelection.h>
#include "OverlayHighlightRenderAction.h"
void
main( int, char* argv[] )
{
// Initialization
Widget mainWindow = SoXt::init( argv[0] );
OverlayHighlightRenderAction::initClass();
// Open the data file
SoInput in;
char* datafile = "monitor.iv";
if ( !in.openFile( datafile ) )
{
fprintf( stderr, "Cannot open %s for reading.\n", datafile );
return;
}
// Read the input file
SoNode* n;
SoSeparator* sep = new SoSeparator;
while ( ( SoDB::read( &in, n ) != FALSE ) && ( n != NULL ) )
sep->addChild( n );
// Create a selection root to show off our new highlight.
SoSelection* sel = new SoSelection;
sel->addChild( sep );
// Create a viewer.
SoXtExaminerViewer* viewer = new SoXtExaminerViewer( mainWindow );
viewer->setSceneGraph( sel );
// Set the overlay scene graph same as normal. For viewers,
// we have to cast to render area graph.
viewer->setOverlaySceneGraph( viewer->SoXtRenderArea::getSceneGraph() );
viewer->setTitle( "Overlay highlight" );
viewer->redrawOverlayOnSelectionChange( sel );
viewer->setOverlayGLRenderAction( new OverlayHighlightRenderAction );
// Set up the overlay color map
SbColor red( 1, 0, 0 );
viewer->setOverlayColorMap( 1, 1, &red );
viewer->show();
SoXt::show( mainWindow );
SoXt::mainLoop();
OverlayHighlightRenderAction::exitClass();
return 0;
}