VR-Link C# API Documentation
 All Classes Files Functions Variables Enumerations Enumerator Macros Pages
4.6 - Working with Remote Entities

Table of Contents

A DtReflectedEntityList maintains the current state of entities that VR-Link learns about through updates received from other participants in an exercise.

For DIS, an application receives entity state PDUs sent by itself along with those sent by other applications. Therefore, in DIS the DtReflectedEntityList contains a representation of locally simulated entities as well.

Each entity in the reflected entity list is represented by an instance of DtReflectedEntity. The list provides member functions that let you look up a DtReflectedEntity local ID or global ID, or iterate through all of the entities in the list.

VR-Link provides both HLA and DIS versions of DtReflectedEntityList and DtReflectedEntity, which are defined in reflectedEntityListHLA.h, reflectedEntityHLA.h, reflectedEntityListDIS.h, and reflectedEntityDIS.h. Rather than include all of these header files, you can include reflectedEntityList.h and reflectedEntity.h, which include the appropriate versions based on whether or not you have included the DtHLA=1 definition in your compile line.

Although the two versions of these classes are defined separately, most of the public member function names and prototypes are shared by the two versions, so that you can make most of these calls regardless of which protocol you are using.

4.6.1 Creating Reflected Entity Lists

A DtReflectedEntityList is created on an exercise connection as follows:

DtExerciseConn exConn(...);
...
DtReflectedEntityList(&exConn);

Whenever the DtReflectedEntityList detects a new entity, it automatically creates a new DtReflectedEntity to represent it. Whenever it receives a state update, it updates the corresponding DtReflectedEntity to reflect the current state. These two events will occur automatically as long as DtExerciseConn::drainInput() is called periodically within your application. No further action is required before inspecting data on remote entities.

4.6.2 Iterating Through a DtReflectedEntityList

To iterate through a DtReflectedEntityList, you can use the first() and last() member functions, which return the first and last entities in the list. Use DtReflectedEntity's next() and prev() member functions to get the next and previous entity in the list. These two functions return NULL if you try to read past the end or beginning of the list. You can iterate through all entities in the list as follows:

DtReflectedEntityList rel(...);
...
for (DtReflectedEntity* ent = rel.first();
ent;
ent = ent->next())
{
...
}

DtReflectedEntity also has wrapNext() and wrapPrev() member functions that are similar to next() and prev(), but which loop back to the first or last entity in the list when you try to move past the end or beginning of the list.

You can obtain the total number of entities in the DtReflectedEntityList with the count() member function.

If you want to look up a DtReflectedEntity by its ID, use DtReflectedEntityList::lookup(). The lookup() member function can accept either of the following ID types:

For example:

DtGlobalObjectDesignator id = fireInteraction.targetId();
DtReflectedEntity* ent = reflectedEntityList.lookup(id);

or

#if DtHLA
DtObjectId id = 15;
#elif DtDIS
DtObjectId id = DtEntityIdentifier(1, 2, 3);
#endif
DtReflectedEntity* ent = reflectedEntityList.lookup(id);

For more information about the different ways of identifying objects, please see 4.7 - Identifying Objects.

4.6.3 Delayed Discovery of Reflected Objects

You can delay discovery of reflected objects until some user-defined condition is met. VR-Link will not add an object to a reflected object list or inform the application about it until the specified predicate evaluates to true.

While this functionality can be used in DIS as a means of filtering, it is especially useful in HLA applications when an object is discovered before attribute information arrives. For example, you can indicate that VR-Link should not add the object to the reflected object list until values for certain important attributes (for example, entity type) arrive. This relieves applications from the need to continuously check whether attribute values are valid yet.

To specify the discovery condition for a reflected object list, you must write a predicate function that returns a boolean value, and then register it with the list, using setDiscoveryCondition(). For example, if you want a reflected entity list to wait until the entity type is something other than the initial value (0,0,0,0,0,0,0), write a function that looks like the following:

bool criteria(DtReflectedObject* obj, void* usr)
{
DtReflectedEntity* ent = (DtReflectedEntity*) obj;
if (ent->esr()->entityType() == DtEntityType(0,0,0,0,0,0,0))
{
return false;
}
return true;
}

Then tell the reflected entity list about it like so:

DtReflectedEntityList rel(&exConn);
rel.setDiscoveryCondition(criteria, NULL);

