vsTASKER 7 Tutorial
×
Menu
Index

RPR-FOM

  
In this demo, we will use the RPR FOM to connect to a sample federate provided by Mak Technology (F18HLA1516.ex) with VR-Link.
We will subscribe to an Aircraft entity (F16) and its Emitter Beam.
We will also publish our own Aircraft.
Fire/Detonation will also be managed.
 
First, let's open the FOM
 
In the OMT Part, we can see that the FOM has been converted in local editable objects:
When the hierarchy is complex, it could be nice to sort things. As there is no tool to automate that, use the Diagram mode to maximum size then move any object with shift key depressed to also move all its children.
You can also right click the mouse and use the context menu Gather Below to put all the children recursively below each selected parent in order to visually and manually rearrange the whole OMT hierarchy.
 
We can change any of them before converting to local C++ data structures.
In the Complex panel, let's Edit the RTIObjectIdStruct:
 
Let's Edit the ID Field by changing the Type to char:
 
and the Cardinality to 100 (or more)
 
 
In the SOM panel, let's just add the Objects and Interactions we want to Subscribe and Publish.
We will Insert the following Objects:
 
Publisher: Aircraft
Subscriber: BaseEntity, EmitterSystem and EmitterBeam.
 
Aircraft Object belongs to BaseEntity. So, we need to insert it from the Publisher area.
Let's now open BaseEntity FedItem, let's rename it to pAircraft (p for Publish) and let's select the Aircraft Object by merging it with its parent nodes.
 
To merge PhysicalEntity with BaseEntity, we must remove all Objects in between, here, BaseEntityOther, AggregateEntity and EnvironmentalEntity.
If we need to subscribe to them, we will create another FedItem for each of them.
Select each of them from the list then press (if many, use multiple selections, using the shift or ctrl key) until you get:
 
Now, select PhysicalEntity Object and merge it with parent (BaseEntity) using
You will get the merged new Object:
 
At the bottom of the list, select Object Platform then use
 
Finally, let's do the same for Aircraft Object at the bottom of the list.
You should get the following:
 
We will now select the BaseEntity.PhysicalEntity.Platform.Aircraft Object by opening it and just check the Activate checkbox:
 
Now, the green lamp shows that the Item is selected for publishing:
 
In the Subscriber side, let's just add BaseEntity Object, then EmitterBeam and the EmbeddedSystem.
For the EmbeddedSystem, the EmitterSystem Object must be merged because we need some of its attributes:
 
You do it the same way as with Aircraft.
Other Objects can remain in the list as long as they are not Activated. They will be ignored by the code generator.
Do not forget to activate the base Object of each FedItem.
 
Finally, add the two Interactions: WeaponFire and MunitionDetonation.
Activate them also.
 
You should then have the following:
 
 
In order to simplify the data manipulation (from and to the RTI), we will use the OMT -> DataModel converter.
Let's go to the OMT panel, then right click to select the OMT ConvTool...:
 
Select "Optimize Selection" to filter only the Objects/Interactions (and inherited ones) as well as used other data structures to be converted as DataModels.
There is no need to translate the full OMT database as only some Objects will be needed and used.
If later, some must be added, they just can be manually added.
 
Now press Generate.
 
Back in the Hla Model diagram view, you should see something like that:
 
 
 
Back again to the SOM view, time to fill the sBaseEntity FedItem.
First, we need to associate a DataModel. We will use the hlaBaseEntity one:
 
Because the Object is associated with an Entity, we need to check the checkbox. This will insure proper code generation related to Entity creation itself.
If not checked, user will need to handle manually Entity new, delete and handle pairing.
 
Now, we will edit the Spatial Attribute and use the Wizard button:
 
The code means that local variable pointer will change the DataModel associated with the Entity (if any), otherwise, the rti_data structure will be used instead.
Both rti_data and local are automatically generated and of type of the DataModel used (here: hlaBaseEntity).
 
To know the structure definition of Spatial, we can either use the OMT definition or the DataModel definitions:
 
Then:
 
We need to convert to Big Endian all coming data from the RTI, fields by fields.
Let's do the following into the Spatial Attribute:
 
