Logo Search packages:      
Sourcecode: acm version File versions  Download package

dis_if.c

/*
 *  Copyright (C) 1995  Mats Lofkvist  CelsiusTech Electronics AB
 *
 *  With additions by Riley Rainey, 1995-1998
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 dated June, 1991.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program;  if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
 */

static char rcsid[] = "$Id: dis_if.c,v 1.2 1998/05/12 03:59:37 riley Exp $";

#include <stdio.h>
#include <math.h>
#ifndef WIN32
#include <sys/time.h>
#endif
#include <assert.h>

#include <pm.h>
#include <dis/dis.h>
#include <dis.h>

#ifdef WIN32
extern DISxApplicationInfo *WinACMDISxInitializeApplication(int, int, int);
extern int WinACMDISReadPDU (DISxApplicationInfo *, dis_pdu*);
extern int WinACMDISWritePDU (DISxApplicationInfo *, dis_pdu*);
#endif

int acknowledgePDU (dis_acknowledge_pdu *pdu);
int transferControlPDU (dis_transfer_control_pdu *pdu);
int setDataPDU (dis_set_data_pdu *pdu);
int startPDU (dis_start_pdu *);
int stopPDU (dis_stop_pdu *);
static int dis_initializeEMInfo ( Entity_t *e );

#define DEFAULT_LOCATION_THRESHOLD      1.0
#define DEFAULT_ORIENTATION_THRESHOLD   (3.0 * M_PI / 180.0)

#define SEND_TIMEOUT_SECONDS  4.8
#define RECV_TIMEOUT_SECONDS  12.0

static dis_EntityEnterCb entityEnterCb = NULL;
static dis_DetonationCb detonationCb = NULL;

static double locationThreshold = DEFAULT_LOCATION_THRESHOLD;
static double orientationThreshold = DEFAULT_ORIENTATION_THRESHOLD;

static int exercise;
static int site;
static int application;

static  OutstandingRequestInfo_t *request_chain_head = 0;
static  OutstandingRequestInfo_t *request_chain_tail = 0;

static int network_enabled = 1;

TransferControlRequestCallback transferControlRequestCallback = 0;

void
dis_setTransferControlRequestCallback ( TransferControlRequestCallback p )
{
      transferControlRequestCallback = p;
}

static OutstandingRequestInfo_t *
addRequest( dis_request_id request_id )
{
      OutstandingRequestInfo_t *p = malloc (sizeof(OutstandingRequestInfo_t));

      if (p) {
            memset (p, 0, sizeof(OutstandingRequestInfo_t));
            p->request_id = request_id;

            p->next = NULL;
            p->prev = request_chain_tail;
            if ( request_chain_tail ) {
                  p->next = p;
            }
            if (request_chain_head == NULL) {
                  request_chain_head = p;
            }
            request_chain_tail = p;
      }

      return p;
}

/*
 *  Remove the specified request tracking structure from the
 *  request tracking list.
 */

static void
removeRequest(OutstandingRequestInfo_t *pItem)
{
      OutstandingRequestInfo_t *p = request_chain_head;

      /*
       * The request list is a mundane doubly linked list.
       */

      if (pItem) {
            if (pItem->prev == NULL) {
                  request_chain_head = p->next;
            }
            else {
                  pItem->prev->next = p->next;
            }

            if (pItem->next == NULL) {
                  request_chain_tail = p->prev;
            }
            else {
                  pItem->next->prev = p->prev;
            }

            free ( pItem );
      }
}

static OutstandingRequestInfo_t *
findRequestByRequestID ( dis_request_id request_id )
{
      OutstandingRequestInfo_t *p = request_chain_head;

      while ( p ) {
            if (p->request_id == request_id) {
                  break;
            }
            p = p->next;
      }

      return p;
}

/*
 *  We must limit our PDU transmission rate on lower bandwidth
 *  connections.
 */

static double bandwidth_bps = 0.0;  /* 0.0 turns off limiting */

static double theTime;
static int absoluteTime = 0;

static DISxApplicationInfo *app;

DISxApplicationInfo *
dis_getApplicationInfo(void)
{
      return app;
}

extern int killPlayer(craft * c);

int dis_shouldTransmitPDUs ( Entity_t *e );

static Entity_t *entities;
static int entity_top = -1;

int dis_isLocalEntity (const dis_entity_id *id);
Entity_t *
dis_getEntityTable(void)
{
      return entities;
}

long
dis_getEntityTop(void)
{
      return entity_top;
}

void
dis_enableNetwork(int state)
{
      network_enabled = state;
}

/*
 *  These coordinate system conversion routines don't do very much today,
 *  but, eventually, we must support a spheroid world (WGS84 standard).
 */

void
ACMtoDISWorld(VPoint * in, dis_world_coordinates * out)
{
      out->x = (in->x);
      out->y = (in->y);
      out->z = (in->z);
}

void
DIStoACMWorld(dis_world_coordinates * in, VPoint * out)
{
      out->x = (in->x);
      out->y = (in->y);
      out->z = (in->z);
}

void
ACMtoDISVelocity(VPoint * in, dis_float_vector * out)
{
      out->x = (float) FEETtoMETERS(in->x);
      out->y = (float) FEETtoMETERS(in->y);
      out->z = (float) FEETtoMETERS(in->z);
}

void
DIStoACMVelocity(dis_float_vector * in, VPoint * out)
{
      out->x = METERStoFEET(in->x);
      out->y = METERStoFEET(in->y);
      out->z = METERStoFEET(in->z);
}

/*
 *  Generate a transform matrix to get from geocentric to local NED coordinates
 */

void
GenerateWorldToLocalMatrix(WorldCoordinates * w, VMatrix * m)
{
      dis_world_coordinates gc;
      VPoint    p;

      VIdentMatrix(m);
      VRotate(m, ZRotation, -w->longitude);
      VRotate(m, YRotation, -w->latitude);
      VRotate(m, YRotation, -DEGtoRAD(90.0));
      DISWorldCoordinatesToGeocentric(w, &gc);
      VTransform((VPoint *) & gc, m, &p);
      m->m[0][3] = -p.x;
      m->m[1][3] = -p.y;
      m->m[2][3] = -p.z;
}

/*
 *  f i n d E n t i t y
 *
 *  Find the entity with Dis id id in the local entities table
 *  and return its index in the table.
 *
 *  The id (index) is returned on success, -1 is returned on failure.
 */

static int
findEntity(const dis_entity_id * id)
{
      int       i;
      Entity_t   *p = entities;

      for (i = 0; i <= entity_top; i++) {
            if (p->local != -1 &&
                  p->entityId.entity_id == id->entity_id &&
                  p->entityId.sim_id.application_id == id->sim_id.application_id &&
                  p->entityId.sim_id.site_id == id->sim_id.site_id) {
                  return i;
            }
            ++p;
      }

      return -1;
}

/*
 *  f i n d L o c a l E n t i t y
 *
 *  Find the local entity with Dis id id in the local entities table
 *  and return its index in the table.
 *
 *  The id (index) is returned on success, -1 is returned on failure.
 */

static int
findLocalEntity( const dis_entity_id * id )
{
      int       i;
      Entity_t   *p = entities;

      for (i = 0; i <= entity_top; i++) {
            if (p->local == 1 &&
                  p->entityId.entity_id == id->entity_id &&
                  p->entityId.sim_id.application_id == id->sim_id.application_id &&
                  p->entityId.sim_id.site_id == id->sim_id.site_id) {
                  return i;
            }
            ++p;
      }

      return -1;
}

/*
 *  d i s _ s e t B a n d w i d t h
 *
 *  Set and estimate of the available network bandwidth
 *  in bits per second.  This value is used to limit entity
 *  state transmissions in an effort to keep UDP traffic
 *  as close to real-time as possible.
 */

void
dis_setBandwidth(double bps)
{
      bandwidth_bps = bps;
}

/*
 *  d i s _ i n i t
 *
 *  Initialize the DIS library.
 *  The broadcast device used is specified with device (e.g. "le0" on a Sun).
 *  The port for receiving DIS packets is specified with port.
 *     (The port number is currently hardwired to 3000.)
 *  The DIS exercise number, site number and application number are specified
 *  by the corresponding arguments.
 *     (The application number is currently hardwired to a part of the local
 *      hostid. This will ensure uniqe application numbers on a local network.)
 *  User callbacks for entity enter, entity exit, fire and detonation are
 *  specified by the last arguments. NULL callbacks may be used for fire
 *  and detonation.
 *
 *  Zero is returned on success.
 */

