VR-Forces Developer's Guide
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
exampleSymbolDecoration

Table of Contents

Overview

Shows how to add a symbol decoration (additional text that can be displayed next to an entity icon) on a 2D display

Expected Result

exampleSymbolDecoration2.png
Symbol Decoration Result

Example details

The purpose of this example is to show how to add a new Symbol Decoration to a Decoration Set. A Symbol Decoration is a label plus additional text that can be displayed next to an entity. A Decoration Set is a collection of Symbol Decorations. VR-Vantage defines Decoration Sets for entities, props, and tactical graphics. In this examble a Symbol Decoration that consists of a list of DIS / RPR FOM articulated part ids, called "Articulated Part IDs" is added to the Decoration Set for default and ground entities. The symbol decoration set that an entity uses is set in its element definition. Provided an entity's symbol decoration set contains the specified symbol decoration (ground entities for this example), the user will be able to turn this new Symbol Decoration on or off from the Settings / Display / Symbol Decorations dialog. The default set provided in VR-Vantage are: defaultEntity, defaultAirEntity, defaultAirEntity3d, defaultMunitionEntity, defaultSubsurfaceEntity, defaultSurfaceEntity, defaultGroundEntity, defaultProp, and defaultTacticalGraphic.

Plugin Initialization

When a plugin is loaded, VR-Vantage invokes its initDeModule function. This plugin simply calls its init function from initDeModule.

{
// Setup the plugin. Normally, the init function and functionality
// should be in its own library.
init(*de);
return true;
}

The init function ensures that it will only ever be initialize once (even if the function is called multiple times).

Provided the plugin has been loaded into a VR-Vantage application with a master display engine, the plugin registers a creator for the DtArtPartSymbolDecoration it will display (defined in DtArtPartSymbolDecoration.cxx) with the DtSymbolDecorationFactory.

DtSymbolDecorationFactory::instance(de).registerCreator(
new DtArtPartSymbolDecorationCreator());

Finally, the init function connects to the display engine's post initialize signal, requesting that the local function load be invoked when the display engine is initialized.

postInitializeConnection = de.signal_postInitialize.connect(boost::bind(
&load, &de));