if (local) {
  local->Spatial <-(SpatialStruct);
 
  SpatialRVStruct& data = &local->Spatial.DeadReckoningAlgorithm_A_Alternatives.SpatialRVW;
 
  ENDIAN_SWAP_DOUBLE64(data.WorldLocation.X);
  ENDIAN_SWAP_DOUBLE64(data.WorldLocation.Y);
  ENDIAN_SWAP_DOUBLE64(data.WorldLocation.Z);
  ENDIAN_SWAP_FLOAT32(data.Orientation.Psi);
  ENDIAN_SWAP_FLOAT32(data.Orientation.Theta);
  ENDIAN_SWAP_FLOAT32(data.Orientation.Phi);
  ENDIAN_SWAP_FLOAT32(data.VelocityVector.XVelocity);
  ENDIAN_SWAP_FLOAT32(data.VelocityVector.YVelocity);
  ENDIAN_SWAP_FLOAT32(data.VelocityVector.ZVelocity);
  ENDIAN_SWAP_FLOAT32(data.AngularVelocity.XAngularVelocity);
  ENDIAN_SWAP_FLOAT32(data.AngularVelocity.YAngularVelocity);
  ENDIAN_SWAP_FLOAT32(data.AngularVelocity.ZAngularVelocity);
}
 
Let's do similar for EntityIdentifier Attribute:
 
if (local) {
  local->EntityIdentifier <-(EntityIdentifierStruct);
 
  ENDIAN_SWAP_SHORT16(local->EntityIdentifier.FederateIdentifier.SiteID);
  ENDIAN_SWAP_SHORT16(local->EntityIdentifier.FederateIdentifier.ApplicationID);
  ENDIAN_SWAP_SHORT16(local->EntityIdentifier.EntityNumber);
}
 
... and EntityType Attribute:
 
if (local) {
  local->EntityType <-(EntityTypeStruct);
 
  ENDIAN_SWAP_SHORT16(local->EntityType.CountryCode);
}
 
Now, in the BaseEntity code, you can write the following, to process the incoming data and update the corresponding vsTASKER data structures and Models:
 
SpatialRVStruct& data = &local->Spatial.DeadReckoningAlgorithm_A_Alternatives.SpatialRVW;
 
WCoord pos(WC_ECEF, data.WorldLocation.X, data.WorldLocation.Y, data.WorldLocation.Z);
pos.convertECEFtoXYZ();
entity->pos = pos;
 
pos.convertXYZtoLLA();
pos.setOrigin(pos.lat, pos.lon);
Vec3d& hpr = pos.eulerToHpr(data.Orientation.Psi, data.Orientation.Theta, data.Orientation.Phi);
entity->getDyn()->setHeading(-hpr[0]);
 
Vec3d& spd = pos.remoteToLocal(data.VelocityVector.XVelocity, data.VelocityVector.YVelocity, data.VelocityVector.ZVelocity);
float s = sqrt(SQR(spd[0])+SQR(spd[1])+SQR(spd[2]));
entity->getDyn()->setSpeed(s);
 
data.AngularVelocity.XAngularVelocity = 0;
data.AngularVelocity.YAngularVelocity = 0;
data.AngularVelocity.ZAngularVelocity = 0;
 
PtfDisType& type = *(PtfDisType*) entity->findComponent("PtfDisType");  // this component belongs to MilLib
if (type) {
  type.kind        = local->EntityType.EntityKind;
  type.domain      = local->EntityType.Domain;
  type.country     = local->EntityType.CountryCode;
  type.category    = local->EntityType.Category;
  type.subcategory = local->EntityType.Subcategory;
  type.specific    = local->EntityType.Specific;
  type.extra       = local->EntityType.Extra;
}
else
printf("Warning: %s does not have %s DataModel!\n", entity->getName(), "PtfDisType");
 
PtfStatus* status = (PtfStatus*) entity->findDataModel("PtfStatus");
if (status) {
   type->update(status);
   status->updateSymbol(SM_2525B);
}
else printf("Warning: %s does not have %s DataModel!\n", entity->getName(), "PtfStatus");
 
 
Let's open the pAircraft Object to edit the Spatial Attribute. We first use the Wizard button to get the code and then, we add the Endian swap macro after the receiving from the RTI:
 
 
We do the same for the EntityIdentifier Attribute:
 