int
dis_init(int xexercise, int xsite, int xapplication,
             dis_EntityEnterCb xentityEnterCb,
             dis_DetonationCb xdetonationCb)
{
      int       i;
      dis_simulation_addr addr;

      entityEnterCb = xentityEnterCb;
      detonationCb = xdetonationCb;

      exercise = xexercise;

#ifdef WIN32
#define DISxReadPDU           WinACMDISReadPDU
#define DISxWritePDU    WinACMDISWritePDU
      app = WinACMDISxInitializeApplication(xexercise, 
                                                              xsite, xapplication);
      if (app == NULL) {
            return -1;
      }
#else
      app = DISxInitializeApplication(xexercise, xsite, xapplication);
      if (app == NULL) {
            return -1;
      }
#endif

#ifdef USE_CONTROL_REQUEST
      DISxSetPDUProtocolFamily ( PDUTypeExperimentalRequestControl,
                                             PDUFamilySimulationManagement );
      DISxSetPDUProtocolFamily ( PDUTypeExperimentalGrantControl,
                                             PDUFamilySimulationManagement );
#endif

/*
 *  Get the actual simulation address assigned to us.
 */

      DISxGetSimulationAddress(app, &addr);
      site = addr.site_id;
      application = addr.application_id;

/*
 *  Allocate storage for the entity table and initialize it.
 */

      entities = (Entity_t *) malloc(sizeof(Entity_t) * MAX_ENTITIES);
      for (i = 0; i < MAX_ENTITIES; i++) {
            entities[i].local = -1;
      }

      return 0;
}

/*
 *  d i s _ c l o s e
 *
 *  Close down the DIS library.
 *
 *  Zero is returned on success.
 */

int
dis_close(void)
{
      if (entities) {
            free (entities);
      }
      return 0;
}

/*
 *  d i s _ s e t D R T h r e s h o l d s
 *
 *  Set the dead reckoning thresholds for location and orientation.
 *  The values shall be given in meters and radians (psi, theta, phi).
 *
 *  Zero is returned on success.
 */

int
dis_setDRThresholds(double location, double orientation)
{
      locationThreshold = location;
      orientationThreshold = orientation;

      return 0;
}

static const double timeFactor = 596523.235556;       /* 2^31/3600 bits per second */

/*
 *  t i m e D I S T o D o u b l e
 *
 *  Convert a DIS timestamp to a double in UNIX format (seconds since 1970).
 *
 *  If the timestamp _and_ the local time both are absolute times,
 *  the timestamp is used for the part of hour. The local time 'theTime'
 *  is used to get the hour part. The returned value will be
 *  the closest possible to 'theTime'.
 *
 *  If either the timestamp or the local time are _not_ absolute,
 *  the local time is returned. This could be improved...
 */

#ifdef WIN32
#define rint(x)   (double)( (int)(x) )
#endif

static double
timeDISToDouble(dis_timestamp disTime)
{
      double    seconds;                  /* seconds into the current hour */
      double    myseconds;          /* ditto for 'theTime' */
      double    diffseconds;
      double    myhour;             /* hour part of 'theTime' */

      /* if either time is not absolute, return the local time */
      if (disTime.type == 0 || absoluteTime == 0)
            return theTime;

      seconds = disTime.time / timeFactor;
      myseconds = fmod(theTime, 3600.0);
      myhour = rint((theTime - myseconds) / 3600.0);

      diffseconds = myseconds - seconds;

#ifdef DIS_DEBUG_TIME
      printf("time diff %f s\n", diffseconds);
#endif
      if (diffseconds > 1800.0)
            return 3600.0 * (myhour + 1) + seconds;
      else if (diffseconds < -1800.0)
            return 3600.0 * (myhour - 1) + seconds;
      else
            return 3600.0 * myhour + seconds;
}

/*
 *  t i m e D o u b l e T o D I S
 *
 *  Convert a double in UNIX format (seconds since 1970) to a DIS timestamp.
 *  If reference is 0, the time will be marked relative.
 *  If reference is 1, the time will be marked absolute, i.e. true UTC time.
 */

static    dis_timestamp
timeDoubleToDIS(double time, int reference)
{
      unsigned long tmp;
      dis_timestamp res;

      tmp = (unsigned long) (fmod(time, 3600.0) * timeFactor);
      if (tmp > 2147483647L)        /* 2^31 - 1 */
            res.time = 2147483647L;
      else
            res.time = tmp;
      res.type = reference;

      return res;
}

/*
 *  d i s _ s e t T i m e
 *
 *  Set the current time in the DIS library as a relative time.
 *
 *  Zero is returned on success.
 */

int
dis_setTime(double time)
{
      theTime = time;
      absoluteTime = 0;

      return 0;
}

/*
 *  d i s _ s e t T i m e A b s o l u t e
 *
 *  Set the current time in the DIS library using the system time,
 *  and mark the time as absolute, i.e. true UTC time.
 *  This will improve the dead reckoning performance on networks
 *  with significant delays between players _iff_ this host really
 *  have true UTC time (and others declaring true time also really
 *  have it).
 *
 *  Zero is returned on success.
 */

int
dis_setTimeAbsolute(void)
{
      struct timeval tv;

      gettimeofday(&tv, NULL);
      theTime = tv.tv_sec + tv.tv_usec / 1000000.0;
      absoluteTime = 1;

      return 0;
}

/*
 *  g e t E n t i t y S t a t e D a t a
 *
 *  Read in the entity state data from the entity state PDU es
 *  and write it to the local entity with id (index) eid.
 */

static void
getEntityStateData(int eid, dis_entity_state_pdu * es)
{

      entities[eid].lastTime = timeDISToDouble(es->hdr.time_stamp);
      entities[eid].lastLocation[0] = es->pos.x;
      entities[eid].lastLocation[1] = es->pos.y;
      entities[eid].lastLocation[2] = es->pos.z;
      entities[eid].lastVelocity[0] = es->vel.x;
      entities[eid].lastVelocity[1] = es->vel.y;
      entities[eid].lastVelocity[2] = es->vel.z;
      entities[eid].lastOrientation[0] = es->orientation.psi;
      entities[eid].lastOrientation[1] = es->orientation.theta;
      entities[eid].lastOrientation[2] = es->orientation.phi;
      entities[eid].lastLinearAcc[0] = es->dr_parm.linear_acc.x;
      entities[eid].lastLinearAcc[1] = es->dr_parm.linear_acc.y;
      entities[eid].lastLinearAcc[2] = es->dr_parm.linear_acc.z;
      entities[eid].lastAngularVel[0] = es->dr_parm.angular_vel.x;
      entities[eid].lastAngularVel[1] = es->dr_parm.angular_vel.y;
      entities[eid].lastAngularVel[2] = es->dr_parm.angular_vel.z;

      if (es->marking.charset == DISCharSetASCII)
            strcpy(entities[eid].markings, es->marking.marking);
      else
            entities[eid].markings[0] = '\0';

      DISProcessNewDRParameters(es, &entities[eid].dr);
}

/*
 *  e n t i t y E n t e r
 *
 *  Process the entity state PDU esPDU for a new (currently unknown)
 *  entity.
 *
 *  Zero is returned on success.
 */

static int
entityEnter(dis_entity_state_pdu * esPDU)
{
      int       eid;
      craft    *c = NULL;

      for (eid = 0; eid < MAX_ENTITIES; eid++) {
            if (entities[eid].local == -1) {
                  break;
            }
      }
      if (eid >= MAX_ENTITIES) {
            return -1;
      }
      if (eid > entity_top) {
            entity_top = eid;
      }
      entities[eid].local = 0;
      entities[eid].state = DIS_ENTITY_STATE_SIMULATING;
      entities[eid].pending_state = DIS_ENTITY_STATE_NONE;
      entities[eid].emit_while_frozen = 0;
      entities[eid].forceId = esPDU->force_id;
      entities[eid].entityId = esPDU->id;
      entities[eid].entityType = esPDU->type;
      entities[eid].altEntityType = esPDU->alt_type;
      entities[eid].em = (EntityEM_t *) NULL;

      /*
     *  We only care about setting the dead reckoning thresholds
       *  so that we can assume comtrol of an entity.
       */

      DISSetDRThresholds(&entities[eid].dr, SEND_TIMEOUT_SECONDS,
                                 locationThreshold, orientationThreshold);

      getEntityStateData(eid, esPDU);

      /*
       * Pass entity information to the main ACM code.  Based on the DIS
       * entity type, it will determine if this is worth tracking.
       */

      (entityEnterCb) ( eid, &esPDU->type, esPDU->force_id, &c );

      if (c) {
            /* ACM says it's worth tracking */
            entities[eid].c = c;
#ifdef DIS_DEBUG
            printf ("adding entity %d; entity top %d\n", eid, entity_top);
#endif
      }
      else {
            /* must not be an entity we care about ... */
            entities[eid].local = -1;
            if (eid == entity_top) {
                  entity_top--;
            }
            return -1;
      }

      return eid;
}

/*
 *  e n t i t y E x i t
 *
 *  Remove the entity with id (index) eid from the local table.
 */

