Overview
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.
Expected Result
Bullet Hole Result
Example details
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 );
DtVecNormalize( rightVec, rightVec );
frontVec = rightVec.crossProduct( localDown );
DtVecNormalize( frontVec, frontVec );
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];
DtTaitBryan result;
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 ./bin64/exampleBulletHole_stealth.bat (on Windows) or ./bin64/exampleBulletHole_stealth.sh (on Linux). In Vantage go to menu "Settings->Visual Model Editors..", in "Interaction Definition Editor" select "BulletDetonation". With the "+" sign on the right panel, add a new "Detonation Bullet Holes" for the "Model Sets=3D Models" visualizer. After that, add the following "Visualizer Definition Attributes"; "type=DtBulletHoleVisualizer", "texture=(path to your VRV installation)/examples/exampleBulletHole/BulletHole.png", "radius=0.4". Load Makland Terrain in Vantage. Connect Vantage with DIS. Open VRForce with a empty scenario (connect with DIS), load Makland. Add a "Chilian M16" Character near a building in VRForce. Hit play, select the "Chilian M16" right click on him and select "Task->Engagement->Provide Suppresive Fire at Location...", Click on the building and click the ok button on the popup menu. You should then see the bullet detonation in Vantage on the building. For more information about running examples, please see Running Applications and Examples.
Example Source Files
DtBulletHoleUpdater.cxx
#include <boost/lexical_cast.hpp>
#include <matrix/vlMath.h>
namespace makVrv
{
: DtSceneObjectUpdater(de, id)
, mySource(0)
, myEnd(0, 0, 0)
, myDirty(false)
{
}
DtBulletHoleUpdater::~DtBulletHoleUpdater()
{
}
void DtBulletHoleUpdater::setEnabled( bool enabled )
{
}
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 );
DtVecNormalize( rightVec, rightVec );
frontVec = rightVec.crossProduct( localDown );
DtVecNormalize( frontVec, frontVec );
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];
DtTaitBryan result;
DtBodyToRef_to_Euler( resultDcm, &result );
mySceneObject->setOrientation(0, result);
}
else
{
mySceneObject->setVisible(false);
}
myDirty = false;
}
}
void DtBulletHoleUpdater::setBulletEnd( const DtVector& end)
{
myEnd = end;
myDirty = true;
}
void DtBulletHoleUpdater::setBulletSource(
DtUniqueID sourceId )
{
mySource = sourceId;
myDirty = true;
}
}
DtBulletHoleUpdater.h
#pragma once
#include <string>
#include <matrix/vlVector.h>
#include <matrix/vlTaitBryan.h>
namespace makVrv
{
{
public:
virtual ~DtBulletHoleUpdater();
virtual void update( double simTime );
virtual void setEnabled(bool enabled);
virtual void setBulletEnd(const DtVector& end);
virtual void setBulletSource(
DtUniqueID sourceId);
protected:
DtVector myEnd;
bool myDirty;
};
}
DtBulletHoleUpdaterAgent.cpp
DtBulletHoleUpdaterAgent.hpp
DtBulletHoleUpdaterClasses.cpp
DtBulletHoleUpdaterClasses.hpp
DtBulletHoleVisualizer.inl
#ifndef DtBulletHoleVisualizer_INL_
#error This file may only be included by DtBulletHoleVisualizer.h
#endif
#include <vrvCore/DtWidgetAgent.hpp>
#include <vrvCore/DtSceneObjectAgent.hpp>
#include <vrvCore/DtPolygonModelAgent.hpp>
#include "DtBulletHoleUpdaterAgent.hpp"
namespace makVrv
{
{
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;
}
}
}
DtBulletHoleVisualizer.h
#pragma once
namespace makVrv
{
class DtPolygonModelAgent;
{
public DtStateVisualizer
{
public:
DtBulletHoleVisualizer(DtBaseConnection& simulation,
DtStateListener& listener, int modelSet);
virtual ~DtBulletHoleVisualizer();
virtual void setParent(DtStateVisualizer*);
virtual void createModelAgents();
virtual void destroyModelAgents();
virtual void setVisualizerDefinition(const DtVisualizerDefinition&);
virtual void setModelDefinitionForAgents();
virtual const DtStateVisualizer::TypeInfo& typeInfo() const;
static const DtStateVisualizer::TypeInfo& theTypeInfo();
protected:
virtual void createBulletHole();
protected:
std::string myTexture;
float myRadius;
DtPolygonModelAgent* myPolygonAgent;
};
typedef DtStateVisualizerCreatorTemplate<DtBulletHoleVisualizer>
}
}
#define DtBulletHoleVisualizer_INL_
#undef DtBulletHoleVisualizer_INL_
DtMovablePolygonModel.cxx
#include <osg/Geometry>
#include <osgDB/ReadFile>
#include <osg/AlphaFunc>
namespace makVrv
{
: DtOsgPolygonModel(de, id)
{
DtModelInstance* mi =
new DtMovablePolygonModelInstance(myDe);
setModelInstance(0);
setModelInstance(mi);
signal_modelInstanceCreated( mi, this );
osg::StateSet* ss = static_cast<DtOsgModelInstance*>(mi)->rootNode()->getOrCreateStateSet();
ss->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
ss->setAttributeAndModes(new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.3), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
mi->enableColorBlending(true);
}
DtMovablePolygonModel::~DtMovablePolygonModel()
{
}
void DtMovablePolygonModel::setVisible(bool isVisible)
{
DtModel::setVisible(isVisible);
mySceneConnector->setVisible(isVisible);
}
void DtMovablePolygonModel::setPosition( int vtx, const DtVector& position )
{
if (vtx == 0)
{
mySceneConnector->setPosition(position);
}
else
{
DtPolygonModelInstance* mi =
dynamic_cast<DtPolygonModelInstance*>(modelInstance());
if (mi)
{
mi->setPosition(vtx - 1, position);
}
}
}
void DtMovablePolygonModel::setOrientation( int vtx, const DtTaitBryan& orientation )
{
if (vtx == 0)
{
mySceneConnector->setOrientation(orientation);
}
else
{
DtPolygonModelInstance* mi =
dynamic_cast<DtPolygonModelInstance*>(modelInstance());
if (mi)
{
mi->setOrientation(vtx - 1, orientation);
}
}
}
void DtMovablePolygonModel::DtMovablePolygonModelInstance::addToConnector( DtOsgSceneConnector* connector )
{
osg::Group* parent = connector->getOrCreateTransformRoot();
if (!parent->containsNode(myGeode.get()))
{
parent->addChild(myGeode.get());
}
}
void DtMovablePolygonModel::update()
{
bool localMyNeedsUpdate = myNeedsUpdate;
DtOsgPolygonModel::update();
if (localMyNeedsUpdate)
{
DtPolygonModelInstance* mi =
dynamic_cast<DtPolygonModelInstance*>(modelInstance());
osg::Geode* geode = static_cast<osg::Geode*>(mi->rootNode());
if (geode->getNumDrawables()>0)
{
osg::BoundingBox bb;
bb._min = osg::Vec3f(-1,-1,-1);
bb._max = osg::Vec3f(1,1,1);
geode->getDrawable(0)->setInitialBound(bb);
geode->getDrawable(0)->getBound();
}
}
}
}
DtMovablePolygonModel.h
#pragma once
namespace makVrv
{
class DtDe;
class DtOsgSceneConnector;
: public DtOsgPolygonModel
{
public:
class DtMovablePolygonModelInstance : public DtPolygonModelInstance
{
public:
DtMovablePolygonModelInstance( DtDe& de )
: DtPolygonModelInstance(de)
{
}
virtual ~DtMovablePolygonModelInstance()
{
}
virtual void addToConnector( DtOsgSceneConnector* connector );
};
virtual ~DtMovablePolygonModel();
virtual void setVisible(bool isVisible);
virtual void setPosition( int vtx, const DtVector& position );
virtual void setOrientation( int vtx, const DtTaitBryan& orientation );
protected:
virtual void update();
};
}
DtMovablePolygonModelAgent.cpp
DtMovablePolygonModelAgent.hpp
DtMovablePolygonModelClasses.cpp
DtMovablePolygonModelClasses.hpp
exampleBulletHolePlugin.cxx
#include <boost/bind.hpp>
#include <iostream>
#define DL_DLL_IGCONVRLINK DT_DLL_EXAMPLEBULLETHOLE
#define DT_PROTOCOL_NAMESPACE vrvDis
#define DtDIS 1
#include "DtMovablePolygonModelClasses.hpp"
using namespace makVrv;
#undef DT_PROTOCOL_NAMESPACE
#undef DtDIS
void initializeBulletHoleMenuItem(
DtDe& de,
int bulletHoleVisualTypeId)
{
}
{
std::cout << "Loading bullet hole plugin" << std::endl;
{
"DtPolygonModel", new DtMovablePolygonModelClassesCreator);
const int bulletHoleVisualTypeId = 976;
de, "BulletHoleItem", bulletHoleVisualTypeId));
"Bullet Holes", "../examples/exampleBulletHole/BulletHole.png",
if(app)
{
}
}
}
{
{
return true;
}
return false;
}
exampleBulletHolePlugin.h
#ifndef exampleBulletHolePlugin_h_
#define exampleBulletHolePlugin_h_
#define DT_DE_PLUGIN_EXPORT_MACRO DT_DLL_EXAMPLEBULLETHOLE
namespace makVrv { class DtDe; }
#endif
[<< Examples] [Home] [Top of Page]