if (local) {
  ENDIAN_SWAP_SHORT16(local->EntityIdentifier.FederateIdentifier.SiteID);
  ENDIAN_SWAP_SHORT16(local->EntityIdentifier.FederateIdentifier.ApplicationID);
  ENDIAN_SWAP_SHORT16(local->EntityIdentifier.EntityNumber);
 
  "EntityIdentifier" <- (EntityIdentifierStruct) &local->EntityIdentifier;
}
 
... and the EntityType one:
 
if (local) {
  ENDIAN_SWAP_SHORT16(local->EntityType.CountryCode);
 
  "EntityType" <- (EntityTypeStruct) &local->EntityType;
}
 
Then, at the Object code level, we need to set the data first.
A Publisher FedItem is called for all Entities (or Handles) it holds.
When the FedItem is Entity Based (), it processes used_by entities one by one and retrieve the associated Handle. When the FedItem is Handle based (), it processes Handles one by one and retrieve the associated Entity (if any).
 
 if (ent_idx >= getEntities().count()) { ent_idx = 0; return LEAVE; }
 else {
    entity = getEntity(ent_idx++);
    local = entity? (hlaAircraft*)entity->findDataModel("hlaAircraft"): NULL;
    // User can add his code here...
 
    if (local) {
       SpatialRVStruct& data = local->Spatial.DeadReckoningAlgorithm_A_Alternatives.SpatialRVW;
 
       double h = entity->getDyn()->getHeading();
       double p = entity->getDyn()->getElev();
       double r = entity->getDyn()->getRoll();
 
       // position
       WCoord pos(WC_XYZ, entity->pos.x, entity->pos.y, entity->pos.z);
       pos.convertXYZtoECEF();
       data.WorldLocation.X = pos.x;
       data.WorldLocation.Y = pos.y;
       data.WorldLocation.Z = pos.z;
 
       // orientation
       pos.convertECEFtoLLA();
       pos.setOrigin(pos.lat, pos.lon);
       Vec3d& vec = pos.hprToEuler(-h,p,r);
       data.Orientation.Psi   = vec[0];
       data.Orientation.Theta = vec[1];
       data.Orientation.Phi   = vec[2];
 
       // speed
       double s = entity->getDyn()->getSpeed();
       Vec3d& spd = pos.localToRemote(s*sin(h)*cos(p), s*cos(h)*cos(p), s*sin(p));
       data.VelocityVector.XVelocity = spd[0];
       data.VelocityVector.YVelocity = spd[1];
       data.VelocityVector.ZVelocity = spd[2];
 
       // identifier
       local->EntityIdentifier.FederateIdentifier.SiteID = 1;
       local->EntityIdentifier.FederateIdentifier.ApplicationID = 1;
       local->EntityIdentifier.EntityNumber = entity->getId();
 
       // DIS type
       PtfDisType& type = *(PtfDisType*) entity->findComponent("PtfDisType");
       if (type) {
          local->EntityType.EntityKind  = type.kind;
          local->EntityType.Domain      = type.domain;
          local->EntityType.CountryCode = type.country;
          local->EntityType.Category    = type.category;
          local->EntityType.Subcategory = type.subcategory;
          local->EntityType.Specific    = type.specific;
          local->EntityType.Extra       = type.extra;
       }
       else printf("Warning: %s does not have %s DataModel!\n", entity->getName(), "PtfDisType");
    }
    return CONTINUE;
 }
 return PROCEED;
 
 
When 'e' key is depressed in the F18.exe window, an EmitterSystem Object is created and an EmitterBeam Object is also attached to it.
Both Objects are related to an Entity but one EmitterSystem can hold several EmitterBeam while one Entity normally got one EmitterSystem.
 
The EmitterSystem FedItem is not Entity based () so, Handles must be paired manually with any Entity and extra void* data that might be needed.
This is handy because next time the Handle will reflect updates, Entity and user data pointer will be returned back.
 