static void
entityExit(int eid)
{
      craft    *c;

#ifdef DIS_DEBUG
      printf("entityExit (%d)\n", eid);
#endif

      c = entities[eid].c;
      if (c != NULL && c->type == CT_DIS_PLANE) {
#ifdef DIS_DEBUG
            printf("killing ptbl player index %d in entityExit\n", c->pIndex);
#endif
            killPlayer(c);
      }
      entities[eid].local = -1;
      if (eid == entity_top) {
            entity_top--;
      }
}

/*
 *  e n t i t y S t a t e P D U
 *
 *  Process an incoming entity state PDU.
 *
 *  Zero is returned on success.
 */

static int
entityStatePDU(dis_entity_state_pdu * esPDU)
{
      int       eid;

      eid = findEntity(&esPDU->id);

      if ((esPDU->appearance & DISAppearanceDamageDestroyed)) {
            /* deactivated or destroyed entity. if we know about it, exit it */
#ifdef DIS_DEBUG
            printf ("entity %d marked as destroyed.\n");
#endif
            if (eid >= 0) {
                  entityExit(eid);
            }
            return 0;
      }
      else {
            /* normal entity state PDU. if we know about it, update data,
               otherwise enter it */
            if (eid >= 0) {
                  getEntityStateData(eid, esPDU);
                  return 0;
            }
            else {
                  eid = entityEnter(esPDU);
                  if (eid >= 0) {
                        return 0;
                  }
                  else
                        return -1;
            }
      }
}

/*
 *  f i r e P D U
 *
 *  Process an incoming fire PDU.
 *
 *  Zero is returned on success.
 */

 /*
  *  These munition types are renderable with ACM's cannon simulation
  *  code.
  */

static dis_entity_type cannon_types[] =
{
      {2, 2, 225, 2, 1, 0, 0},
      {2, 2, 225, 2, 2, 0, 0},
      {2, 2, 225, 2, 3, 0, 0},
      {2, 2, 225, 2, 4, 0, 0},
      {2, 2, 222, 2, 1, 0, 0},
      {0, 0, 0, 0, 0, 0, 0}
};

static int
firePDU(dis_fire_pdu * fPDU)
{
      int       i, eid;
      craft    *m;
      dis_entity_type *dp;

      for (dp = cannon_types; dp->kind != 0; ++dp) {
            if (fPDU->burst.munition.kind == dp->kind &&
                  fPDU->burst.munition.domain == dp->domain &&
                  fPDU->burst.munition.country == dp->country &&
                  fPDU->burst.munition.category == dp->category &&
                  fPDU->burst.munition.subcategory == dp->subcategory) {
                  break;
            }
      }

/*
 *  Not one of the ones that we model?  Then do nothing.
 */

      if (dp->kind == 0) {
            return 0;
      }
/*
 *  Allocate a projectile record
 */

      for ((i = 0, m = mtbl); i < MAXPROJECTILES; (++i, ++m)) {
            if (m->type == CT_FREE) {
                  m->type = CT_DIS_CANNON;
                  break;
            }
      }
      if (i == MAXPROJECTILES) {
            return -1;
      }
/*
 *  Fill out the projectile record
 */

      eid = findEntity(&fPDU->firing_id);
      if (eid >= 0) {
            m->owner = entities[eid].c->pIndex;
      }
      else {
            m->owner = -1;
      }
      m->createTime = curTime;
      m->curRoll = 0.0;
      m->curPitch = 0.0;
      m->curHeading = 0.0;

/*
 *  Determine the initial position.
 */

      DIStoACMVelocity((dis_float_vector *) &fPDU->vel, &m->Cg);
      DIStoACMWorld(&fPDU->pos, &m->Sg);

      m->prevSg = m->Sg;
      m->rounds = fPDU->burst.quantity;
      m->tracerMod = 10 /*TRACER_MOD */ ;
      m->tracerVal = 0;
      m->offset = 0.0;
      m->interval = deltaT;
      m->cinfo = lookupCraft("m61a1 cannon");

      return 0;
}

/*
 *  d e t o n a t i o n P D U
 *
 *  Process an incoming detonation PDU.
 *
 *  Zero is returned on success.
 */

static int
detonationPDU(dis_detonation_pdu * dPDU)
{
      int       firingEid, targetEid, eid, ftype;
      double    time, worldLocation[3], entityLocation[3];
      craft    *c;

      if (detonationCb == NULL) {
            eid = findEntity(&dPDU->munition_id);
            if (eid >= 0) {
                  entities[eid].local = -1;
                  if (eid == entity_top) {
                        entity_top--;
                  }
            }
            return 0;
      }
      firingEid = findEntity(&dPDU->firing_id);
      if (firingEid < 0)
            return -1;
      targetEid = findEntity(&dPDU->target_id);
      if (targetEid < 0)
            return -2;

      /* the ftype field isn't relly used anymore */

      if (dPDU->burst.munition.category == 2)         /* Ballistic */
            ftype = DIS_FIRE_M61A1;
      else
            ftype = DIS_FIRE_AIM9M;

      time = timeDISToDouble(dPDU->hdr.time_stamp);

      worldLocation[0] = dPDU->pos.x;
      worldLocation[1] = dPDU->pos.y;
      worldLocation[2] = dPDU->pos.z;

      entityLocation[0] = dPDU->loc.x;
      entityLocation[1] = dPDU->loc.y;
      entityLocation[2] = dPDU->loc.z;

      eid = findEntity(&dPDU->munition_id);
      c = (eid < 0) ? NULL : entities[eid].c;

      (detonationCb) (ftype, firingEid, targetEid, time, worldLocation,
                              entityLocation, c, dPDU);

      if (eid >= 0) {
            entities[eid].local = -1;
            if (eid == entity_top) {
                  entity_top--;
            }
      }
      return 0;
}

/*
 *  e m i s s i o n P D U
 *
 *  Process an incoming EM emission PDU.
 *
 *  Zero is returned on success.
 */

static int
emissionPDU(dis_em_emission_pdu * pdu)
{
      Entity_t   *e;
      int       emitterEid;

      emitterEid = findEntity(&pdu->emitter_id);
      if (emitterEid < 0) {
            return -1;
      }
      else {
            e = &entities[emitterEid];
      }

/*
 *  First emission received?
 */

      if (e->em == (EntityEM_t *) NULL) {

            dis_initializeEMInfo ( e );

      }

/*
 *  Not the first emission.  Free the old PDU variable fields and insert
 *  the new one.
 */

      else {
            DISFreePDUComponents((dis_pdu *) & e->em->em);
      }
      e->em->em = *pdu;
      e->em->lastTime = theTime;
      return 0;
}

/*
 *  d i s _ r e c e i v e
 *
 *  Process all available incoming PDU's from the network.
 *  User callbacks will be called for entering entities,
 *  exiting entities, firing entities and detonations.
 *
 *  Zero is returned on success.
 */

int
dis_receive(void)
{
      int       status, err, free_needed;
      dis_pdu   pdu;
      int       i;

      if (network_enabled == 0) {
            return 0;
      }

      err = 0;
      while ((status = DISxReadPDU(app, &pdu)) == 0) {

            free_needed = 1;

            /* ignore other exercises */
            if (pdu.hdr.exercise_id != exercise)
                  goto free_pdu;

            switch (pdu.hdr.pdu_type) {
            case PDUTypeEntityState:
                  /* don't read our own broadcasts */
                  if (dis_isLocalEntity(&pdu.entity_state.id))
                        goto free_pdu;

                  err = entityStatePDU(&pdu.entity_state);
                  break;

            case PDUTypeFire:
                  /* don't read our own broadcasts */
                  if (dis_isLocalEntity(&pdu.detonation.firing_id))
                        goto free_pdu;

                  err = firePDU(&pdu.fire);
                  break;

            case PDUTypeDetonation:
                  /* don't read our own broadcasts */
                  if (dis_isLocalEntity(&pdu.detonation.firing_id))
                        goto free_pdu;

                  err = detonationPDU(&pdu.detonation);
                  break;

            case PDUTypeEmission:
                  /* don't read our own broadcasts */
                  if (dis_isLocalEntity(&pdu.em_emission.emitter_id))
                        goto free_pdu;
                  err = emissionPDU(&pdu.em_emission);
                  free_needed = 0;
                  break;

            case PDUTypeSetData:
                  /* don't read our own broadcasts */
                  if (dis_isLocalEntity(&pdu.set_data.orig_id))
                        goto free_pdu;
                  err = setDataPDU( &pdu.set_data );
                  free_needed = 0;
                  break;

            case PDUTypeStopFreeze:
                  /* don't read our own broadcasts */
                  if (dis_isLocalEntity(&pdu.stop.orig_id))
                        goto free_pdu;
                  err = stopPDU( &pdu.stop );
                  free_needed = 0;
                  break;

            case PDUTypeStartResume:
                  /* don't read our own broadcasts */
                  if (dis_isLocalEntity(&pdu.start.orig_id))
                        goto free_pdu;
                  err = startPDU( &pdu.start );
                  free_needed = 0;
                  break;

            case PDUTypeTransferControl:
                  /* don't read our own broadcasts */
                  if (dis_isLocalEntity(&pdu.transfer_control.orig_id))
                        goto free_pdu;
                  err = transferControlPDU( &pdu.transfer_control );
                  free_needed = 0;
                  break;

            case PDUTypeAcknowledge:
                  /* don't read our own broadcasts */
                  if (dis_isLocalEntity(&pdu.acknowledge.orig_id))
                        goto free_pdu;
                  err = acknowledgePDU( &pdu.acknowledge );
                  free_needed = 0;
                  break;

            default:
                  fprintf(stderr, 
                              "dis_receive: Ignoring PDU type %d\n", pdu.hdr.pdu_type);
                  err = 0;
                  break;
            }

/*
 *  Free any dynamically allocated variable components that are part of this
 *  PDU.
 */

        free_pdu:
            if (free_needed) {
                  DISFreePDUComponents(&pdu);
            }

      }                                         /* while (... DISxReadPDU ...) */

/*
 *  check for timeouts on remote entities and look for pending state changes
 */

      for (i = 0; i <= entity_top; i++) {
            Entity_t *e = &entities[i];
            if (e->local == 0
                  && theTime - e->lastTime > RECV_TIMEOUT_SECONDS) {
#ifdef DIS_DEBUG
                  printf ("entity state PDU timeout: index %d\n", i);
#endif
                  entityExit(i);
            }

            if ( e->pending_state != DIS_ENTITY_STATE_NONE ) {
                  if (theTime >= e->pending_time) {
                        e->state = e->pending_state;
                        e->pending_state = DIS_ENTITY_STATE_NONE;
                  }
            }
      }

      if (err != 0) {
            return -2;
      }
      else {
            return 0;
      }
}