When the display engine has been fully initialized, it raises the post initialize signal, which causes the load function to be called. The load function disconnects from the post initialize signal (it's no longer needed) and asks for a DtWriteSettingsLock on the DtSymbolDecorationSettings object (this object is shared across multiple threads, so a mutex must be used).

DtWriteSettingsLock<DtSymbolDecorationSettings>
settings(DtSharedSettingsManager::instance(*de));

Once the function is cleared to write, it gets a copy of the "defaultEntity" and "defaultGroundEntity" Decoration Sets

DtSymbolDecorationSettings::DecorationSet decorationSet =
settings->getOrCreateDecorationSet( "defaultEntity" );

adds the "DtArtPartSymbolDecoration" Symbol Decoration to the copy

decorationSet.insert( "DtArtPartSymbolDecoration" );

and then sets the Decoration Set for "defaultEntity" to be the modified Decoration Set.

settings->setDecorationSet( "defaultEntity", decorationSet );

"defaultEntity" Decoration Set is used by entities but ground entities have their own set type so the symbol decoration is also added to the "defaultGroundEntity" set. This allows the example to display the art parts for the likes of the M1A2 tank, shown in the image above.

Articulated Part Symbol Decoration

The DtArtPartSymbolDecoration class implements the new Articulated Part Symbol Decoration. Symbol Decorations are created for each object as it is discovered. If the current display settings indicate that the Articulated Part Symbol Decoration should be visible, then its visualize method will be called after it is created (or when the Articulated Part Symbol Decoration type is enabled). The visualize method tells the Decoration Set visualizer that it will be a Text / Value type decoration, and provides the label for the text portion.

myParentVisualizer.widget()->setTypeValueText(
myIndex, DtUnicode::fromAscii("Articulated Part IDs"));

It creates an updater agent, which in turn causes an updater to be created for each rendering engine (see Articulated Part Symbol Decoration Updater) that will periodically update the label, in case any parts areadded or removed.

myUpdater = DtArtPartDecorationUpdaterAgent::create(
myParentVisualizer.simulation().sceneInterface());

The updater is initialized with a pointer to the scene object of the visualizer responsible for visualizing the entire Decoration Set for that object,

myUpdater->setSceneObject(myParentVisualizer.sceneObject());

the id of the widget object owned by the Decoration Set,

myUpdater->attachAttributeGroupWidget(myParentVisualizer.widget()->uniqueId());

and with the index in the list of displayed Symbol Decorations that the Articulated Part Symbol Decoration will use.

myUpdater->setAttributeIndex(myIndex);

Conversely, unvisualize is called when the symbol decoration should be hidden; it sets the Symbol Decoration type for its index to Null, and deletes the DtArtPartDecorationUpdater created in visualize.

if (myParentVisualizer.widget())
{
myParentVisualizer.widget()->setTypeNull(myIndex);
}
delete myUpdater;
myUpdater = 0;

Articulated Part Symbol Decoration Updater

DtArtPartSymbolDecoration creates a DtArtPartDecorationUpdaterAgent when visualize is called. This causes an instance of the distributed object DtArtPartDecorationUpdater to be created on each rendering engine (master display engine and any Vr-Vantage remote display engines that may be in use).

The updater inherits from DtTickable; the DtArtPartDecorationUpdater constructor registers the updater with the DtTickableManager, will will call the virtual needsUpdate method each redner frame, and if needsUpdate returns true, will call the update method.

NeedsUpdate checks that it has a valid pointer to the widget to be updated, a valid index into that widget, and checks a timer variable which determines if the Symbol Decoration is due for an update.

if (!myAttributeGroupWidget // no widget to update
|| myLastSymbolUpdateTime > tNow - 1.0 // only update once/second for efficiency
|| myLabelIndex == -1)// no label in the widget has been assigned yet))
{
return false;
}
return true;

If any of these tests fail, needsUpdate returns false; otherwise needsUpdate returns true, and udpate is called.

myLastSymbolUpdateTime = simTime;

Update gets the scene object the updater is attached to. Note that this will be the scene object used to visualize the 2D representation, as Symbol Decorations are a 2D feature.

DtSceneObject* so = mySceneObject;

The scene object contains the elementID of the element being visualized. That can be used to look up the scene object of the 3D representation (which will have the articulated part information we want).

if (so)
{
// get 3d model set scene object for the same simulated element
so = findSceneObjectFor( so->elementID(), DtModelSetId::Models3D );
}

Provided the 3D scene object is found, the updater then iterates through the list of models held by the scene object, using a dynamic cast to find models that are of type DtArticulatedModel. For each DtArticulatedModel found, the updater gets the part id and adds it to a vector of part ids.

for (DtSceneObject::ModelList::iterator iter = models.begin();
iter != models.end(); ++iter)
{
DtArticulatedModel* artModel =
dynamic_cast<DtArticulatedModel*>(iter->model);
if (artModel)
{
artModel->getParts(parts); // appends to the end of parts
}
}

The updater iterates over the vector of part ids, converting each to string form and adding it to a character buffer.

unsigned int bufSize = 10 * parts.size() + 1;
char* bufStart = new char[bufSize];
char* buf = bufStart;
bufStart[0] = '\0'; // make sure the string is null terminated even when there are no parts
for (DtArticulatedModel::PartList::iterator iter = parts.begin();
iter != parts.end(); ++iter)
{
int partId = *iter;
int sizeLeft = bufSize - (buf - bufStart);
int written = DtSnprintf(buf, sizeLeft, "%i ", partId);
buf += written;
}

Finally, the value field for the Symbol Decoration is set to the buffer.

myAttributeGroupWidget->setValueTextValue(
myLabelIndex, DtUnicode::fromAscii(bufStart));

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/exampleSymbolDecoration_stealth.bat (on Windows) or ./bin64/exampleSymbolDecoration_stealth.sh (on Linux). Connect to a network connection which has entites being published. Switch to PVD mode. Enable the articulated parts id entity label to see the example plugin's text next to the entities. To enable the "articulated parts id" from the toolbar, select the dropdown menu "Show/hide symbol decoration settings" and click on "Show Articulated Parts". For more information about running examples, please see Running Applications and Examples.

Example Source Files


exampleSymbolDecorationPlugin.h

/******************************************************************************
** Copyright (c) 2019 MAK Technologies, Inc.
** All rights reserved.
******************************************************************************/
#pragma once
// Get proper local export symbol
// Setup proper plugin export symbol
#define DT_DE_PLUGIN_EXPORT_MACRO DT_DLL_EXAMPLESYMBOLDECORATION
// Declare & export the plugin initialization function (bool initDeModule(DtDe* de))
namespace makVrv
{
class DtDe;
}
// Work function for the plugin initialization

exampleSymbolDecorationPlugin.cxx

/******************************************************************************
** Copyright (c) 2024 MAK Technologies, Inc.
** All rights reserved.
******************************************************************************/
#include <vrvCore/DtDe.h>
using namespace makVrv;
static vrvSignalsLib::connection postInitializeConnection;
void load(DtDe* de)
{
// We're done with the signal, so disconnect from it
postInitializeConnection.disconnect();
// The symbol decoration settings are shared across multiple threads, so
// they're controlled by the DtSharedSettingsManager. To write to them,
// we need to get a write locked settings object from the manager:
{
settings->getOrCreateDecorationSet( "defaultEntity" );
decorationSet.insert( "DtArtPartSymbolDecoration" );
settings->setDecorationSet( "defaultEntity", decorationSet );
// Add the art part decoration to the "defaultGroundEntity" decoration set
// This means the decorations will work with ground entities e.g. M1A2
// For other types of entities, the possible options are:
// Decoration set to use; provided sets are:
// defaultEntity
// defaultAirEntity
// defaultAirEntity3d
// defaultMunitionEntity
// defaultSubsurfaceEntity
// defaultSurfaceEntity
// defaultGroundEntity,
// defaultProp
// defaultTacticalGraphic
settings->getOrCreateDecorationSet("defaultGroundEntity");
decorationGroundSet.insert("DtArtPartSymbolDecoration");
settings->setDecorationSet("defaultGroundEntity", decorationGroundSet );
}
} // end of method
void init(DtDe& de)
{
// Ensure that init only gets called once
// (not strictly necessary here, but this is good practice in general)
if (de.isInMasterMode())
{
// The new decoration must be added to a decoration set; the settings
// manager that owns decoration sets can't be used until after the
// display engine is initialized, so we connect to its post initialize
// signal and access the settings later.
postInitializeConnection = de.signal_postInitialize.connect(boost::bind(
&load, &de));
}
} // end of method
{
// Setup the plugin. Normally, the init function and functionality
// should be in its own library.
init(*de);
return true;
}

DtArtPartSymbolDecoration.h

/*****************************************************************************
* Copyright (c) 2023 MAK Technologies, Inc.
* All rights reserved.
*****************************************************************************/
#pragma once
namespace makVrv
{
class DtArtPartDecorationUpdaterAgent;
class DT_DLL_EXAMPLESYMBOLDECORATION DtArtPartSymbolDecoration
: public DtSymbolDecoration
{
public:
DtArtPartSymbolDecoration( unsigned int index,
DtSymbolDecorationVisualizer& parentVisualizer );
virtual ~DtArtPartSymbolDecoration();
virtual void visualize();
virtual void unvisualize();
protected:
unsigned int myIndex;
DtSymbolDecorationVisualizer& myParentVisualizer;
DtArtPartDecorationUpdaterAgent* myUpdater;
};
class DT_DLL_EXAMPLESYMBOLDECORATION DtArtPartSymbolDecorationCreator
: public DtSymbolDecorationCreator
{
public:
DtArtPartSymbolDecorationCreator();
virtual ~DtArtPartSymbolDecorationCreator();
virtual DtSymbolDecoration* create( DtSymbolDecorationVisualizer& parentVisualizer );
virtual DtSymbolDecorationCreator* clone();
virtual const DtUnicode& displayName() const;
virtual const std::string& decorationClassName() const;
virtual const std::string& tag() const;
};
}

DtArtPartSymbolDecoration.cxx

/*****************************************************************************
* Copyright (c) 2023 MAK Technologies, Inc.
* All rights reserved.
*****************************************************************************/
#include "DtArtPartDecorationUpdaterAgent.hpp"
#include <vrvCore/DtAttributeGroupWidgetAgent.hpp>
#include <vrvCore/DtSceneObjectAgent.hpp>
#include <vrvCore/DtSymbolDecorationUpdaterAgent.hpp>
namespace makVrv
{
unsigned int index, DtSymbolDecorationVisualizer& parentVisualizer )
: DtSymbolDecoration()
, myIndex( index )
, myParentVisualizer( parentVisualizer )
, myUpdater( 0 )
{
}
DtArtPartSymbolDecoration::~DtArtPartSymbolDecoration()
{
if ( myParentVisualizer.widget() )
{
myParentVisualizer.widget()->setTypeNull(myIndex);
}
delete myUpdater;
myUpdater = 0;
}
void DtArtPartSymbolDecoration::visualize()
{
myParentVisualizer.widget()->setTypeValueText(
myIndex, DtUnicode::fromAscii("Articulated Part IDs"));
myUpdater = DtArtPartDecorationUpdaterAgent::create(
myParentVisualizer.simulation().sceneInterface());
myUpdater->setSceneObject(myParentVisualizer.sceneObject());
myUpdater->attachAttributeGroupWidget(myParentVisualizer.widget()->uniqueId());
myUpdater->setAttributeIndex(myIndex);
}
void DtArtPartSymbolDecoration::unvisualize()
{
if (myParentVisualizer.widget())
{
myParentVisualizer.widget()->setTypeNull(myIndex);
}
delete myUpdater;
myUpdater = 0;
}
//
// DtArtPartSymbolDecorationCreator method definitions.
//
DtArtPartSymbolDecorationCreator::DtArtPartSymbolDecorationCreator()
{
}
DtArtPartSymbolDecorationCreator::~DtArtPartSymbolDecorationCreator()
{
}
DtSymbolDecoration* DtArtPartSymbolDecorationCreator::create(
DtSymbolDecorationVisualizer& parentVisualizer )
{
unsigned int index = 0;
std::string name = displayName().toAscii();
if (parentVisualizer.labelIndexForDecorationByName(index, name))
{
// The visualizer already has an index for this decoration
return new DtArtPartSymbolDecoration( index, parentVisualizer );
}
else
{
// The visualizer does not have an index for this decoration, set one
// now by getting the next available label index - users could use a
// hard-coded value but ensure to set it with the parent visualizer
// using DtSymbolDecorationVisualizer::setLabelIndexForDecorationByName
// to prevent its index from getting overridden
index = parentVisualizer.nextLabelIndex();
parentVisualizer.setLabelIndexForDecorationByName(index, name);
return new DtArtPartSymbolDecoration( index, parentVisualizer );
}
}
DtSymbolDecorationCreator* DtArtPartSymbolDecorationCreator::clone()
{
return new DtArtPartSymbolDecorationCreator();
}
const DtUnicode& DtArtPartSymbolDecorationCreator::displayName() const
{
static DtUnicode name(DtUnicode::tr("Articulated Parts"));
return name;
}
const std::string& DtArtPartSymbolDecorationCreator::decorationClassName() const
{
static std::string name("DtArtPartSymbolDecoration");
return name;
}
const std::string& DtArtPartSymbolDecorationCreator::tag() const
{
static std::string tag("");
return tag;
}
}