The need to wait for the entity type is very common, so this behavior is already available in DtReflectedEntityList, but it is off by default. To turn it on, when you create your list, call:

rel.discoverOnlyWhenEntityTypeKnown(true);

Calling it again with false turns this behavior off again.

4.6.4 Inspecting an Entity's State

DtReflectedEntity uses a DtEntityStateRepository to store the current state of an entity. This is the same class used by DtEntityPublisher to store the state of a locally simulated entity. For more details, please see 4.5.2 Setting an Entity's State. Individual state data items are inspected through DtEntityStateRepository inspector functions, rather than directly through the DtReflectedEntity.

You can get a pointer to a DtReflectedEntity's DtEntityStateRepository using DtReflectedEntity::entityStateRep() (or DtReflectedEntity::esr()).

DtEntityStateRepository has inspector functions that allow you to look at various components of an entity's state. You can get time, space, and position information using:

You can examine components that affect the outward appearance of an entity with functions such as:

DtEntityStateRepository has inspector functions that return the last value that was passed to the respective mutator function. These functions make it easier to obtain non-dead-reckoned data from an DtEntityStateRepository that is, in general, doing dead-reckoning. The functions are:

For the complete list of functions, please see entityStateRepository.h. DtEntityStateRepository is derived from DtBaseEntityStateRepository, so inherited functions are in baseEntityStateRepository.h.

In HLA, when you create a DtReflectedEntityList, VR-Link requests an update of all attributes. However, because of the way HLA works, you cannot be absolutely certain that all the attributes have been updated at the point that you inspect them. For information about how to resolve this problem, please see the HLA-specific paragraphs in 4.6.7 Learning when Entities Join or Leave an Exercise.

DtEntityStateRepository has mutator functions, but these are usually used to set a locally simulated entity's state, rather than that of a reflected entity, which is set automatically from data in state updates received from the exercise.

Positions, velocities, and accelerations returned by DtEntityStateRepository are in geocentric coordinates, as specified in the DIS Standard and DIS-based FOMs. (For details, please see 4.5.3 Coordinates.) Orientation is available in one of the following forms:

For information about how to convert rotation matrices to Euler angles, and how to convert among the many different coordinate systems supported by VR-Link, please see 12.3 - Coordinate Conversions.

VR-Link also provides various View classes (described in 4.9 - Coordinate Views), which provide the ability to inspect data in a DtEntityStateRepository in other coordinate systems without explicitly performing coordinate conversions.

Some of the inspector functions return enumerations. Values for these enumerations are in disEnums.h. When a bool is returned, it will be either true or false.

The following example shows how to use DtEntityStateRepository's inspector functions to examine the current state of a reflected entity, within a function that prints part of the state of the first entity in the list:

void printStateOfFirstEnt(DtReflectedEntityList *rel)
{
// Grab a pointer to the first entity.
DtReflectedEntity *firstEnt = rel->first();
// Exit if the list is empty
if(!firstEnt)
{
return;
}
DtEntityStateRepository *esr = firstEnt->entityStateRep();
// Print out Entity information.
cout
<< "ID: " << firstEnt->globalId().string() << '\n'
<< "Loc: " << esr->location().string() << '\n'
<< "Vel: " << esr->velocity().string() << '\n'
<< "Accel: " << esr->acceleration().string() << '\n'
<< "Orient: " << esr->orientation().string() << '\n'
<< "AngVel: " << esr->rotationalVelocity().string() << endl;
if (esr->flamesPresent())
{
cout << "Flaming!\n";
}
// The DtDamageState enumeration is in disEnums.h
if (esr->damageState() == DtDamageDestroyed)
{
cout << "Destroyed!\n";
}
}

DtEntityStateRepository::printData() works in a similar way, and you can use it to print the current contents of a DtEntityStateRepository in human readable form.

4.6.5 Dead-Reckoning

By default, the position, velocity, and orientation for a DtReflectedEntity that are returned by the member functions of its DtEntityStateRepository (location(), velocity(), orientation(), and bodyToGeoc()), are dead-reckoned values. That is, they are not necessarily the values last received via state updates from the exercise. They are extrapolated forward to the current value of VR-Link simulation time from acceleration, velocity, and angular velocity based on the entity's current dead-reckoning algorithm.