/*
 *  s e n d E n t i t y S t a t e
 *
 *  Send an entity state PDU for the local entity with id (index) eid.
 *
 *  Zero is returned on success.
 */

static int
sendEntityState(int eid)
{
      dis_entity_state_pdu pdu, *esPDU = &pdu;
      int       i;
      Entity_t  *e = &entities[eid];

      if (network_enabled == 0) {
            return 0;
      }

      if  (dis_shouldTransmitPDUs ( e ) == 0) {
            return 0;
      }

      esPDU->hdr.pdu_type = PDUTypeEntityState;
      esPDU->hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);
      esPDU->id = e->entityId;
      esPDU->force_id = e->forceId;
      esPDU->art_parm_count = 0;
      esPDU->type = e->entityType;
      esPDU->alt_type = e->altEntityType;
      esPDU->pos.x = e->location[0];
      esPDU->pos.y = e->location[1];
      esPDU->pos.z = e->location[2];
      esPDU->vel.x = (float) e->velocity[0];
      esPDU->vel.y = (float) e->velocity[1];
      esPDU->vel.z = (float) e->velocity[2];
      esPDU->orientation.psi = (float) e->orientation[0];
      esPDU->orientation.theta = (float) e->orientation[1];
      esPDU->orientation.phi = (float) e->orientation[2];
      esPDU->appearance = e->appearance;
      esPDU->dr_parm.algorithm = DISDRMethodRVW;
      esPDU->dr_parm.linear_acc.x = (float) e->linearAcceleration[0];
      esPDU->dr_parm.linear_acc.y = (float) e->linearAcceleration[1];
      esPDU->dr_parm.linear_acc.z = (float) e->linearAcceleration[2];
      esPDU->dr_parm.angular_vel.x = (float) e->angularVelocity[0];
      esPDU->dr_parm.angular_vel.y = (float) e->angularVelocity[1];
      esPDU->dr_parm.angular_vel.z = (float) e->angularVelocity[2];
      esPDU->marking.charset = DISCharSetASCII;
      memset(esPDU->marking.marking, 0, MARKINGS_LEN);
      strcpy(esPDU->marking.marking, e->markings);
      esPDU->capabilities = 0;
      esPDU->art_parm = NULL;

      if (DISxWritePDU(app, (dis_pdu *) esPDU) != 0) {
            printf ("error writing PDU\n");
            return -2;
      }
      else {
            DISProcessNewDRParameters(esPDU, &entities[eid].dr);
            entities[eid].lastTime = theTime;
            for (i = 0; i < 3; i++) {
                  entities[eid].lastLocation[i] = entities[eid].location[i];
                  entities[eid].lastVelocity[i] = entities[eid].velocity[i];
                  entities[eid].lastLinearAcc[i] = entities[eid].linearAcceleration[i];
                  entities[eid].lastOrientation[i] = entities[eid].orientation[i];
                  entities[eid].lastAngularVel[i] = entities[eid].angularVelocity[i];
            }

            return 0;
      }
}

/*
 *  s e t P o s D a t a
 *
 *  Set the position data of the entity with id (index) eid to the given
 *  values.
 */

static void
setPosData(int eid, double loc[3], double vel[3], double linAcc[3],
               double ori[3], double angVel[3])
{
      int       i;

      for (i = 0; i < 3; i++) {
            entities[eid].location[i] = loc[i];
            entities[eid].velocity[i] = vel[i];
            entities[eid].linearAcceleration[i] = linAcc[i];
            entities[eid].orientation[i] = ori[i];
            entities[eid].angularVelocity[i] = angVel[i];
      }
}

/*
 *  d i s _ e n t i t y E n t e r
 *
 *  Enter an entity of type etype (DIS_ENTITY_XXX).
 *  The initial location, velocity, linear acceleration, orientation
 *  and angular velocity will be set from the corresponding arguments.
 *  Velocity and acceleration shall be given in world coordinates.
 *  All parameter units are based on meters, radians and seconds.
 *
 *  The world coordinate system used in DIS is GCC (geocentric cartesian
 *  coordinates), an earth-centered right-handed Cartesian system with
 *  the positive X-axis passing through the Prime Meridian at the Equator,
 *  with the positive Y-axis passing through 90 degrees East longitude
 *  at the Equator and with the positive Z-axis passing through the
 *  North Pole.
 *
 *  The body coordinate system used in DIS is centered at the center of
 *  the entity's bounding volume (excluding articulated parts) and have
 *  the positive x-axis pointing to the front of the entity, the positive
 *  y-axis pointing to the right side of the entity and the positive z-axis
 *  pointing out of the bottom of the entity.
 *
 *  Orientation is given as [psi, theta, phi]. Angular velocity is given
 *  as [angular velocity around body x-axis, ditto y, ditto z].
 *
 *  The id to be used for further reference is returned in eid.
 *
 *  Zero is returned on success.
 */

int
dis_entityEnter(int team, 
                        craft * c, 
                        dis_entity_type * e1, 
                        dis_entity_type * e2,
                        double loc[3], 
                        double vel[3],
                        double linAcc[3], 
                        double ori[3], 
                        double angVel[3],
                        int *neid)
{
      static int eid;

      for (eid = 0; eid < MAX_ENTITIES; eid++) {
            if (entities[eid].local < 0) {
                  break;
            }
      }
      if (eid >= MAX_ENTITIES) {
            return -1;
      }
      if (eid > entity_top) {
            entity_top = eid;
      }
#ifdef DIS_DEBUG
      printf("Allocated eid %d; entity_top %d\n", eid, entity_top);
#endif

      entities[eid].local = 1;
      entities[eid].c = c;
      entities[eid].state = DIS_ENTITY_STATE_SIMULATING;
      entities[eid].pending_state = DIS_ENTITY_STATE_NONE;
      entities[eid].emit_while_frozen = 0;

      DISxIssueEntityID(app, &entities[eid].entityId);

      entities[eid].forceId = (team == 1) ?
            DISForceFriendly :
            DISForceOpposing;
      entities[eid].entityType = *e1;
      entities[eid].altEntityType = *e2;

      memset(entities[eid].markings, 0, MARKINGS_LEN);
      entities[eid].markings[0] = '\0';
      entities[eid].appearance = 0;
      setPosData(eid, loc, vel, linAcc, ori, angVel);

      *neid = eid;

/*
 *  Allocate  EM emission fields (optimized for the fact that we have
 *  only one system, with one beam, and one potentially tracked target.
 */

      entities[eid].em =
            (EntityEM_t *) Vmalloc(sizeof(EntityEM_t));
      entities[eid].em->mode = 0;
      entities[eid].em->cur_target = -1;
      entities[eid].em->em.hdr.pdu_type = PDUTypeEmission;
      entities[eid].em->em.emitter_id = entities[eid].entityId;

      entities[eid].em->s =
            (dis_em_system_info *) Vmalloc(sizeof(dis_em_system_info));
      entities[eid].em->b =
            (dis_beam_info *) Vmalloc(sizeof(dis_beam_info));
      entities[eid].em->target =
            (dis_track_info *) Vmalloc(sizeof(dis_track_info));
      entities[eid].em->em.system = entities[eid].em->s;
      entities[eid].em->em.num_systems = 1;
      entities[eid].em->em.system[0].num_beams = 0;

      DISSetDRThresholds(&entities[eid].dr, SEND_TIMEOUT_SECONDS,
                                 locationThreshold, orientationThreshold);

      /* let the normal dis code do it */
#ifdef notdef
      if (sendEntityState(eid) < 0)
            return -1;
      else
#endif
            return 0;
}