In Subscriber FedItems, Attributes/Parameters are received first, then the Object/Interaction code is called.
In our case, the Discover panel is of no use because we do not know yet what to do with the Handle (we do not create an Entity).
 
So, it will be in the EmitterSystem Object code that we will pair the Handle with the referred Entity:
 
 // No entity paired yet with the Handle.
 if (!entity) {
     // We must look for an external entity whose name is given by the ID
     entity = S:findEntity(rti_data.HostObjectIdentifier.ID);
     // if one is found...
     if (entity) {
        add(entity);   // we add it to the used_by list of the FedItem, for tracability mainly
        store(entity); // we associate it with the handle. Next time, we will not come here
     }
  }
 
When Entity is found, we can then retrieve the Entity DataModel and set the values coming from the RTI.
 
  if (entity) local = (hlaEmitterSystem*) entity->findComponent("hlaEmitterSystem");
  if (local) {
     local->EntityIdentifier = rti_data.EntityIdentifier;
     local->HostObjectIdentifier = rti_data.HostObjectIdentifier;
     local->EmitterIndex = rti_data.EmitterIndex;
  }
 
 
For this Object, we need to get the following Attributes:
 
EmitterSystemIdentifier: name of the RTI Object. With it, we will retrieve the associated Handle and then, the Entity:
 
if (local) {
  local->EmitterSystemIdentifier <-(RTIObjectIdStruct);
}
else {
  rti_data.EmitterSystemIdentifier <-;
}
 
EffectiveRadiatedPower: just for the length of the beam:
 
if (local) {
  local->EffectiveRadiatedPower <-(HLAfloat32BEdBmperfectalways);
  ENDIAN_SWAP_FLOAT32(local->EffectiveRadiatedPower);
}
else {
  rti_data.EffectiveRadiatedPower <-;
  ENDIAN_SWAP_FLOAT32(rti_data.EffectiveRadiatedPower);
}
 
BeamAzimuthSweep: for the beam wideness value:
 
if (local) {
  local->BeamAzimuthSweep <-(HLAfloat32BEradiansperfectalways);
  ENDIAN_SWAP_FLOAT32(local->BeamAzimuthSweep);
}
else {
  rti_data.BeamAzimuthSweep <-;
  ENDIAN_SWAP_FLOAT32(rti_data.BeamAzimuthSweep);
}
 
BeamAzimuthCenter: for the beam current azimuth:
 
 if (local) {
  local->BeamAzimuthCenter <-(HLAfloat32BEradiansperfectalways);
  ENDIAN_SWAP_FLOAT32(local->BeamAzimuthCenter);
}
else {
  rti_data.BeamAzimuthCenter <-;
  ENDIAN_SWAP_FLOAT32(rti_data.BeamAzimuthCenter);
}
 
Once that done, we just need now to set the DataModel according to the coming values:
 
vt_amb is a pointer to the vtAmbassador class.
The getDesc() function with an Object name returns the ObjInstDesc that contains the RTI Handle for this named Object.
The function retreiveData() with an Handle returns the tuple (Handle, Entity, UserData).
With these two functions, we are able to associate the local data pointer with the Handle and make it point to the hlaEmitterBeam component.
 
Later coming updates will automatically set the data into the DataModel object of the Entity, pointed to by local.
The EmitterBeam main code will then just draw the beam on the map (local->draw())
 
The same way, we must not forget to clean the DataModel drawing by calling clean() in the Remove panel of the EmitterBeam Object :
 
 
Once the DataModel have been constructed from OMT definitions, they can be augmented with user data and methods.
To prevent later overwriting of these DataModels, it is a good idea to lock them to secure the changes.
 
Here, we want the hlaEmitterBeam DataModel to be able to draw  something according to its values.
We will use a Gph_Item and three  functions: init(), draw() and clean():
 
The Methods panel will get the declarations of the three functions:
 
// ******************************************************
void Dml::init()
{
   gfx_dome.id = -1;
}
 