DtArtPartDecorationUpdater.h

/******************************************************************************
** Copyright (c) 2023 MAK Technologies, Inc.
** All rights reserved.
******************************************************************************/
#pragma once
#include <vrvCore/DtDe.h>
#include <string>
namespace makVrv
{
class DtAttributeGroupWidget;
class DtSceneObject;
enum class DtModelSetId;
class DT_DLL_EXAMPLESYMBOLDECORATION DtArtPartDecorationUpdater : public DtTickable
{
public:
DtArtPartDecorationUpdater(DtDe& de, DtUniqueID id);
virtual ~DtArtPartDecorationUpdater();
virtual bool needsUpdate(DtTime tNow);
virtual void update(double simTime);
virtual void setSceneObject(DtSceneObject* sceneObject);
virtual void attachAttributeGroupWidget(DtUniqueID id);
virtual void setAttributeIndex(unsigned int index);
protected:
DtSceneObject* findSceneObjectFor(
const DtElementID& elementId, DtModelSetId modelSet);
virtual void slot_sceneObjectToBeDeleted(const DtUniqueID& id);
protected:
DtDe& myDe;
DtSceneObject* mySceneObject;
DtUniqueID myUniqueId;
float myLastSymbolUpdateTime;
DtAttributeGroupWidget* myAttributeGroupWidget;
int myLabelIndex;
// When the class is destructed, the scene object may or may not
// exist. Using a separate connection variable makes it possible to
// 'disconnect' even if the scene object has been deleted.
vrvSignalsLib::connection mySceneObjectConnection;
};
}