void
dis_setEntityMarkings(int eid, char *markings)
{
      strncpy(entities[eid].markings, markings, MARKINGS_LEN);
}

void
dis_getEntityMarkings(int eid, char *markings, int max)
{
      strncpy(markings, entities[eid].markings, max);
}

void
dis_setEntityAppearance(int eid, dis_entity_appearance x)
{
      entities[eid].appearance = x;
}

dis_entity_appearance
dis_getEntityAppearance(int eid)
{
      return entities[eid].appearance;
}

/*
 *  d i s _ e n t i t y E x i t
 *
 *  Remove the local entity with id eid from the simulation.
 *
 *  Zero is returned on success.
 */

int
dis_entityExit(int eid)
{

#ifdef DIS_DEBUG
      printf("Entity_t exit called: eid %d\n", eid);
#endif

      if (entities[eid].local != 1)
            return -1;
      else {
            entities[eid].appearance = DISAppearanceDamageDestroyed;
            sendEntityState(eid);

            if (entities[eid].em) {
                  free(entities[eid].em->s);
                  free(entities[eid].em->b);
                  free(entities[eid].em->target);
                  free(entities[eid].em);
            }
            entities[eid].local = -1;
            if (eid == entity_top) {
                  entity_top--;
            }
            return 0;
      }
}

/*
 *  d i s _ e n t i t y S t a t e
 *
 *  Update the state information for a local entity.
 *  The information will be broadcasted on the network
 *  only if it is necessary to keep the other hosts dead
 *  reckoning from exceeding the thresholds.
 *  See dis_entityEnter for information about the arguments.
 *
 *  This procedure also handles the transmission of emission PDU's.
 *
 *  Zero is returned on success.
 */

int
dis_entityState(int eid, double loc[3], double vel[3],
                        double linAcc[3],
                        double ori[3], double angVel[3])
{
      double    delta, min_delta;
      int       needToSend = 0, needEMPDU = 0, status, i, j;
      dis_euler_angles ori_e;

      if (entities[eid].local != 1)
            return -1;

      setPosData(eid, loc, vel, linAcc, ori, angVel);

/*
 *  EM emission PDU possibly needed ?
 */

      delta = theTime - entities[eid].lastTime;

      if (entities[eid].em && entities[eid].em->mode > 0) {
            if (delta > SEND_TIMEOUT_SECONDS) {
                  needEMPDU = 1;
                  for (i = 0; i < entities[eid].em->em.num_systems; ++i) {
                        for (j = 0; j < entities[eid].em->em.system[0].num_beams; ++j) {
                              entities[eid].em->em.system[i].beam[j].fundamental.beam_sweep_sync += (float) delta;
                        }
                  }
                  entities[eid].em->lastTime = theTime;
                  entities[eid].em->em.state_update = 0;
            }
      }

      ori_e.psi   = (float) ori[0];
      ori_e.theta = (float) ori[1];
      ori_e.phi   = (float) ori[2];

      needToSend = DISTestDRThresholds(&entities[eid].dr, delta,
                        (dis_world_coordinates *) loc, &ori_e);

      /*
       *  Are we limiting PDU tansmissions? 
       *
       *  If so, ensure enough time has passed since
       *  our last entity state transmission.
       */

      if (bandwidth_bps > 0.0) {
            min_delta = 1440.0 * entity_top / bandwidth_bps;
            if (delta < min_delta) {
                  needToSend = 0;
            }
      }

      if (needToSend != 0) {
            sendEntityState(eid);
      }
      else {
            /* no need to send ES PDU */
      }

      if ( needEMPDU && entities[eid].state == DIS_ENTITY_STATE_SIMULATING ) {
            entities[eid].em->em.hdr.time_stamp =
                  timeDoubleToDIS(theTime, absoluteTime);
            status = DISxWritePDU(app, (dis_pdu *) & entities[eid].em->em);
      }
      return (needToSend > 0) ? 1 : 0;
}

/*
 *  d i s _ g e t E n t i t y S t a t e
 *
 *  Return state information for a remote entity.
 *  The state information is dead reckoned from the last
 *  received data on the entity.
 *
 *  Zero is returned on success.
 */

int
dis_getEntityState(int eid, double loc[3], double vel[3], double ori[3])
{
      int       i;

      if (entities[eid].local < 0) {
            return -1;
      }
      else if (entities[eid].local == 1) {
            for (i = 0; i < 3; i++) {
                  loc[i] = entities[eid].location[i];
                  ori[i] = entities[eid].orientation[i];
            }

            return 0;
      }
      else {
            VMatrix   orientation;
            dis_linear_vel_vector drvel;

            DISComputeDRPosition(&entities[eid].dr,
                                           theTime - entities[eid].lastTime,
                                (dis_world_coordinates *) & entities[eid].location,
                                           &drvel,
                                           &orientation);

            entities[eid].velocity[0] = drvel.x;
            entities[eid].velocity[1] = drvel.y;
            entities[eid].velocity[2] = drvel.z;

            matrixToEuler(&orientation,
                                &entities[eid].orientation[0],
                                &entities[eid].orientation[1],
                                &entities[eid].orientation[2]);

            for (i = 0; i < 3; i++) {
                  loc[i] = entities[eid].location[i];
                  vel[i] = entities[eid].velocity[i];
                  ori[i] = entities[eid].orientation[i];
            }

            return 0;
      }
}

/*
 *  d i s _ f i r e
 *
 *  Broadcast information about an entity firing a weapon.
 *  The type of fire is given by ftype as one of the DIS_FIRE_XXX types.
 *  The id's of the firing entity and the target entity are given with
 *  firingEid and targetEid or as DIS_ID_NONE if not known.
 *  The number of rounds, location of the source of fire, the velocity
 *  vector of the rounds and the range of the rounds are given with
 *  the corresponding arguments.
 *  The id of the event generated is returned in eventId.
 *  If the fire type is a missile, a missile entity is created and its
 *  id is returned in missileEid. The user program should generate
 *  position data for the missile during its lifetime by calling
 *  dis_entityState().
 *
 *  Zero is returned on success.
 *
 *  Not yet implemented.
 */

int
dis_fire(int ftype, int firingEid, int targetEid, int rounds,
             double location[3], double velocity[3], double range,
             int *eventId, int *missileEid)
{
      /* TODO */
      *eventId = 0;
      *missileEid = 0;

      return 0;
}

static dis_entity_id null_id =
{0, 0, 0};

int
dis_fire_cannon(craft * c, VPoint * pos, VPoint * vel, int quantity, int rate)
{
      dis_fire_pdu fire;
      int       status;

      if (network_enabled == 0) {
            return 0;
      }

      fire.hdr.pdu_type = PDUTypeFire;

      fire.firing_id = entities[c->disId].entityId;
      fire.target_id = null_id;
      fire.munition_id = null_id;

      DISxIssueEventID(app, &fire.event);

      fire.fire_mission_index = 0;  /* NO_FIRE_MISSION */

      ACMtoDISWorld(pos, &fire.pos);
      fire.burst.munition = cannon_types[1];
      fire.burst.warhead = 0;
      fire.burst.fuze = 0;
      fire.burst.quantity = quantity;
      fire.burst.rate = rate;

      ACMtoDISVelocity(vel, (dis_float_vector *) &fire.vel);

      fire.range = 0.0f;

      fire.hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);

      status = DISxWritePDU(app, (dis_pdu *) & fire);
      return (status == 0) ? 0 : -1;
}

/*
 *  d i s _ d e t o n a t i o n
 *
 *  Broadcast information about a detonation.
 *  The type of fire is given by ftype as one of the DIS_FIRE_XXX types.
 *  The id's of the firing entity and the target entity are given with
 *  firingEid and targetEid or as DIS_ID_NONE if not known.
 *  The id of the corresponding fire event is given as eventId or as
 *  DIS_ID_NONE if not known.
 *  If the detonation is from a missile, the id of the missile is given
 *  as missileEid  or as DIS_ID_NONE. The library will exit the
 *  missile entity.
 *  The location of the detonation in world coordinates and in target
 *  body coordinates are given as worldLocation and entityLocation.
 *
 *  Zero is returned on success.
 */

