Version 4 (modified by osg, 6 years ago)

Note on changing IntersectVisitor? to IntersectionVisitor?

Intersection Tests

(Wiki editing note: Needs conversion to new IntersectionVisitor (IntersectVisitor is deprecated)
(Wiki editing note: Code needs conversion to osgViewer)
(Wiki editing note: Add link to complete source code at bottom)

Goal

Add an absurd number of randomly located billboard shrubs to a scene. Use the osgUtil intersection functions to find the correct height (z) value on a terrain model to place the billboards.

Overview

If we want to extend the previous tutorial to add some arbitrary number of billboards to a scene that includes a terrain model, it would be useful to be able to retrieve the height (z) of terrain at a given east-west/north-south (x/y) position. We can do this by using the functions provided by the osgUtil intersection modules. For more information on the osgUtil classes see documentation here. The basic classes we'll use are described briefly:

LineSegments (osg::LineSegment) Intersection detection is based on rays. Line segments provide a means to define these rays. Line segments consist of two osg::Vec3 instances - one to define a start point and one to define the end point. When invoked, intersection detection is performed along this ray.

Hits (osgUtil::Hit) Provide programmers access to the basic values associated with a intersection dection test. A hit contains the information avaiable when a ray intersects geometry in the scene. Of primary interest, Hit instances provide local or world coordinate position and normal data as a Vec3. The methods getLocalIntersectPoint, getLocalIntersectNormal, getWorldIntersectPoint and get worldIntersectNormal each return an osg::Vec3 instance with local/world instersection point/normal values.

HitList (osgUtil::!IntersectVisitor::HitList) A single segment may intersect any number of geometry instances within a scene (or with the same geometry multiple times.) For each segment that intersection detection is peformed, a list is created. This list contains the Hit instances associated with each intersection detected. If no intersections are detected, the list remains empty.

IntersectVisitor (osgUtil::IntersectVisitor) Intersection detection tests between a ray and geometry instances in a scene are initiated by creating and applying an intersection visitor. The intersectionVisitor class is derived from the NodeVisitor class and so are created and invoked in much the same manner as NodeVisitor instances (this may help.) A list of line segments to use for intersection detection is maintained. For each of these segments, a list of hits (osgUtil::!IntersectVisitor::HitList instance) is created.

The code

To correctly determine the height at which to locate models on the terrain we'll follow these basic steps:

  • Create a LineSegment instance using two Vec3 instances to define start and end points of the ray used for intersection detection.
  • Create an IntersectVisitor instance.
  • Add the LineSegment instances to the IntersectVisitor
  • Initiate IntersectVisitor traversal of the scene graph at the appropriate node.
  • Retrieve the world coordinates results of the intersection detection test.

The following code demonstrates these steps for two cases. The first section handles adding a single LineSegment instance to an IntersectVisitor to determine the correct height at which to place a tank model. The second section handles adding multiple line segments for an arbitrary number of billboards. For each billboard we want to add, we'll add a LineSegment instance to our IntersectVisitor. To position billboards, we'll use the intersection detection associated with individual LineSegments.

    #include <osg/Geometry>
    #include <osg/Texture2D>
    #include <osg/Billboard>
    #include <osg/BlendFunc>
    #include <osgDB/Registry>
    #include <osgDB/ReadFile>
    #include <osgProducer/Viewer>
    #include <osg/PositionAttitudeTransform>
    #include <ctime> 
    #include <cstdlib> 

    #define NUMBER_O_SHRUBS 1250

    osg::Drawable* createShrub(const float & scale, osg::StateSet* bbState)
    {
       float width = 1.5f;
       float height = 3.0f;

       width *= scale;
       height *= scale;

       osg::Geometry* shrubQuad = new osg::Geometry;

       osg::Vec3Array* shrubVerts = new osg::Vec3Array(4);
       (*shrubVerts)[0] = osg::Vec3(-width/2.0f, 0, 0);
       (*shrubVerts)[1] = osg::Vec3( width/2.0f, 0, 0);
       (*shrubVerts)[2] = osg::Vec3( width/2.0f, 0, height);
       (*shrubVerts)[3] = osg::Vec3(-width/2.0f, 0, height);

       shrubQuad->setVertexArray(shrubVerts);

       osg::Vec2Array* shrubTexCoords = new osg::Vec2Array(4);
       (*shrubTexCoords)[0].set(0.0f,0.0f);
       (*shrubTexCoords)[1].set(1.0f,0.0f);
       (*shrubTexCoords)[2].set(1.0f,1.0f);
       (*shrubTexCoords)[3].set(0.0f,1.0f);
       shrubQuad->setTexCoordArray(0,shrubTexCoords);

       shrubQuad->addPrimitiveSet(new osg::DrawArrays osg::PrimitiveSet::QUADS,0,4));

       shrubQuad->setStateSet(bbState); 

       return shrubQuad;
    }

    int main( int argc, char **argv )
    {
       osgProducer::Viewer viewer;

       osg::Group* rootNode = new osg::Group();
       osg::Node* tankNode = NULL;
       osg::Node* terrainNode = NULL;
       osg::PositionAttitudeTransform* tankXform = 
          new osg::PositionAttitudeTransform();

       tankNode = osgDB::readNodeFile("\\Models\\T72-Tank\\T72-tank_des.flt");
       terrainNode = osgDB::readNodeFile("\\Models\\JoeDirt\\JoeDirt.flt");
       if (! (tankNode && terrainNode))
       {
          std::cout << "Couldn't load models, quitting." << std::endl;
          return -1;
       }
       rootNode->addChild(terrainNode);
       rootNode->addChild(tankXform);
       tankXform->addChild(tankNode);

This section finds the height of the terrain model at a given X,Y location and uses this data to position a tank model.

       double tankXPosition = -10.0;
       double tankYPosition = -10.0;

       osg::LineSegment* tankLocationSegment = new osg::LineSegment();
       tankLocationSegment->set(
          osg::Vec3(tankXPosition, tankYPosition, 999) ,
          osg::Vec3(tankXPosition, tankYPosition, -999) );

       osgUtil::IntersectVisitor findTankElevationVisitor;
       findTankElevationVisitor.addLineSegment(tankLocationSegment);
       terrainNode->accept(findTankElevationVisitor);

       osgUtil::IntersectVisitor::HitList tankElevationLocatorHits;
       tankElevationLocatorHits = 
          findTankElevationVisitor.getHitList(tankLocationSegment);
       osgUtil::Hit heightTestResults;
       if ( tankElevationLocatorHits.empty() )
       {
          std::cout << " couldn't place tank on terrain" << std::endl;
          return -1;
       }
       heightTestResults = tankElevationLocatorHits.front();
       osg::Vec3d terrainHeight = heightTestResults.getWorldIntersectPoint();

       tankXform->setPosition( terrainHeight );
       tankXform->setAttitude( 
          osg::Quat(osg::DegreesToRadians(-45.0), osg::Vec3(0,0,1) ) ); 

This section of code sets up our viewer, billboard node, and state as before.

       viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);

       viewer.setSceneData(rootNode);
       viewer.realize();

       osg::Billboard* shrubBillBoard = new osg::Billboard();
       rootNode->addChild(shrubBillBoard);

       shrubBillBoard->setMode(osg::Billboard::AXIAL_ROT);
       shrubBillBoard->setAxis(osg::Vec3(0.0f,0.0f,1.0f));
       shrubBillBoard->setNormal(osg::Vec3(0.0f,-1.0f,0.0f));

       osg::Texture2D *ocotilloTexture = new osg::Texture2D;
       ocotilloTexture->setImage(osgDB::readImageFile("images\\ocotillo.png"));

       osg::StateSet* billBoardStateSet = new osg::StateSet;

       billBoardStateSet->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
       billBoardStateSet->setTextureAttributeAndModes(0, ocotilloTexture, osg::StateAttribute::ON );
       billBoardStateSet->setAttributeAndModes( new osg::BlendFunc, osg::StateAttribute::ON );

       osg::AlphaFunc* alphaFunction = new osg::AlphaFunc;
       alphaFunction->setFunction(osg::AlphaFunc::GEQUAL,0.05f);
       billBoardStateSet->setAttributeAndModes( alphaFunction, osg::StateAttribute::ON );