DtArtPartDecorationUpdater.cxx

/******************************************************************************
** Copyright (c) 2024 MAK Technologies, Inc.
** All rights reserved.
******************************************************************************/
#include <matrix/vlDcm.h>
namespace makVrv
{
DtDe& de, DtUniqueID id)
: DtTickable(&de.renderer().tickableManager())
, myLastSymbolUpdateTime(0.0)
, myAttributeGroupWidget(0)
, myLabelIndex(-1)
, mySceneObject(0)
, myDe(de)
, myUniqueId(id)
{
}
DtArtPartDecorationUpdater::~DtArtPartDecorationUpdater()
{
myAttributeGroupWidget = 0;
try
{
myDe.renderer().tickableManager().removeTickableObject(this);
}
catch (DtCorruptedState* err)
{
myDe.log().out(DtDebugLog::Debug) <<
"DtTickableManager failed to remove DtSymbolDecorationUpdater : " <<
err->message() << std::endl;
}
mySceneObjectConnection.disconnect();
}
bool DtArtPartDecorationUpdater::needsUpdate(DtTime tNow)
{
if (!myAttributeGroupWidget // no widget to update
|| myLastSymbolUpdateTime > tNow - 1.0 // only update once/second for efficiency
|| myLabelIndex == -1)// no label in the widget has been assigned yet))
{
return false;
}
return true;
}
void DtArtPartDecorationUpdater::update(double simTime)
{
myLastSymbolUpdateTime = simTime;
// Get the 3D scene object the updater is attached to, and count the
// articulated parts in any articulated models that it owns
DtSceneObject* so = mySceneObject;
if (so)
{
// get 3d model set scene object for the same simulated element
so = findSceneObjectFor( so->elementID(), DtModelSetId::Models3D );
}
if (so)
{
// Get the articulated parts owned by the scene object associated
// with this decoration, and update the label.
DtArticulatedModel::PartList parts;
DtSceneObject::ModelList& models = so->models();
for (DtSceneObject::ModelList::iterator iter = models.begin();
iter != models.end(); ++iter)
{
DtArticulatedModel* artModel =
dynamic_cast<DtArticulatedModel*>(iter->model);
if (artModel)
{
artModel->getParts(parts); // appends to the end of parts
}
}
// build a string containing a list of the articulated part ids
unsigned int bufSize = 10 * parts.size() + 1;
char* bufStart = new char[bufSize];
char* buf = bufStart;
bufStart[0] = '\0'; // make sure the string is null terminated even when there are no parts
for (DtArticulatedModel::PartList::iterator iter = parts.begin();
iter != parts.end(); ++iter)
{
int partId = *iter;
int sizeLeft = bufSize - (buf - bufStart);
int written = DtSnprintf(buf, sizeLeft, "%i ", partId);
buf += written;
}
// update the label to list the articulated part ids
myAttributeGroupWidget->setValueTextValue(
myLabelIndex, DtUnicode::fromAscii(bufStart));
delete [] bufStart;
}
}
void DtArtPartDecorationUpdater::attachAttributeGroupWidget(DtUniqueID id)
{
myAttributeGroupWidget = 0;
DtAgentUpdateResolverInterface* agentUpdater =
myDe.agentManager().findUpdater(id);
if (agentUpdater)
{
myAttributeGroupWidget =
agentUpdater->castObjectTypeFromUpdater<DtAttributeGroupWidget>
("DtWidget");
}
}
void DtArtPartDecorationUpdater::setSceneObject(DtSceneObject* sceneObject)
{
mySceneObjectConnection.disconnect();
mySceneObject = sceneObject;
if (mySceneObject)
{
mySceneObjectConnection = mySceneObject->signal_sceneObjectToBeDeleted.connect(boost::bind(
&DtArtPartDecorationUpdater::slot_sceneObjectToBeDeleted, this, boost::placeholders::_1));
}
}
void DtArtPartDecorationUpdater::slot_sceneObjectToBeDeleted(const DtUniqueID& id)
{
if (mySceneObject && mySceneObject->uniqueId() == id)
{
setSceneObject(NULL);
}
}
void DtArtPartDecorationUpdater::setAttributeIndex(unsigned int index)
{
myLabelIndex = index;
}
DtSceneObject* DtArtPartDecorationUpdater::findSceneObjectFor(
const DtElementID& elementId, DtModelSetId modelSet)
{
DtElementData::SceneObjectIdList sceneObjectIds;
DtElementData& mapper = myDe.dataBank().elementData();
mapper.getSceneObjectIds(elementId, sceneObjectIds, modelSet);
if (sceneObjectIds.size() && sceneObjectIds[0])
{
// look up object given id
DtAgentUpdateResolverInterface* updater =
myDe.agentManager().findUpdater(sceneObjectIds[0]);
if(updater)
{
return updater->castObjectTypeFromUpdater<DtSceneObject>(
"DtSceneObject");
}
}
return 0;
}
}

[<< Examples] [Home] [Top of Page]


Document ID: Generated on Thu Oct 23 22:29:17 EDT 2025 from SVN revision 280951
Copyright © 2005-2024 MAK Technologies. All Rights Reserved (www.mak.com)