int
dis_detonation(dis_entity_type * etype,
                     int firingEid, int targetEid, int missileEid,
                     double worldLocation[3], double entityLocation[3],
                     double vel[3])
{
      dis_detonation_pdu pdu;
      int       status;

      if (network_enabled == 0) {
            return 0;
      }

      pdu.hdr.pdu_type = PDUTypeDetonation;

      if (entities[firingEid].local != 1) {
            return -1;
      }
      if (targetEid != DIS_ID_NONE && entities[targetEid].local == -1) {
            return -2;
      }
      pdu.firing_id = entities[firingEid].entityId;
      if (targetEid != DIS_ID_NONE) {
            pdu.target_id = entities[targetEid].entityId;
      }
      else {
            pdu.target_id = null_id;
      }

#ifdef DIS_DEBUG
      printf("detonation: %d %d %d\n", firingEid, targetEid, missileEid);
#endif

      if (missileEid != DIS_ID_NONE) {
            pdu.munition_id = entities[missileEid].entityId;
      }
      else {
            pdu.munition_id = null_id;
      }

      DISxIssueEventID(app, &pdu.event);

      pdu.vel.x = (float) vel[0];
      pdu.vel.y = (float) vel[1];
      pdu.vel.z = (float) vel[2];

      pdu.pos.x = worldLocation[0];
      pdu.pos.y = worldLocation[1];
      pdu.pos.z = worldLocation[2];

      pdu.burst.munition = *etype;

/*
 *  This code will need some extra work ...
 */

      if (pdu.burst.munition.category == 2) {
            pdu.burst.warhead = DISWarheadKinetic;
            pdu.burst.fuze = DISFuzeContact;
            pdu.burst.quantity = 1;
            pdu.burst.rate = 0;
            pdu.result = DISDetonationResultEntityImpact;
      }
      else {
            pdu.burst.warhead = DISWarheadHEFragment;
            pdu.burst.fuze = DISFuzeProximity;
            pdu.burst.quantity = 1;
            pdu.burst.rate = 0;
            pdu.result = DISDetonationResultDetonation;
      }

      pdu.loc.x = (float) entityLocation[0];
      pdu.loc.y = (float) entityLocation[1];
      pdu.loc.z = (float) entityLocation[2];

      pdu.result = 0;
      pdu.num_art_parms = 0;
      pdu.art_parm = NULL;

      pdu.hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);

      status = DISxWritePDU(app, (dis_pdu *) & pdu);
      return (status == 0) ? 0 : -1;
}

/*
 * d i s _ i n i t i a l i z e E M I n f o
 *
 * Initialize EM info data structures.  Returns zero on success, -1 on error.
 */

static int
dis_initializeEMInfo ( Entity_t *e )
{
      int result = 0;

      if (e->em == (EntityEM_t *) NULL) {

/*
 *  Allocate  EM emission information structure and initialize it to reflect
 *  that this is an external entity (mode == -1).
 */

            e->em = (EntityEM_t *) Vmalloc(sizeof(EntityEM_t));
            if (e->em) {
                  e->em->mode = -1;
                  e->em->cur_target = -1;
            }
            else {
                  result = -1;
            }

      }
      return result;
}

/*
 *  Construct an EM emission PDU that reflects the current state of our
 *  radar set.
 */

int
constructEmissionPDU(craft * c, int mode, int update)
{
      dis_em_emission_pdu *em;
      dis_em_system_info *s;
      dis_beam_info *b;
      dis_track_info *target;

      entities[c->disId].em->mode = mode;
      em = &entities[c->disId].em->em;
      s = entities[c->disId].em->s;
      b = entities[c->disId].em->b;
      target = entities[c->disId].em->target;

      DISxIssueEventID(app, &em->event);

      em->system = s;
      em->state_update = update;
      em->num_systems = 1;

      s->location.x = 0.0f;
      s->location.y = 0.0f;
      s->location.z = 0.0f;
      s->emitter_system.name = 0;
      s->emitter_system.function = DISEmitterFuncAirborneFireControl;
      s->emitter_system.id = 1;

      switch (mode) {
      case 0:
            s->num_beams = 0;
            s->beam = NULL;
            break;

/*
 *  Three-bar track while scan mode
 */

      case 1:
            s->num_beams = 1;
            s->beam = b;
            b->beam_id = 1;
            b->beam_parm_index = 0;
            b->beam_function = DISBeamFuncAcquisitionAndTracking;
            b->fundamental.freq = 9000.0f;
            b->fundamental.erp = 100.0f;
            b->fundamental.prf = 18000.0f;
            b->fundamental.pulse_width = 1.0f;
            b->fundamental.beam_azimuth_center = 0.0f;
            b->fundamental.beam_azimuth_sweep = 120.0f;
            b->fundamental.beam_elev_center = 0.0f;
            b->fundamental.beam_elev_sweep = 120.0f;
            b->fundamental.beam_sweep_sync = 0.0f;
            b->pad = 0;
            b->jamming_mode = 0;
            break;

/*
 *  Four bar 20 x 30 ACM mode
 */

      case 2:
            s->num_beams = 1;
            s->beam = b;
            b->beam_id = 1;
            b->beam_parm_index = 1;
            b->beam_function = DISBeamFuncAcquisitionAndTracking;
            b->fundamental.freq = 9000.0f;
            b->fundamental.erp = 100.0f;
            b->fundamental.prf = 18000.0f;
            b->fundamental.pulse_width = 1.0f;
            b->fundamental.beam_azimuth_center = 0.0f;
            b->fundamental.beam_azimuth_sweep = 30.0f;
            b->fundamental.beam_elev_center = 0.0f;
            b->fundamental.beam_elev_sweep = 20.0f;
            b->fundamental.beam_sweep_sync = 0.0f;
            b->pad = 0;
            b->jamming_mode = 0;
            break;

/*
 *  Single target track
 */

      case 3:
            s->num_beams = 1;
            s->beam = b;
            b->beam_id = 1;
            b->beam_parm_index = 2;
            b->beam_function = DISBeamFuncAcquisitionAndTracking;
            b->fundamental.freq = 9000.0f;
            b->fundamental.erp = 100.0f;
            b->fundamental.prf = 18000.0f;
            b->fundamental.pulse_width = 1.0f;
            b->fundamental.beam_azimuth_center = 0.0f;      /* wrong, don't care */
            b->fundamental.beam_azimuth_sweep = 0.0f;
            b->fundamental.beam_elev_center = 0.0f;         /* wrong, don't care */
            b->fundamental.beam_elev_sweep = 0.0f;
            b->fundamental.beam_sweep_sync = 0.0f;
            b->pad = 0;
            b->jamming_mode = 0;
            break;
      }

      entities[c->disId].em->cur_target = c->curRadarTarget;

      if (c->curRadarTarget == -1) {
            b->tracked_target = NULL;
            b->num_targets = 0;
      }
      else {
            b->tracked_target = target;
            b->num_targets = 1;

            target->target =
                  entities[ptbl[c->curRadarTarget].disId].entityId;
            target->emitter_id = 1;
            target->beam_id = 1;
      }
      return 0;
}

/*
 *  Set local entities current radar mode (modes are 0=off, 1=wide scan)
 */

int
dis_setRadarMode(craft * c, int mode, int update)
{
      int       status;
      Entity_t  *e = &entities[c->disId];

      if ( e->em == NULL) {
            dis_initializeEMInfo ( e );
      }

      if ( e->em->mode == mode) {
            return 0;
      }

      if ( e->em->mode == -1 ) {
            e->em->s = (dis_em_system_info *) Vmalloc(sizeof(dis_em_system_info));
            e->em->b = (dis_beam_info *) Vmalloc(sizeof(dis_beam_info));
            e->em->target = (dis_track_info *) Vmalloc(sizeof(dis_track_info));
            e->em->em.system = e->em->s;
            e->em->em.num_systems = 1;
            e->em->em.system[0].num_beams = 0;
      }
      constructEmissionPDU(c, mode, 1);
      entities[c->disId].em->em.hdr.time_stamp =
            timeDoubleToDIS(theTime, absoluteTime);
      entities[c->disId].em->lastTime = theTime;
      if (network_enabled == 0) {
            return 0;
      }
      status = DISxWritePDU(app, (dis_pdu *) & entities[c->disId].em->em);
      return (status == 0) ? 0 : -1;
}

/*
 *  Notify the world that our current radar target changed
 */

int
dis_radarTargetChanged(craft * c)
{
      int       status;

      if ( entities[c->disId].em == NULL) {
            dis_initializeEMInfo ( &entities[c->disId] );
      }

      if (entities[c->disId].em->cur_target == c->curRadarTarget) {
            return 0;
      }
      constructEmissionPDU(c, entities[c->disId].em->mode, 1);
      entities[c->disId].em->em.hdr.time_stamp =
            timeDoubleToDIS(theTime, absoluteTime);
      entities[c->disId].em->lastTime = theTime;
      if (network_enabled == 0) {
            return 0;
      }
      status = DISxWritePDU(app, (dis_pdu *) & entities[c->disId].em->em);
      return (status == 0) ? 0 : -1;
}

/*
 *  Get the number of beams emitted from this aircraft
 */

int
dis_getBeamCount(craft * c)
{
      if (entities[c->disId].em && entities[c->disId].em->em.num_systems > 0) {
            return entities[c->disId].em->em.system[0].num_beams;
      }
      return 0;
}