// ******************************************************
void Dml::draw()
{
  if (gfx_dome.id <0) {  // not displayed yet
     // Dome
     vt_rtc->gui_map.drawDome(entity->getName(),   // base
                              BeamAzimuthCenter-BeamAzimuthSweep,
                              BeamAzimuthCenter+BeamAzimuthSweep,
                              0, 0,
                              EffectiveRadiatedPower*10,
                              entity->db->getColor());
     gfx_dome = vt_rtc->gui_map.getLastItem();
  }
  else {
     if (gfx_dome.id >= 0) {
      gfx_dome.color = entity->db->getColor();
      gfx_dome.dome.min_azim = BeamAzimuthCenter-BeamAzimuthSweep;
      gfx_dome.dome.max_azim = BeamAzimuthCenter+BeamAzimuthSweep;
      gfx_dome.dome.min_elev = DEG2RAD(0);
      gfx_dome.dome.max_elev = DEG2RAD(0);
      gfx_dome.dome.radius   = EffectiveRadiatedPower*10;
      vt_rtc->gui_map.updateGraphic(gfx_dome);
     }
   }
}
 
// ******************************************************
void Dml::clean()
{
   if (gfx_dome.id > -1) {
      vt_rtc->gui_map.removeGraphic(gfx_dome.id);
      gfx_dome.id = -1;
   }
}
 
 
By pressing the 'b' button in the F18.exe window, the closest entity from the F18 will receive a  WeaponFire interaction and 3 seconds later a MunitionDetonation.
We will subscribe to these two Interactions.
 
WeaponFire: We want to draw a blue circle around the shooter then a line between the shooter and the target. For that, we need to get the following three parameters: FiringLocation, FiringObjectIdentifier and TargetObjectIdentifier
 
FiringLocation Attribute code to draw the blue circle:
 
rti_data.FiringLocation <-(WorldLocationStruct);
 
ENDIAN_SWAP_DOUBLE64(rti_data.FiringLocation.X);
ENDIAN_SWAP_DOUBLE64(rti_data.FiringLocation.Y);
ENDIAN_SWAP_DOUBLE64(rti_data.FiringLocation.Z);
 
WCoord pos(WC_ECEF, rti_data.FiringLocation.X, rti_data.FiringLocation.Y, rti_data.FiringLocation.Z);
pos.convertECEFtoXYZ();
R:gui_map.drawCircle(pos, 100, clBlue, 3);
 
Then, from the WeaponFire Object code, we draw the line:
 
Vt_Entity* from = S:findEntity(rti_data.FiringObjectIdentifier.ID);
Vt_Entity* to = S:findEntity(rti_data.TargetObjectIdentifier.ID);
 
if (from && to) R:gui_map.drawLine(from->pos, to->pos, clRed, 2);
 
MunitionDetonation: We will use the TargetObjectIdentifier to kill the Entity and use the DetonationLocation to draw a red circle of 3 seconds:
 
rti_data.DetonationLocation <-(WorldLocationStruct);
 
ENDIAN_SWAP_DOUBLE64(rti_data.DetonationLocation.X);
ENDIAN_SWAP_DOUBLE64(rti_data.DetonationLocation.Y);
ENDIAN_SWAP_DOUBLE64(rti_data.DetonationLocation.Z);
 
WCoord pos(WC_ECEF, rti_data.DetonationLocation.X, rti_data.DetonationLocation.Y, rti_data.DetonationLocation.Z);
pos.convertECEFtoXYZ();
 
R:gui_map.drawCircle(pos, 100, clRed, 3);
 
MunitionDetonation Object code:
 
Entity* target = (Entity*) S:findEntity(rti_data.TargetObjectIdentifier.ID);
if (target) {
   target->status->setDamage(_Damaged);
   target->dyn->startCrash();
}
 
 
This demo is available in HLA/Mak/1516/vrlink20017-F18-1516 and Hla/Mak/1516/vrlink-F18-1516.
See the Scenario description to know how to launch the VR-Link demo part.
If you do not have VR-Link, you can still use the /HLA/Mak/1516/multisensors_master1516 and slave with two vsTASKER running (available also on Pitch/1516)
Rely on the Scenario explanations to see how to manipulate the demos.