This example demonstrates how to add a new kind of visualizer to VR-Vantage.
To demonstrate, we'll add the ability to draw bullet detonations as bullet hole decals on the terrain.
To draw the bullet hole decals we can use a distributed polygon model. Since the simulation often doesn't correlate perfectly with the terrain loaded in VR-Vantage, we'll also need a scene object updater to set the position of the model so that it lines up perfectly with the terrain. The visualizer itself will read the simulated detonation's state and construct the decal and updater to visualize it.
In the plugin's initialization function, we can register the new visualizer and:
DtStateVisualizerFactory::instance(de).addStateVisualizerCreator(de,
DtAgentFactory::instance(de).registerAgentCreator(
"DtPolygonModel", new DtMovablePolygonModelClassesCreator);
const int bulletHoleVisualTypeId = 976;
DtQtMenuAssembler::instance(de).registerMenu(new DtObserverSettingItem(
de, "BulletHoleItem", bulletHoleVisualTypeId));
DtVisualTypeManager::instance(de).registerVisualType(
DtVisualTypeManager::VisualType("bullet_holes", bulletHoleVisualTypeId,
"Bullet Holes", "../examples/exampleBulletHole/BulletHole.png",
DtVisualTypeManager::ModelSetAll));
if(app)
{
DtMenuPath::menu("DtObserverMenu").item("BulletHoleItem"));
DtToolbarPath::toolbar("DtObserverSettingsToolbar").item("BulletHoleItem"));
}
In the GUI, we can create a new visual definition and map bullet detonations to the bullet hole visualizer. For more information see the comment in DtBulletHoleVisualizer.h. Once this is done, the visualizer will be created whenever the simulation reports a bullet detonation interaction. In turn, the visualizer will create its model and updater according to its visual definition:
DtBulletHoleVisualizer::DtBulletHoleVisualizer(
DtBaseConnection& simulation, DtStateListener& listener, int modelSet)
: DtStateVisualizer(simulation, listener, modelSet)
, myPolygonAgent(0)
, myRadius(1)
{
}
DtBulletHoleVisualizer::~DtBulletHoleVisualizer()
{
destroyModelAgents();
}
void DtBulletHoleVisualizer::createModelAgents()
{
if (((DtVrlinkDetonationInteractionStateListener*)&myListener)->detonationResult()
== DtDetResGroundImpact)
{
createBulletHole();
}
}
void DtBulletHoleVisualizer::destroyModelAgents()
{
if (mySceneObjectAgent)
{
mySimulation.temporaryEffects().takeAgent(mySceneObjectAgent, 60, 0);
mySceneObjectAgent = 0;
}
if (myPolygonAgent)
{
mySimulation.temporaryEffects().takeAgent(myPolygonAgent, 60, 0);
myPolygonAgent = 0;
}
}
void DtBulletHoleVisualizer::setVisualizerDefinition(const DtVisualizerDefinition& visDef)
{
const DtVisualizerAttributeFilename* texFile = dynamic_cast<const DtVisualizerAttributeFilename*>(
visDef.findAttribute("texture"));
if (texFile)
{
myTexture = texFile->value().toAscii();
}
const DtVisualizerAttributeFloat* radius = dynamic_cast<const DtVisualizerAttributeFloat*>(
visDef.findAttribute("radius"));
if (radius)
{
myRadius = radius->value();
}
else
{
myRadius = 1;
}
DtStateVisualizer::setVisualizerDefinition(visDef);
}
void DtBulletHoleVisualizer::setModelDefinitionForAgents()
{
}
void DtBulletHoleVisualizer::setParent(DtStateVisualizer*)
{
}
void DtBulletHoleVisualizer::createBulletHole()
{
DtStateVisualizer::createModelAgents();
DtVrlinkDetonationInteractionStateListener* detStateListener =
(DtVrlinkDetonationInteractionStateListener*)&myListener;
if(!mySceneObjectAgent)
{
mySceneObjectAgent = DtSceneObjectAgent::create(mySimulation.sceneInterface(), myDistributeFlag);
mySceneObjectAgent->setModelSet(myModelSet);
}
myPolygonAgent = DtPolygonModelAgent::create(mySimulation.sceneInterface(), myDistributeFlag);
mySceneObjectAgent->addModel(myPolygonAgent, myVisualizerType);
mySceneObjectAgent->setPosition(1, DtVector(-myRadius, -myRadius, 0.1));
mySceneObjectAgent->setPosition(2, DtVector(myRadius , -myRadius, 0.1));
mySceneObjectAgent->setPosition(3, DtVector(myRadius , myRadius, 0.1));
mySceneObjectAgent->setPosition(4, DtVector(-myRadius , myRadius, 0.1));
DtBulletHoleUpdaterAgent* updater = DtBulletHoleUpdaterAgent::create(mySimulation.sceneInterface(), myDistributeFlag);
updater->setSceneObject( mySceneObjectAgent );
updater->setBulletSource(detStateListener->sourceId());
updater->setBulletEnd(detStateListener->location());
myPolygonAgent->setNumTextureTiles(1);
myPolygonAgent->setTexturePath(myTexture);
}
const DtStateVisualizer::TypeInfo& DtBulletHoleVisualizer::typeInfo() const
{
return theTypeInfo();
}
const DtStateVisualizer::TypeInfo& DtBulletHoleVisualizer::theTypeInfo()
{
static DtVisualizerSchema* schema = 0;
if (!schema)
{
schema = new DtVisualizerSchema("DtBulletHoleVisualizer",
DtUnicode::tr("Detonation Bullet Holes"));
DtVisualizerSchema::Parameter
param;
param.myName = "texture";
param.myDescription = DtUnicode::tr("Texture file for the bullet hole decals");
param.myRequired = true;
param.myVisualizerAttributeTypeInfoName = DtVisualizerAttributeFilename::theTypeInfo().className;
schema->addParameter(param);
param.myName = "radius";
param.myDescription = DtUnicode::tr("Radius in meters of the bullet hole decals");
param.myRequired = false;
param.myVisualizerAttributeTypeInfoName = DtVisualizerAttributeFloat::theTypeInfo().className;
schema->addParameter(param);
}
static DtStateVisualizer::TypeInfo thisTypeInfo(
"DtBulletHoleVisualizer",
DtUnicode::tr("Bullet Hole Visualizer"),
InteractionVisualizerType,
*schema);
return thisTypeInfo;
}
To place the bullet hole decal, we will make a vector from the shooter to the detonation and intersect it with the terrain. The bullet hole decal will be placed at the location of that intersection and rotated to have the same normal vector. Since intersections require access to the current scene graph, we use an updater to do the calculation:
void DtBulletHoleUpdater::update( double simTime )
{
if (myDirty)
{
DtVector bulletSource;
DtAgentUpdateResolverInterface* resolver =
myDe.agentManager().findUpdater( mySource );
if( resolver )
{
DtSceneObject* sceneObject = resolver->
castObjectTypeFromUpdater<DtSceneObject>( "DtSceneObject" );
if ( sceneObject )
{
sceneObject->getPosition(0, bulletSource);
}
}
DtIntersector& intersector = DtIntersectorManager::instance(myDe).modelSetIntersector( mySceneObject->modelSet() );
DtVector trajectory = myEnd - bulletSource;
DtVector segStart = bulletSource + DtVector(trajectory.x()*0.5, trajectory.y()*0.5, trajectory.z()*0.5);
DtVector segmentEnd = segStart + trajectory;
DtIntersectorResult isectResult;
bool hit = intersector.findFirstIntersectWithLineSegment(
segStart.x(), segStart.y(), segStart.z(),
segmentEnd.x(), segmentEnd.y(), segmentEnd.z(),
isectResult, DtIntersector::SupportNodes);
if (hit)
{
mySceneObject->setVisible(true);
mySceneObject->setPosition(0, DtVector(isectResult.x, isectResult.y, isectResult.z));
DtTaitBryan orientation;
DtVector localDown;
DtVecNeg( DtVector(isectResult.normalX, isectResult.normalY, isectResult.normalZ), localDown );
DtDcm localOriDcm( orientation );
DtVector frontVec;
frontVec[0] = localOriDcm[1][0];
frontVec[1] = localOriDcm[0][0];
frontVec[2] = -localOriDcm[2][0];
DtVector rightVec = localDown.crossProduct( frontVec );
frontVec = rightVec.crossProduct( localDown );
DtDcm resultDcm;
register double* resDcmPtr = &resultDcm[0][0];
*resDcmPtr++ = frontVec[1];
*resDcmPtr++ = rightVec[1];
*resDcmPtr++ = localDown[1];
*resDcmPtr++ = frontVec[0];
*resDcmPtr++ = rightVec[0];
*resDcmPtr++ = localDown[0];
*resDcmPtr++ = -frontVec[2];
*resDcmPtr++ = -rightVec[2];
*resDcmPtr = -localDown[2];
DtBodyToRef_to_Euler( resultDcm, &result );
mySceneObject->setOrientation(0, result);
}
else
{
mySceneObject->setVisible(false);
}
myDirty = false;
}
}
Building the Example
VR-Vantage includes pre-built versions of the example plug-in. To build it yourself, follow the instructions at Building VR-Vantage Examples, Applications, and Plug-ins.
Running the Example
This example is a plug-in. You can run it by running ./bin/exampleBulletHole_stealth.bat (on Windows) or ./bin/exampleBulletHole_stealth.sh (on Linux). Follow the instructions in DtBulletHoleVisualizer.h on setting up the element definition and enabling the bullet holes setting. For more information about running examples, please see Running Applications and Examples.