/*
 *  Get parameters describing the specified beam
 */

void
dis_getRadarParameters(craft * c, int j, double *az_center, double *az_width,
                                 double *el_center, double *el_width, double *e)
{
      dis_beam_info *b;

      if ( entities[c->disId].em == NULL) {
            dis_initializeEMInfo ( &entities[c->disId] );
      }

      b = &entities[c->disId].em->em.system[0].beam[j];

      *az_center = b->fundamental.beam_azimuth_center;
      *az_width = b->fundamental.beam_azimuth_sweep;
      *el_center = b->fundamental.beam_elev_center;
      *el_width = b->fundamental.beam_elev_sweep;
      *e = b->fundamental.erp;
}

int
dis_isLocalEntity (const dis_entity_id *id)
{
      int eid = findLocalEntity ( id );

      return (eid >= 0) ? 1 : 0;
}

int
transferControlPDU ( dis_transfer_control_pdu *pdu )
{
      Entity_t     *e;
      int          eid;
      dis_acknowledge_pdu reply_pdu;
      int status = 0;
      int error_return_needed = 0;

      reply_pdu.hdr            = pdu->hdr;
      reply_pdu.hdr.pdu_type   = PDUTypeAcknowledge;
      reply_pdu.hdr.time_stamp = timeDoubleToDIS( theTime, absoluteTime );

      reply_pdu.orig_id      = pdu->recv_id;
      reply_pdu.recv_id      = pdu->orig_id;
      reply_pdu.request_id   = pdu->request_id;

      /*
       *  Request applies to one of our entities?
       */

      eid = findLocalEntity( &pdu->target_id );
      if ( eid >= 0 ) {

            e = &entities[eid];

            if (pdu->recv_id.sim_id.site_id == site &&
                  ( pdu->recv_id.sim_id.application_id == ALL_APPLIC ||
                    pdu->recv_id.sim_id.application_id == application )) {
      
                  reply_pdu.acknowledge_flag   = 36;  /* per CALSPAN */
                  reply_pdu.resp_flag    = 1;               /* per CALSPAN */

                  switch (pdu->transfer_type) {

                  /*
                   *  Someone wants us to take control of an entity
                   */

                  case DISTransferTypeEntityControllerRequest:

                        /*
                         *  the transferControlRequestCallback function is 
                         *  reponsible for determining if we can feasibly take 
                         *  control of the entity.
                         */

                        if (transferControlRequestCallback != NULL &
                              (*transferControlRequestCallback)(e, pdu) == 0) {

                              status = DISxWritePDU( app, (dis_pdu *) & reply_pdu );
                              status = (status == 0) ? 0 : -1;

                              if (status == 0) {
                                    e->local = 1;
                              }
                        }
                        else {
                              error_return_needed = 1;
                        }
                        break;

                /*
                   *  Control of this entity is requested by someone else.
                   */

                  case DISTransferTypeEntityRequest:

                        if (transferControlRequestCallback != NULL &&
                              (*transferControlRequestCallback)(e, pdu) == 0) {

                              status = DISxWritePDU( app, (dis_pdu *) & reply_pdu );
                              status = (status == 0) ? 0 : -1;

                              if (status == 0) {
                                    e->local = 0;
                              }
                        }
                        else {
                              error_return_needed = 1;
                        }
                        break;
                  }

            }
      }
      else {
            /*
             *  The target entity was not local to us.  Still, the PDU might 
         *  look like it is destined for us.  In this case, return an 
         *  error reply.
             */

            if (pdu->recv_id.sim_id.site_id == site &&
                  ( pdu->recv_id.sim_id.application_id == ALL_APPLIC ||
                    pdu->recv_id.sim_id.application_id == application )) {

                  error_return_needed = 1;
            }
      }

      if (error_return_needed) {

            reply_pdu.acknowledge_flag   = 36;  /* per CALSPAN */
            reply_pdu.resp_flag  = 5;                 /* error state, per CALSPAN */

            status = DISxWritePDU(app, (dis_pdu *) & reply_pdu);
            status = (status == 0) ? 0 : -1;
      }

      return status;
}

int
acknowledgePDU ( dis_acknowledge_pdu *pdu )
{
      Entity_t   *e;
      int       eid;
      OutstandingRequestInfo_t *preq;

      preq = findRequestByRequestID ( pdu->request_id );

      if ( preq != NULL && 
             preq->request_type == OUTSTANDING_REQUEST_TRANSFER_CONTROL ) {

            e = preq->e;

            /*
             * no longer need to track the request
             */

            removeRequest ( preq );

            /*
             * Were we expecting to be granted control? If not, there is some sort
         * of error.
             *
             * This is a non-standard exchange defined by CALSPAN. We are 
         * processing a response to a control request.  The request was 
         * originated by us, the response we just received comes from the 
             * controlling application.
             */

            if ( pdu->acknowledge_flag == 36 ) {

                  /*
                   * response flag set to "1" to indicate a success
                   *                      "5" indicates a failure
                   */

                  switch (pdu->resp_flag) {
                  case 1:
                        if (e->controlRequestCallback) {
                              (e->controlRequestCallback)( (dis_pdu *) pdu, 
                                                                         e->callbackData );
                        }
                        e->local = 1;
                        e->state = DIS_ENTITY_STATE_SIMULATING;
                        e->pending_state = DIS_ENTITY_STATE_NONE;
                        break;

                  case 5:
                        if (e->controlRequestCallback) {
                              (e->controlRequestCallback)( (dis_pdu *) pdu, 
                                                                         e->callbackData );
                        }
                        break;

                  default:
                        printf( "Unrecognized response flag in Acknowledge PDU: %d\n",
                                    pdu->resp_flag );
                        break;
                  }

                  e->controlRequestCallback = NULL;
                  e->callbackData = NULL;
            }
      }

      return 0;
}

int
dis_requestControl (Entity_t *e, 
                              int (*callbackFn)(dis_pdu*, void *), 
                              void *arg)
{
      dis_pdu pdu;
      int status;
      OutstandingRequestInfo_t *preq;

      memset ( &pdu, 0, sizeof(pdu) );

      if (e->local == -1 ||
            e->local == 1) {
            return -1;
      }

      e->callbackData = arg;
      e->controlRequestCallback = callbackFn;

      pdu.hdr.pdu_type = PDUTypeTransferControl;
      pdu.hdr.time_stamp = timeDoubleToDIS(theTime, absoluteTime);

      pdu.transfer_control.request_id = DISxIssueRequestID ( app );

      pdu.transfer_control.orig_id.sim_id.site_id = site;
      pdu.transfer_control.orig_id.sim_id.application_id = application;
      pdu.transfer_control.orig_id.entity_id = NO_ENTITY;

      pdu.transfer_control.recv_id.sim_id = e->entityId.sim_id;
      pdu.transfer_control.recv_id.entity_id = NO_ENTITY;

      pdu.transfer_control.target_id = e->entityId;

      pdu.transfer_control.reliability_service = 1;
      pdu.transfer_control.num_record_sets = 0;
      pdu.transfer_control.transfer_type = DISTransferTypeEntityRequest;

      /*
       *  Add tracking information so that we know about this
       *  outstanding request.
       */

      preq = addRequest ( pdu.transfer_control.request_id );
      if (preq) {
            preq->request_type = OUTSTANDING_REQUEST_TRANSFER_CONTROL;
            preq->e = e;
            /* requests can timeout, but we don't track that, yet */
            preq->timeout_time = theTime + 5.0;
      }

      status = DISxWritePDU( app, & pdu );
      return (status == 0) ? 0 : -1;
}

int
setDataPDU (dis_set_data_pdu *pdu)
{
      Entity_t     *e;
      int          eid;
      dis_data_pdu reply_pdu;
      unsigned int i;
      int status;

      reply_pdu.hdr     = pdu->hdr;
      reply_pdu.hdr.pdu_type   = PDUTypeData;
      reply_pdu.hdr.time_stamp = timeDoubleToDIS( theTime, absoluteTime );

      reply_pdu.orig_id = pdu->recv_id;
      reply_pdu.recv_id = pdu->orig_id;
      reply_pdu.request_id  = pdu->request_id;

      DISInitializeDatumInfo (&reply_pdu.datum_info);

      eid = findEntity(&pdu->recv_id);
      if (eid < 0) {
            return -1;
      }
      else {
            e = &entities[eid];
      }

      for (i=0; i<pdu->datum_info.num_fixed_data; ++i) {
            switch ( pdu->datum_info.variable_datum[i].datum_id ) {
            case DatumOrientationX:
                  break;
            case DatumOrientationY:
                  break;
            case DatumOrientationZ:
                  break;

            case DatumXVelocity:
                  break;
            case DatumYVelocity:
                  break;
            case DatumZVelocity:
                  break;
            }
      }

      for (i=0; i<pdu->datum_info.num_variable_data; ++i) {
            switch ( pdu->datum_info.variable_datum[i].datum_id ) {
            case DatumGeocentricCoordinatesX:
                  break;
            case DatumGeocentricCoordinatesY:
                  break;
            case DatumGeocentricCoordinatesZ:
                  break;
            }
      }

      /*
       * Send reply
       */

      status = DISxWritePDU(app, (dis_pdu *) & reply_pdu);
      return (status == 0) ? 0 : -1;
}