The current value of VR-Link simulation time is the last value passed to setSimTime(), which should be called once per frame by an application. In this way, all entities are dead-reckoned to the same time within that frame, regardless of the order in which the locations are inspected. Time of validity of the inputs to the dead-reckoning equation is the value of VR-Link simulation time when the data was received from the exercise.

Notes

For non-dead-reckoned values, use the values returned by DtEntityStateRepository's member functions lastSetLocation(), lastSetVelocity(), and so on.

4.6.5.1 Dead-Reckoning Details

A DtEntityStateRepository uses a DtDeadReckoner to perform dead-reckoning calculations, or other types of extrapolation of position and orientation. When DtEntity-StateRepository's mutator functions are used to set any position-related values, those values are passed to the DtDeadReckoner's mutators. Then, when DtEntityStateRepository's inspectors are used to ask for current values, the DtEntityStateRepository obtains extrapolated values from the dead-reckoner using its inspectors.

VR-Link's dead reckoning logic is in the virtual functions DtDeadReckoner::deadReckonPosition() and DtDeadReckoner::deadReckonOrientation(). These functions calculate the position and orientation based on the entity's current rates of movement and the time that has passed since the last update. To change the way dead-reckoning is handled by a DtEntityStateRepository:

  1. Create a subclass of DtDeadReckoner.
  2. Override DtDeadReckoner::deadReckonPosition() and DtDeadReckoner::deadReckonOrientation().
  3. Tell a DtEntityStateRepository to use an instance of your subclass through its useDeadReckoner() function (which is inherited from DtBaseEntityStateRepository).

Similarly, if you want a DtEntityStateRepository not to perform dead-reckoning at all, pass NULL to setApproximator(). In fact, this is what a DtEntityPublisher does with its DtEntityStateRepository, since you typically do not want dead-reckoned values when inspecting the DtEntityStateRepository of a locally simulated entity.

To restore the DtEntityStateRepository's original default dead-reckoner, call useDeadReckoner() with no arguments.

4.6.5.2 Dead-Reckoning Algorithms

The enums for the dead-reckoning algorithms are as follows:

For details on the individual algorithms, consult the DIS standard (IEEE Std 1278.1-1995, Annex B).

4.6.6 Using Smoothing

VR-Link can smooth out the jumps in entity position that would otherwise occur when new HLA or DIS state data arrives. The DtSmoother class (defined in smoother.h) implements this functionality. DtSmoother is derived from DtDeadReckoner, so a DtSmoother can be used by a reflected entity's entity state repository as its DtDeadReckoner. If a DtEntityStateRepository is using a DtSmoother, the values returned by location(), velocity(), orientation() and bodyToGeoc() are smoothed values.

The DtReflectedEntityList constructors have an optional boolean argument indicating whether its reflected entities should use smoothers as their dead-reckoners or not (default is no). Alternatively, you can instruct an individual reflected entity's DtEntity-StateRepository to use a DtSmoother using DtEntityStateRepository's useSmoother() member function.

To set the global default time over which smoothing takes place, use DtSmoother's static function setDfltSmoothPeriod(). To override the default in individual DtSmoothers, use setSmoothPeriod().

Note
Smoothing applies to entities and aggregates, but not to other objects.

4.6.7 Learning when Entities Join or Leave an Exercise

Often, an application will want to be notified when an entity joins or leaves the exercise. This is achieved by registering entity-addition and entity-removal callbacks with a DtReflectedEntityList. Entity-addition callbacks are called by VR-Link just after an entity is added to the DtReflectedEntityList. Entity-removal callbacks are called just before an entity is removed from the DtReflectedEntityList. The callbacks are made within DtExerciseConn::drainInput() when your application receives news from the exercise that an entity has joined or left.

Entity-addition and entity-removal callbacks must have the following function signature:

void func(DtReflectedEntity* ent, void* userData);

The callbacks are registered with a DtReflectedEntityList using its addEntityAdditionCallback() and addEntityRemovalCallback() member functions. They can be unregistered with removeEntityAdditionCallback() and removeEntityRemovalCallback().

In the following example, the application prints HELLO and GOODBYE when an entity comes or goes, along with the ID of the entity:

void printHello(DtReflectedEntity* ent, void* userData)
{
assert(ent);
cout << "HELLO " << ent->id().string() << endl;
}
void printGoodbye(DtReflectedEntity *ent, void *userData)
{
assert(ent);
cout << "GOODBYE " << ent->id().string() << endl;
}
int main()
{
...
rel->addEntityAdditionCallback(printHello, 0);
rel->addEntityRemovalCallback(printGoodbye, 0);
...
}

An alternate method of receiving notification that an entity has joined or left the exercise is by subclassing DtReflectedEntityList, and overriding the virtual functions entityAdded() and removeAndDelete(). The entityAdded() member function is called just after an entity is added to a DtReflectedEntityList. The removeAndDelete() member function is called to remove an entity from the DtReflectedEntityList. If you override removeAndDelete(), be sure to call down to the base version of this function from within your implementation, since this is what actually accomplishes the removal of the entity from the list.

An entity is added to the DtReflectedEntityList as soon as VR-Link receives a discoverObject() service call from the RTI. This occurs before the first attribute update is sent by the RTI. Therefore, the DtReflectedEntity's DtEntityStateRepository will not contain any data at the time that the entity is passed to your entity addition callback or the overridden version of entityAdded(). Only the entity's ID will have been set at this point. Therefore, within your callback or entityAdded() you should only be doing things like saving a pointer to the entity, rather than trying to inspect any data.

Typically, the first update immediately follows the discoverObject() invocation for a new entity. Therefore, by the time drainInput() returns, most data will be valid for your application to inspect. If you want to be notified immediately after the first update arrives, please see the next section, 4.6.8 Notifying an Application when State Updates Arrive.

Another consideration is that these callbacks as well as entityAdded() and removeAndDelete() are called from within RTI callbacks. RTI rules prohibit making RTI calls from within an RTI callback, so do not make any RTI calls (or call any functions that make RTI calls) from within entityAdded() or removeAndDelete().

Entity addition callbacks and the virtual function entityAdded() are called after the first entity state PDU for an entity has been processed. Therefore, the state of the entity is available within entityAdded(), through the entity's DtEntityStateRepository. However in the interest of writing protocol-independent code (to take into account the HLA issues discussed in previous paragraphs), you might not want to inspect the data at this point in the code for DIS applications.

4.6.8 Notifying an Application when State Updates Arrive

Some applications might want to be notified when new state data is received from the application that is simulating a particular entity. You can do this by registering a post-update callback function with a DtReflectedEntity. These functions must have the following signature:

void func(DtReflectedEntity* ent, void* userData);

They are registered using DtReflectedEntity's addPostUpdateCallback() member functions. To unregister your callback function, use the removePostUpdateCallback() member function.

Post-update callbacks are called by VR-Link from within DtExerciseConn::drainInput() immediately after a state update message has been decoded into the reflected entity's DtEntityStateRepository. Therefore, when your callback is called, the DtEntityStateRepository will already reflect the new values contained in the update.

To illustrate use of this functionality, suppose you have an application that needs to create different types of graphic icons to represent different types of entities. You want to be notified when a new entity arrives, so that you can create a new icon, but entity type information is not available to an entity-addition callback in HLA. To check for the entity type, register a post-update callback from within your entity-addition callback. When the post-update callback is invoked, you can check to see whether you have received a value for the entity type (since in HLA, that data might not arrive in the first update):

void myEntityAdditionCb(DtReflectedEntity* ent, void* usr)
{
assert(ent);
// A new entity has arrived, but its ESR is empty. Ask
// to be notified when an update has been processed.
ent->addPostUpdateCb(myPostUpdateCb, usr);
}
void myPostUpdateCb(DtReflectedEntity* ent, void* usr)
{
assert(ent);
// A state update for the entity has just been processed,
// but there is no guarantee that the entity type was included.
DtEntityStateRepository* esr = ent->esr();
if (esr->entityType() != DtEntityType(0,0,0,0,0,0,0))
{
// We have received entity type info, and can now use it
// to create an appropriate icon.
addIcon(esr->entityType());
// We probably no longer need the post-update callback.
ent->removePostUpdateCb(myPostUpdateCb, usr);
}
}
int main()
{
...
// Register the entity addition callback with a reflected
// entity list.
rel->addEntityAdditionCallback(myEntityAdditionCb, someObj);
...
}