Follow the same basic steps above for determining the collision point of a ray and the terrain model. This time we'll add multiple LineSegments to our IntersectVisitor. The results (position of intersection) of the intersection test for each of these rays will be used to position a billboard.

       srand(time(0)); // Initialize random number generator.

       osgUtil::IntersectVisitor isectVisitor;
       osg::LineSegment* terrainIsect[NUMBER_O_SHRUBS];

       int randomX, randomY;

       for (int i=0; i< NUMBER_O_SHRUBS; i++ )
       {
          randomX = (rand() % 100) + 1;
          randomY = (rand() % 100) + 1;
          terrainIsect[i] = new osg::LineSegment(
             osg::Vec3(randomX, randomY, 999) ,
             osg::Vec3(randomX, randomY, -999) );
          isectVisitor.addLineSegment(terrainIsect[i]);
       }
       terrainNode->accept(isectVisitor);

       osg::Drawable* shrubDrawable[NUMBER_O_SHRUBS];

       for (int j = 0 ; j < NUMBER_O_SHRUBS; j ++)
       {
          float randomScale = ((rand() % 15) + 1 ) / 10.0;
          shrubDrawable[j] = createShrub( randomScale, billBoardStateSet);
          osgUtil::IntersectVisitor::HitList hitList = isectVisitor.getHitList(terrainIsect[j]);
          if (! hitList.empty() )
          { 
             osgUtil::Hit firstHit = hitList.front();
             osg::Vec3d shrubPosition = firstHit.getWorldIntersectPoint();

             // osg::Vec3d shrubPosition =
             // isectVisitor.getHitList(terrainIsect[j]).front().getWorldIntersectPoint();

             shrubBillBoard->addDrawable( shrubDrawable[j] , shrubPosition );
          }
       } 

       while( !viewer.done() )
       {
          // wait for all cull and draw threads to complete.
          viewer.sync();

          // update the scene by traversing it with the update visitor which will
          // call all node update callbacks and animations.
          viewer.update();

          // fire off the cull and draw traversals of the scene.
          viewer.frame();
       }
       // wait for all cull and draw threads to complete before exit.
       viewer.sync();
       return 0;
    }