int
stopPDU (dis_stop_pdu *pdu)
{
      Entity_t     *e;
      int          eid;
      int          status = 0;
      int          need_reply = 1;
      int          all_local_entities = 0;
      dis_acknowledge_pdu reply_pdu;
      struct timeval tv;
      double changeTime;

      reply_pdu.hdr     = pdu->hdr;
      reply_pdu.hdr.pdu_type   = PDUTypeAcknowledge;
      reply_pdu.hdr.time_stamp = timeDoubleToDIS( theTime, absoluteTime );

      reply_pdu.orig_id = pdu->recv_id;
      reply_pdu.recv_id = pdu->orig_id;

      reply_pdu.request_id  = pdu->request_id;
      reply_pdu.acknowledge_flag = DISAcknowledgeFlagStop;
      reply_pdu.resp_flag = DISRequestStatusComplete;

      if (pdu->recv_id.sim_id.site_id == ALL_SITES) {
            all_local_entities = 1;
      }
      else if (pdu->recv_id.sim_id.site_id == site &&
                 pdu->recv_id.sim_id.application_id == ALL_APPLIC) {
            all_local_entities = 1;
      }
      else if (pdu->recv_id.sim_id.site_id == site &&
                 pdu->recv_id.sim_id.application_id == application &&
                   pdu->recv_id.entity_id == ALL_ENTITIES) {
            all_local_entities = 1;
      }

      DISTimeToTimeval( &pdu->real_time, &tv );
      changeTime = tv.tv_sec + tv.tv_usec / 1000000.0;

      if ( all_local_entities ) {
            int i;
            
            e = &entities[0];
            for ( i=0; i<entity_top; ++i, ++e ) {
                  
                  if (e->local != 1) {
                        continue;
                  }
                  
                  if ( changeTime <= theTime ) {
                        e->state = DIS_ENTITY_STATE_STOPPED;
                  }
                  else {
                        e->pending_state = DIS_ENTITY_STATE_STOPPED;
                        e->pending_time = changeTime;
                  }
                  
                  if ( pdu->behavior & DISFrozenBehaviorRunClock ) {
                  }
            
                  if ( pdu->behavior & DISFrozenBehaviorTransmit ) {
                        e->emit_while_frozen = 1;
                  }
            
                  if ( pdu->behavior & DISFrozenBehaviorReceive ) {
                  }
            }
      }
      else {

            eid = findEntity(&pdu->recv_id);
            if (eid < 0) {
                  /* cannot comply, entity not found */
                  reply_pdu.orig_id.entity_id = NO_ENTITY;
                  reply_pdu.resp_flag = DISRequestStatusOther;
            }
            else {
                  e = &entities[eid];

                  if (e->local == 1) {

                        if ( changeTime <= theTime ) {
                              e->state = DIS_ENTITY_STATE_STOPPED;
                        }
                        else {
                              e->pending_state = DIS_ENTITY_STATE_STOPPED;
                              e->pending_time = changeTime;
                        }
                  
                        if ( pdu->behavior & DISFrozenBehaviorRunClock ) {
                        }
                  
                        if ( pdu->behavior & DISFrozenBehaviorTransmit ) {
                              e->emit_while_frozen = 1;
                        }
                  
                        if ( pdu->behavior & DISFrozenBehaviorReceive ) {
                        }
                  }
                  else {
                        need_reply = 0;
                  }
            }
      }
      
      /*
       * Send reply
       */
      if (need_reply) {
            status = DISxWritePDU(app, (dis_pdu *) & reply_pdu);
      }
      return (status == 0) ? 0 : -1;
}

int
startPDU (dis_start_pdu *pdu)
{
      Entity_t     *e;
      int          eid;
      int          status = 0;
      int          need_reply = 1;
      int          all_local_entities = 0;
      dis_acknowledge_pdu reply_pdu;
      struct timeval tv;
      double changeTime;

      reply_pdu.hdr     = pdu->hdr;
      reply_pdu.hdr.pdu_type   = PDUTypeAcknowledge;
      reply_pdu.hdr.time_stamp = timeDoubleToDIS( theTime, absoluteTime );

      reply_pdu.orig_id = pdu->recv_id;
      reply_pdu.recv_id = pdu->orig_id;

      reply_pdu.request_id  = pdu->request_id;
      reply_pdu.acknowledge_flag = DISAcknowledgeFlagStop;
      reply_pdu.resp_flag = DISRequestStatusComplete;

      if (pdu->recv_id.sim_id.site_id == ALL_SITES) {
            all_local_entities = 1;
      }
      else if (pdu->recv_id.sim_id.site_id == site &&
                 pdu->recv_id.sim_id.application_id == ALL_APPLIC) {
            all_local_entities = 1;
      }
      else if (pdu->recv_id.sim_id.site_id == site &&
                 pdu->recv_id.sim_id.application_id == application &&
                   pdu->recv_id.entity_id == ALL_ENTITIES) {
            all_local_entities = 1;
      }

      DISTimeToTimeval( &pdu->real_time, &tv );
      changeTime = tv.tv_sec + tv.tv_usec / 1000000.0;

      if ( all_local_entities ) {
            int i;

            e = &entities[0];
            for ( i=0; i<entity_top; ++i, ++e ) {

                  if (e->local != 1) {
                        continue;
                  }

                  if ( changeTime <= theTime ) {
                        e->state = DIS_ENTITY_STATE_SIMULATING;
                  }
                  else {
                        e->pending_state = DIS_ENTITY_STATE_SIMULATING;
                        e->pending_time = changeTime;
                  }

                  e->emit_while_frozen = 0;
            }
      }
      else {
            eid = findEntity( &pdu->recv_id );
            if (eid < 0) {
                  /* cannot comply. entity not found */
                  reply_pdu.orig_id.entity_id = NO_ENTITY;
                  reply_pdu.resp_flag = DISRequestStatusOther;
            }
            else {
                  e = &entities[eid];

                  /*
                   * Is entity local ?
                   */

                  if ( e->local == 1 ) {

                        if ( changeTime <= theTime ) {
                              e->state = DIS_ENTITY_STATE_SIMULATING;
                        }
                        else {
                              e->pending_state = DIS_ENTITY_STATE_SIMULATING;
                              e->pending_time = changeTime;
                        }

                        e->emit_while_frozen = 0;
                  }
                  else {
                        need_reply = 0;
                  }
            }
      }

      /*
       * Send reply
       */

      if (need_reply) {
            status = DISxWritePDU(app, (dis_pdu *) & reply_pdu);
      }
      return (status == 0) ? 0 : -1;
}

/*
 *  dis_canSimulate ( int eid )
 *
 *  Returns 1 (TRUE) if the entity is local and it is int the simulating state
 */

int
dis_canSimulate ( int eid )
{
      Entity_t *e;

      if ( eid >= 0 || eid <= entity_top ) {
            e = &entities[eid];
            if ( e->local == 1 && e->state == DIS_ENTITY_STATE_SIMULATING ) {
                  return 1;
            }
      }

      return 0;
}

/*
 *  dis_shouldTransmitPDUs ( Entity_t *e )
 *
 *  Determines if an entity should emit entity state PDUs based on 
 *  protocol rules
 */

int
dis_shouldTransmitPDUs ( Entity_t *e )
{
      int result = 0;

      if (e->local == 1) {
            if ( e->state == DIS_ENTITY_STATE_SIMULATING ) {
                  result = 1;
            }
            else if ( e->state == DIS_ENTITY_STATE_STOPPED && 
                          e->emit_while_frozen ) {
                  result = 1;
            }
      }

      return result;
}

/*
 *  d i s _ s n o o p
 *
 *  Process incoming PDUs for the specified number of milliseconds, thus
 *  priming the enity table.  This is called only during program
 *  initialization, when a complete entity table is required.
 */

int
dis_snoop ( int millisec )
{
      int interval_millisec = 500;

      if (disInUse) {
            if (disAbsoluteTime)
                  dis_setTimeAbsolute();
            else
                  dis_setTime(curTime);
            dis_receive();
      }

      dis_receive ();

      while ( millisec > 0 ) {
            if ( millisec < interval_millisec ) {
                  interval_millisec = millisec;
            }
            usleep ( interval_millisec * 1000 );

            updateSimTimeFromSystemClock ();

            if (disInUse) {
                  if (disAbsoluteTime)
                        dis_setTimeAbsolute();
                  else
                        dis_setTime(curTime);
                  dis_receive(); 
            }

            dis_receive ();
            millisec -= interval_millisec;
      }

      return 0;
}  




Generated by  Doxygen 1.6.0   Back to index