Because the receipt of state data works differently in DIS and HLA, and because we wanted the post-drain callback mechanism to work in a protocol-independent manner, you do not have access to the update message itself from within the post-drain callback.

Note
If you want to actually intercept an incoming state update message in either HLA or DIS, please see the appropriate protocol-specific sections.

4.6.9 Timing Out Entities

DtReflectedEntityList can time entities out, removing them from the list if an update has not been received within some period of time.

This capability is on by default in DIS, where the rules state that an entity state PDU (heartbeat) must be sent periodically (usually once every 5 seconds), even if no data has changed. Therefore, if we have not received a heartbeat in a reasonable amount of time (usually 12 seconds), we can safely assume that the entity has left the exercise.

Note
If you are using DIS 7, the heartbeat can be set to different lengths based on entity type, class type, or whether or not entities are moving. Therefore you may want to approach timing out entities similarly to HLA.

In HLA, there is no heartbeat rule. It is perfectly valid for an entity to go several minutes or more without updating attributes, as long as nothing has changed. For this reason, timeouts are off by default. When timeout processing is off, a DtReflectedEntity is not removed from the DtReflectedEntityList until we are notified by the RTI that the entity has left the exercise.

You can control timeout processing for individual reflected entity lists. For both DIS and HLA, you can turn timeouts on and off using DtReflectedEntityList::setTimeoutProcessing(). (This function is inherited from the DtReflectedObjectList base class.) The argument is a bool, either true or false.

If timeout processing is on, the DtReflectedEntityList checks to see if any entities need to be timed out each time DtExerciseConn::drainInput() is called. The timeout interval (the amount of time that can elapse since the last update before an entity is timed out) defaults to 12.0 seconds, but can be configured with DtReflectedObjectList::setTimeoutInterval().

Note
The timeout is calculated using real time, not simulated time.

4.6.10 Subclassing DtReflectedEntity

Some visually-oriented applications might want to associate additional data or functionality with a DtReflectedEntity. One way to achieve this is by subclassing DtReflectedEntity. For example, you could use a subclass of DtReflectedEntity to associate graphics data with the entity.

If you subclass DtReflectedEntity (rather than associating data through composition, for example), you need to subclass DtReflectedEntityList as well. The reason for this is simple – DtReflectedEntities are created by the DtReflectedEntityList, and the DtReflectedEntityList only knows how to create DtReflectedEntities. You need to create a derived DtReflectedEntityList that knows how to create your derived DtReflectedEntities.

A DtReflectedEntityList uses the virtual function newReflectedEntity() to create DtReflectedEntities. It is this function that you must override with a definition that returns a new instance of your derived DtReflectedEntity.

The constructors for DIS and HLA versions of DtReflectedEntity take different arguments. Therefore, the two versions of DtReflectedEntityList::newReflectedEntity(), (which basically just passes its arguments to the DtReflectedEntity constructor) take different arguments as well.

The following example shows how to subclass the two classes:

class myReflectedEntity : public DtReflectedEntity
{
public:
// Constructor
#if DtHLA
myReflectedEntity(DtHlaObject* obj, DtExerciseConn* conn) : DtReflectedEntity(obj, conn)
#elif DtDIS
myReflectedEntity(DtExerciseConn* conn,
const DtEntityIdentifier& id, const DtEntityType& type) :
DtReflectedEntity(conn, id, type)
#endif
{
// The two versions may be able to share a body
...
}
// Specifics of myReflectedEntity
...
};
class myREL : public DtReflectedEntityList
{
public:
// Constructor (same for both DIS and HLA)
myREL(DtExerciseConn* exConn) : DtReflectedEntityList(exConn) {}
#if DtHLA
virtual DtReflectedEntity* newReflectedEntity(DtHlaObject* obj) const
{
return new myReflectedEntity(obj, exerciseConn());
}
#elif DtDIS
virtual DtReflectedEntity* newReflectedEntity(const DtEntityIdentifier& id, const DtEntityType& type) const
{
return new myReflectedEntity(exerciseConn(), id, type);
}
#endif
};

[<< Working with Locally Simulated Entities] [Home] [Top of Page] [Identifying Objects >>]


Document ID: Generated on Wed Jan 7 13:31:29 EST 2015 from SVN revision 149162
Copyright © 2005-2014 VT MÄK. All Rights Reserved (www.mak.com)