]> git.mxchange.org Git - flightgear.git/blobdiff - src/Model/model.cxx
Added explicit std:: prefix to map to work-around MSVC6 bug reported
[flightgear.git] / src / Model / model.cxx
index 5439230acc3c02ce083b3cc33eb50738147e3931..84fdc229c2a84b98e69fae072895408a65d99613 100644 (file)
@@ -7,22 +7,26 @@
 #  include <config.h>
 #endif
 
-#include <string.h>            // for strcmp()
+#include <string.h>             // for strcmp()
 
 #include <plib/sg.h>
 #include <plib/ssg.h>
 
 #include <simgear/compiler.h>
 #include <simgear/debug/logstream.hxx>
+#include <simgear/math/interpolater.hxx>
 #include <simgear/math/point3d.hxx>
 #include <simgear/math/sg_geodesy.hxx>
 #include <simgear/misc/exception.hxx>
 #include <simgear/misc/sg_path.hxx>
 
+#include <Main/fg_props.hxx>
 #include <Main/globals.hxx>
+#include <Main/location.hxx>
 #include <Scenery/scenery.hxx>
 
 #include "model.hxx"
+#include "panelnode.hxx"
 
 
 \f
 // Static utility functions.
 ////////////////////////////////////////////////////////////////////////
 
+/**
+ * Callback to update an animation.
+ */
+static int
+animation_callback (ssgEntity * entity, int mask)
+{
+    ((Animation *)entity->getUserData())->update();
+    return true;
+}
+
+
 /**
  * Locate a named SSG node in a branch.
  */
@@ -43,9 +58,9 @@ find_named_node (ssgEntity * node, const char * name)
     int nKids = node->getNumKids();
     for (int i = 0; i < nKids; i++) {
       ssgEntity * result =
-       find_named_node(((ssgBranch*)node)->getKid(i), name);
+        find_named_node(((ssgBranch*)node)->getKid(i), name);
       if (result != 0)
-       return result;
+        return result;
     }
   } 
   return 0;
@@ -70,7 +85,7 @@ splice_branch (ssgBranch * branch, ssgEntity * child)
  */
 static void
 set_rotation (sgMat4 &matrix, double position_deg,
-             sgVec3 &center, sgVec3 &axis)
+              sgVec3 &center, sgVec3 &axis)
 {
  float temp_angle = -position_deg * SG_DEGREES_TO_RADIANS ;
  
@@ -121,554 +136,545 @@ set_translation (sgMat4 &matrix, double position_m, sgVec3 &axis)
   sgMakeTransMat4(matrix, xyz);
 }
 
+
 /**
- * make model transformation Matrix - based on optimizations by NHV
+ * Make an offset matrix from rotations and position offset.
  */
-static void MakeTRANS( sgMat4 dst, const double Theta,
-                       const double Phi, const double Psi, 
-                        const double lon, const double lat)
-{
-    SGfloat cosTheta = (SGfloat) cos(Theta);
-    SGfloat sinTheta = (SGfloat) sin(Theta);
-    SGfloat cosPhi   = (SGfloat) cos(Phi);
-    SGfloat sinPhi   = (SGfloat) sin(Phi);
-    SGfloat sinPsi   = (SGfloat) sin(Psi) ;
-    SGfloat cosPsi   = (SGfloat) cos(Psi) ;
-
-    SGfloat cosLon = SGD_ONE;
-    SGfloat sinLon = SGD_ZERO;
-    SGfloat cosLat = SGD_ONE;
-    SGfloat sinLat = SGD_ZERO;
-
-    if ( lon != SG_ZERO ) { 
-      sinLon = (SGfloat) sin( lon ) ;
-      cosLon = (SGfloat) cos( lon ) ;
-    }
-    if ( lat != SG_ZERO ) {
-      sinLat   = (SGfloat) sin( lat ) ;
-      cosLat   = (SGfloat) cos( lat ) ;
-    }
-
-
-    sgMat4 tmp;
-       
-    tmp[0][0] = cosPhi * cosTheta;
-    tmp[0][1] =        sinPhi * cosPsi + cosPhi * -sinTheta * -sinPsi;
-    tmp[0][2] =        sinPhi * sinPsi + cosPhi * -sinTheta * cosPsi;
-
-    tmp[1][0] = -sinPhi * cosTheta;
-    tmp[1][1] =        cosPhi * cosPsi + -sinPhi * -sinTheta * -sinPsi;
-    tmp[1][2] =        cosPhi * sinPsi + -sinPhi * -sinTheta * cosPsi;
-       
-    tmp[2][0] = sinTheta;
-    tmp[2][1] =        cosTheta * -sinPsi;
-    tmp[2][2] =        cosTheta * cosPsi;
-       
-    float a = cosLon * cosLat;  // world up [0]
-    float b = -sinLon;
-    float c = sinLat * cosLon;
-    dst[2][0] = a*tmp[0][0] + b*tmp[0][1] + c*tmp[0][2] ;
-    dst[1][0] = a*tmp[1][0] + b*tmp[1][1] + c*tmp[1][2] ;
-    dst[0][0] = -(a*tmp[2][0] + b*tmp[2][1] + c*tmp[2][2]) ;
-    dst[3][0] = SG_ZERO ;
-
-    a = cosLat * sinLon;  // world up [1]
-    b = cosLon;
-    c = sinLat * sinLon;
-    dst[2][1] = a*tmp[0][0] + b*tmp[0][1] + c*tmp[0][2] ;
-    dst[1][1] = a*tmp[1][0] + b*tmp[1][1] + c*tmp[1][2] ;
-    dst[0][1] = -(a*tmp[2][0] + b*tmp[2][1] + c*tmp[2][2]) ;
-    dst[3][1] = SG_ZERO ;
-
-    a = -sinLat;  // world up [2]
-    c = cosLat;
-    dst[2][2] = a*tmp[0][0] + c*tmp[0][2] ;
-    dst[1][2] = a*tmp[1][0] + c*tmp[1][2] ;
-    dst[0][2] = -(a*tmp[2][0] + c*tmp[2][2]) ;
-    dst[3][2] = SG_ZERO ;
-
-    dst[2][3] = SG_ZERO ;
-    dst[1][3] = SG_ZERO ;
-    dst[0][3] = SG_ZERO ;
-    dst[3][3] = SG_ONE ;
-
-}
-
-
-// TODO: once this is working, look at Norm's optimized version
 static void
-world_coordinate( sgCoord *obj_pos,
-                 double lat_deg, double lon_deg, double elev_ft,
-                 double roll_deg, double pitch_deg, double hdg_deg)
-{
-  // setup translation
-  double sea_level_radius_m;
-  double lat_geoc_rad;
-
-  // Convert from geodetic to geocentric
-  // coordinates.
-  sgGeodToGeoc(lat_deg * SGD_DEGREES_TO_RADIANS,
-              elev_ft * SG_FEET_TO_METER,
-              &sea_level_radius_m,
-              &lat_geoc_rad);
-
-  Point3D center = scenery.get_center();
-
-  Point3D geod = Point3D(lon_deg * SG_DEGREES_TO_RADIANS,
-                        lat_geoc_rad,
-                        sea_level_radius_m);
-  geod.setz(geod.radius() + elev_ft * SG_FEET_TO_METER);
-       
-  Point3D world_pos = sgPolarToCart3d( geod );
-  Point3D offset = world_pos - center;
-       
-  sgMat4 POS;
-  sgMakeTransMat4( POS, offset.x(), offset.y(), offset.z() );
+make_offsets_matrix (sgMat4 * result, double h_rot, double p_rot, double r_rot,
+                    double x_off, double y_off, double z_off)
+{
+  sgMat4 rot_matrix;
+  sgMat4 pos_matrix;
+  sgMakeRotMat4(rot_matrix, h_rot, p_rot, r_rot);
+  sgMakeTransMat4(pos_matrix, x_off, y_off, z_off);
+  sgMultMat4(*result, pos_matrix, rot_matrix);
+}
 
-  sgMat4 TRANS;
 
-  MakeTRANS( TRANS, pitch_deg * SG_DEGREES_TO_RADIANS, 
-                    roll_deg * SG_DEGREES_TO_RADIANS,
-                    -hdg_deg * SG_DEGREES_TO_RADIANS, 
-                    lon_deg * SG_DEGREES_TO_RADIANS, 
-                    -lat_deg * SG_DEGREES_TO_RADIANS );
+/**
+ * Read an interpolation table from properties.
+ */
+static SGInterpTable *
+read_interpolation_table (const SGPropertyNode * props)
+{
+  const SGPropertyNode * table_node = props->getNode("interpolation");
+  if (table_node != 0) {
+    SGInterpTable * table = new SGInterpTable();
+    vector<SGPropertyNode_ptr> entries = table_node->getChildren("entry");
+    for (unsigned int i = 0; i < entries.size(); i++)
+      table->addEntry(entries[i]->getDoubleValue("ind", 0.0),
+                     entries[i]->getDoubleValue("dep", 0.0));
+    return table;
+  } else {
+    return 0;
+  }
+}
+
 
-  sgPostMultMat4( TRANS, POS );
+static void
+make_animation (ssgBranch * model,
+                const char * object_name,
+                SGPropertyNode * node)
+{
+  Animation * animation = 0;
+  const char * type = node->getStringValue("type");
+  if (!strcmp("none", type)) {
+    animation = new NullAnimation(node);
+  } else if (!strcmp("range", type)) {
+    animation = new RangeAnimation(node);
+  } else if (!strcmp("billboard", type)) {
+    animation = new BillboardAnimation(node);
+  } else if (!strcmp("select", type)) {
+    animation = new SelectAnimation(node);
+  } else if (!strcmp("spin", type)) {
+    animation = new SpinAnimation(node);
+  } else if (!strcmp("rotate", type)) {
+    animation = new RotateAnimation(node);
+  } else if (!strcmp("translate", type)) {
+    animation = new TranslateAnimation(node);
+  } else {
+    animation = new NullAnimation(node);
+    SG_LOG(SG_INPUT, SG_WARN, "Unknown animation type " << type);
+  }
 
-  sgSetCoord( obj_pos, TRANS );
+  ssgEntity * object;
+  if (object_name != 0) {
+    object = find_named_node(model, object_name);
+    if (object == 0) {
+      SG_LOG(SG_INPUT, SG_WARN, "Object " << object_name << " not found");
+      delete animation;
+      animation = 0;
+    }
+  } else {
+    object = model;
+  }
+  
+  ssgBranch * branch = animation->getBranch();
+  splice_branch(branch, object);
+  branch->setUserData(animation);
+  branch->setTravCallback(SSG_CALLBACK_PRETRAV, animation_callback);
 }
 
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of FG3DModel
+// Global functions.
 ////////////////////////////////////////////////////////////////////////
 
-FG3DModel::FG3DModel ()
-  : _model(0),
-    _selector(new ssgSelector),
-    _position(new ssgTransform)
-{
-}
-
-FG3DModel::~FG3DModel ()
-{
-  // since the nodes are attached to the scene graph, they'll be
-  // deleted automatically
-
-  for (int i = 0; i < _animations.size(); i++) {
-    Animation * tmp = _animations[i];
-    _animations[i] = 0;
-    delete tmp;
-  }
-
-}
-
-void 
-FG3DModel::init (const string &path)
+ssgBranch *
+fgLoad3DModel (const string &path)
 {
+  ssgBranch * model = 0;
   SGPropertyNode props;
 
-                               // Load the 3D aircraft object itself
+                                // Load the 3D aircraft object itself
   SGPath xmlpath = globals->get_fg_root();
   SGPath modelpath = path;
   xmlpath.append(modelpath.str());
 
-                               // Check for an XML wrapper
+                                // Check for an XML wrapper
   if (xmlpath.str().substr(xmlpath.str().size() - 4, 4) == ".xml") {
     readProperties(xmlpath.str(), &props);
     if (props.hasValue("/path")) {
       modelpath = modelpath.dir();
       modelpath.append(props.getStringValue("/path"));
     } else {
-      throw sg_exception("No path for model");
+      if (model == 0)
+       model = new ssgBranch;
     }
   }
 
-                               // Assume that textures are in
-                               // the same location as the XML file.
-  ssgTexturePath((char *)xmlpath.dir().c_str());
-  _model = ssgLoad((char *)modelpath.c_str());
-  if (_model == 0)
-    throw sg_exception("Failed to load 3D model");
-
-                               // Load animations
-  vector<SGPropertyNode *> animation_nodes = props.getChildren("animation");
-  for (unsigned int i = 0; i < animation_nodes.size(); i++) {
-    vector<SGPropertyNode *> name_nodes =
+                                // Assume that textures are in
+                                // the same location as the XML file.
+  if (model == 0) {
+    ssgTexturePath((char *)xmlpath.dir().c_str());
+    model = (ssgBranch *)ssgLoad((char *)modelpath.c_str());
+    if (model == 0)
+      throw sg_exception("Failed to load 3D model");
+  }
+
+                                // Set up the alignment node
+  ssgTransform * align = new ssgTransform;
+  align->addKid(model);
+  sgMat4 res_matrix;
+  make_offsets_matrix(&res_matrix,
+                     props.getFloatValue("/offsets/heading-deg", 0.0),
+                     props.getFloatValue("/offsets/roll-deg", 0.0),
+                     props.getFloatValue("/offsets/pitch-deg", 0.0),
+                     props.getFloatValue("/offsets/x-m", 0.0),
+                     props.getFloatValue("/offsets/y-m", 0.0),
+                     props.getFloatValue("/offsets/z-m", 0.0));
+  align->setTransform(res_matrix);
+
+                                // Load animations
+  vector<SGPropertyNode_ptr> animation_nodes = props.getChildren("animation");
+  unsigned int i;
+  for (i = 0; i < animation_nodes.size(); i++) {
+    vector<SGPropertyNode_ptr> name_nodes =
       animation_nodes[i]->getChildren("object-name");
     if (name_nodes.size() < 1) {
-      SG_LOG(SG_INPUT, SG_ALERT, "No object-name given for transformation");
+        make_animation(model, 0, animation_nodes[i]);
     } else {
       for (unsigned int j = 0; j < name_nodes.size(); j++) {
-       Animation * animation =
-         make_animation(name_nodes[j]->getStringValue(), animation_nodes[i]);
-       if (animation != 0)
-         _animations.push_back(animation);
+        make_animation(model, name_nodes[j]->getStringValue(), animation_nodes[i]);
       }
     }
   }
 
-                               // Set up the alignment node
-  ssgTransform * align = new ssgTransform;
-  align->addKid(_model);
-  sgMat4 rot_matrix;
-  sgMat4 off_matrix;
-  sgMat4 res_matrix;
-  float h_rot = props.getFloatValue("/offsets/heading-deg", 0.0);
-  float p_rot = props.getFloatValue("/offsets/roll-deg", 0.0);
-  float r_rot = props.getFloatValue("/offsets/pitch-deg", 0.0);
-  float x_off = props.getFloatValue("/offsets/x-m", 0.0);
-  float y_off = props.getFloatValue("/offsets/y-m", 0.0);
-  float z_off = props.getFloatValue("/offsets/z-m", 0.0);
-  sgMakeRotMat4(rot_matrix, h_rot, p_rot, r_rot);
-  sgMakeTransMat4(off_matrix, x_off, y_off, z_off);
-  sgMultMat4(res_matrix, off_matrix, rot_matrix);
-  align->setTransform(res_matrix);
+                                // Load panels
+  vector<SGPropertyNode_ptr> panel_nodes = props.getChildren("panel");
+  for (i = 0; i < panel_nodes.size(); i++) {
+    printf("Reading a panel in model.cxx\n");
+    FGPanelNode * panel = new FGPanelNode(panel_nodes[i]);
+    model->addKid(panel);
+  }
 
-                               // Set up the position node
-  _position->addKid(align);
+                               // Load sub-models
+  vector<SGPropertyNode_ptr> model_nodes = props.getChildren("model");
+  for (i = 0; i < model_nodes.size(); i++) {
+    SGPropertyNode_ptr node = model_nodes[i];
+    ssgTransform * align = new ssgTransform;
+    sgMat4 res_matrix;
+    make_offsets_matrix(&res_matrix,
+                       node->getFloatValue("offsets/heading-deg", 0.0),
+                       node->getFloatValue("offsets/roll-deg", 0.0),
+                       node->getFloatValue("offsets/pitch-deg", 0.0),
+                       node->getFloatValue("offsets/x-m", 0.0),
+                       node->getFloatValue("offsets/y-m", 0.0),
+                       node->getFloatValue("offsets/z-m", 0.0));
+    align->setTransform(res_matrix);
+
+    ssgBranch * kid = fgLoad3DModel(node->getStringValue("path"));
+    align->addKid(kid);
+    model->addKid(align);
+  }
 
-                               // Set up the selector node
-  _selector->addKid(_position);
-  _selector->clrTraversalMaskBits(SSGTRAV_HOT);
+  return model;
 }
 
-void
-FG3DModel::update (int dt)
-{
-  for (unsigned int i = 0; i < _animations.size(); i++)
-    _animations[i]->update(dt);
-
-  _selector->select(true);
 
-  sgCoord obj_pos;
-  world_coordinate(&obj_pos, _lat_deg, _lon_deg, _elev_ft,
-                  _roll_deg, _pitch_deg, _heading_deg);
-  _position->setTransform(&obj_pos);
-}
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of Animation
+////////////////////////////////////////////////////////////////////////
 
-bool
-FG3DModel::getVisible () const
+Animation::Animation (SGPropertyNode_ptr props, ssgBranch * branch)
+    : _branch(branch)
 {
-  return _selector->getSelect();
+    _branch->setName(props->getStringValue("name", 0));
 }
 
-void
-FG3DModel::setVisible (bool visible)
+Animation::~Animation ()
 {
-  _selector->select(visible);
 }
 
-void
-FG3DModel::setPosition (double lon_deg, double lat_deg, double elev_ft)
+
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of NullAnimation
+////////////////////////////////////////////////////////////////////////
+
+NullAnimation::NullAnimation (SGPropertyNode_ptr props)
+  : Animation(props, new ssgBranch)
 {
-  _lon_deg = lon_deg;
-  _lat_deg = lat_deg;
-  _elev_ft = elev_ft;
 }
 
-void
-FG3DModel::setOrientation (double roll_deg, double pitch_deg,
-                          double heading_deg)
+NullAnimation::~NullAnimation ()
 {
-  _roll_deg = roll_deg;
-  _pitch_deg = pitch_deg;
-  _heading_deg = heading_deg;
 }
 
-FG3DModel::Animation *
-FG3DModel::make_animation (const char * object_name,
-                                SGPropertyNode * node)
+void
+NullAnimation::update ()
 {
-  Animation * animation = 0;
-  const char * type = node->getStringValue("type");
-  if (!strcmp("none", type)) {
-    animation = new NullAnimation();
-  } else if (!strcmp("select", type)) {
-    animation = new SelectAnimation();
-  } else if (!strcmp("spin", type)) {
-    animation = new SpinAnimation();
-  } else if (!strcmp("rotate", type)) {
-    animation = new RotateAnimation();
-  } else if (!strcmp("translate", type)) {
-    animation = new TranslateAnimation();
-  } else {
-    animation = new NullAnimation();
-    SG_LOG(SG_INPUT, SG_WARN, "Unknown animation type " << type);
-  }
-
-  ssgEntity * object = find_named_node(_model, object_name);
-  if (object == 0) {
-    SG_LOG(SG_INPUT, SG_WARN, "Object " << object_name << " not found");
-    delete animation;
-    animation = 0;
-  } else {
-    animation->init(object, node);
-  }
-
-  return animation;
 }
 
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of FG3DModel::Animation
+// Implementation of RangeAnimation
 ////////////////////////////////////////////////////////////////////////
 
-FG3DModel::Animation::Animation ()
+RangeAnimation::RangeAnimation (SGPropertyNode_ptr props)
+  : Animation(props, new ssgRangeSelector)
 {
+    float ranges[] = { props->getFloatValue("min-m", 0),
+                       props->getFloatValue("max-m", 5000) };
+    ((ssgRangeSelector *)_branch)->setRanges(ranges, 2);
+                       
 }
 
-FG3DModel::Animation::~Animation ()
+RangeAnimation::~RangeAnimation ()
+{
+}
+
+void
+RangeAnimation::update ()
 {
 }
 
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of FG3DModel::NullAnimation
+// Implementation of BillboardAnimation
 ////////////////////////////////////////////////////////////////////////
 
-FG3DModel::NullAnimation::NullAnimation ()
-  : _branch(new ssgBranch)
+BillboardAnimation::BillboardAnimation (SGPropertyNode_ptr props)
+    : Animation(props, new ssgCutout(props->getBoolValue("spherical", true)))
 {
 }
 
-FG3DModel::NullAnimation::~NullAnimation ()
+BillboardAnimation::~BillboardAnimation ()
 {
-  _branch = 0;
 }
 
 void
-FG3DModel::NullAnimation::init (ssgEntity * object,
-                                     SGPropertyNode * props)
-{
-  splice_branch(_branch, object);
-  _branch->setName(props->getStringValue("name", 0));
-}
-
-void
-FG3DModel::NullAnimation::update (int dt)
+BillboardAnimation::update ()
 {
 }
 
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of FG3DModel::SelectAnimation
+// Implementation of SelectAnimation
 ////////////////////////////////////////////////////////////////////////
 
-FG3DModel::SelectAnimation::SelectAnimation ()
-  : _condition(0),
-    _selector(new ssgSelector)
+SelectAnimation::SelectAnimation (SGPropertyNode_ptr props)
+  : Animation(props, new ssgSelector),
+    _condition(0)
 {
+  SGPropertyNode * node = props->getChild("condition");
+  if (node != 0)
+    _condition = fgReadCondition(node);
 }
 
-FG3DModel::SelectAnimation::~SelectAnimation ()
+SelectAnimation::~SelectAnimation ()
 {
   delete _condition;
-  _selector = 0;
-}
-
-void
-FG3DModel::SelectAnimation::init (ssgEntity * object,
-                                     SGPropertyNode * props)
-{
-  splice_branch(_selector, object);
-  _selector->setName(props->getStringValue("name", 0));
-  SGPropertyNode * node = props->getChild("condition");
-  if (node != 0) {
-    _condition = fgReadCondition(node);
-  }
 }
 
 void
-FG3DModel::SelectAnimation::update (int dt)
+SelectAnimation::update ()
 {
   if (_condition != 0 && _condition->test()) 
-    _selector->select(0xffff);
+      ((ssgSelector *)_branch)->select(0xffff);
   else
-    _selector->select(0x0000);
+      ((ssgSelector *)_branch)->select(0x0000);
 }
 
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of FG3DModel::SpinAnimation
+// Implementation of SpinAnimation
 ////////////////////////////////////////////////////////////////////////
 
-FG3DModel::SpinAnimation::SpinAnimation ()
-  : _prop(0),
-    _factor(0),
-    _position_deg(0),
-    _transform(new ssgTransform)
+SpinAnimation::SpinAnimation (SGPropertyNode_ptr props)
+  : Animation(props, new ssgTransform),
+    _prop(fgGetNode(props->getStringValue("property", "/null"), true)),
+    _factor(props->getDoubleValue("factor", 1.0)),
+    _position_deg(props->getDoubleValue("starting-position-deg", 0)),
+    _last_time_sec(globals->get_sim_time_sec())
 {
+    _center[0] = props->getFloatValue("center/x-m", 0);
+    _center[1] = props->getFloatValue("center/y-m", 0);
+    _center[2] = props->getFloatValue("center/z-m", 0);
+    _axis[0] = props->getFloatValue("axis/x", 0);
+    _axis[1] = props->getFloatValue("axis/y", 0);
+    _axis[2] = props->getFloatValue("axis/z", 0);
+    sgNormalizeVec3(_axis);
 }
 
-FG3DModel::SpinAnimation::~SpinAnimation ()
+SpinAnimation::~SpinAnimation ()
 {
-  _transform = 0;
-}
-
-void
-FG3DModel::SpinAnimation::init (ssgEntity * object,
-                                     SGPropertyNode * props)
-{
-                               // Splice in the new transform node
-  splice_branch(_transform, object);
-  _transform->setName(props->getStringValue("name", 0));
-  _prop = fgGetNode(props->getStringValue("property", "/null"), true);
-  _factor = props->getDoubleValue("factor", 1.0);
-  _position_deg = props->getDoubleValue("starting-position-deg", 0);
-  _center[0] = props->getFloatValue("center/x-m", 0);
-  _center[1] = props->getFloatValue("center/y-m", 0);
-  _center[2] = props->getFloatValue("center/z-m", 0);
-  _axis[0] = props->getFloatValue("axis/x", 0);
-  _axis[1] = props->getFloatValue("axis/y", 0);
-  _axis[2] = props->getFloatValue("axis/z", 0);
-  sgNormalizeVec3(_axis);
 }
 
 void
-FG3DModel::SpinAnimation::update (int dt)
+SpinAnimation::update ()
 {
-  float velocity_rpms = (_prop->getDoubleValue() * _factor / 60000.0);
+  double sim_time = globals->get_sim_time_sec();
+  double dt = sim_time - _last_time_sec;
+  _last_time_sec = sim_time;
+
+  float velocity_rpms = (_prop->getDoubleValue() * _factor / 60.0);
   _position_deg += (dt * velocity_rpms * 360);
   while (_position_deg < 0)
     _position_deg += 360.0;
   while (_position_deg >= 360.0)
     _position_deg -= 360.0;
   set_rotation(_matrix, _position_deg, _center, _axis);
-  _transform->setTransform(_matrix);
+  ((ssgTransform *)_branch)->setTransform(_matrix);
 }
 
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of FG3DModel::RotateAnimation
+// Implementation of RotateAnimation
 ////////////////////////////////////////////////////////////////////////
 
-FG3DModel::RotateAnimation::RotateAnimation ()
-  : _prop(0),
-    _offset_deg(0.0),
-    _factor(1.0),
-    _has_min(false),
-    _min_deg(0.0),
-    _has_max(false),
-    _max_deg(1.0),
-    _position_deg(0.0),
-    _transform(new ssgTransform)
+RotateAnimation::RotateAnimation (SGPropertyNode_ptr props)
+    : Animation(props, new ssgTransform),
+      _prop(fgGetNode(props->getStringValue("property", "/null"), true)),
+      _offset_deg(props->getDoubleValue("offset-deg", 0.0)),
+      _factor(props->getDoubleValue("factor", 1.0)),
+      _table(read_interpolation_table(props)),
+      _has_min(props->hasValue("min-deg")),
+      _min_deg(props->getDoubleValue("min-deg")),
+      _has_max(props->hasValue("max-deg")),
+      _max_deg(props->getDoubleValue("max-deg")),
+      _position_deg(props->getDoubleValue("starting-position-deg", 0))
 {
+  _center[0] = props->getFloatValue("center/x-m", 0);
+  _center[1] = props->getFloatValue("center/y-m", 0);
+  _center[2] = props->getFloatValue("center/z-m", 0);
+  _axis[0] = props->getFloatValue("axis/x", 0);
+  _axis[1] = props->getFloatValue("axis/y", 0);
+  _axis[2] = props->getFloatValue("axis/z", 0);
+  sgNormalizeVec3(_axis);
 }
 
-FG3DModel::RotateAnimation::~RotateAnimation ()
+RotateAnimation::~RotateAnimation ()
 {
-  _transform = 0;
+  delete _table;
 }
 
 void
-FG3DModel::RotateAnimation::init (ssgEntity * object,
-                                       SGPropertyNode * props)
-{
-                               // Splice in the new transform node
-  splice_branch(_transform, object);
-  _transform->setName(props->getStringValue("name", 0));
-  _prop = fgGetNode(props->getStringValue("property", "/null"), true);
-  _offset_deg = props->getDoubleValue("offset-deg", 0.0);
-  _factor = props->getDoubleValue("factor", 1.0);
-  if (props->hasValue("min-deg")) {
-    _has_min = true;
-    _min_deg = props->getDoubleValue("min-deg");
-  }
-  if (props->hasValue("max-deg")) {
-    _has_max = true;
-    _max_deg = props->getDoubleValue("max-deg");
+RotateAnimation::update ()
+{
+  if (_table == 0) {
+    _position_deg = (_prop->getDoubleValue() + _offset_deg) * _factor;
+   if (_has_min && _position_deg < _min_deg)
+     _position_deg = _min_deg;
+   if (_has_max && _position_deg > _max_deg)
+     _position_deg = _max_deg;
+  } else {
+    _position_deg = _table->interpolate(_prop->getDoubleValue());
   }
-  _position_deg = props->getDoubleValue("starting-position-deg", 0);
-  _center[0] = props->getFloatValue("center/x-m", 0);
-  _center[1] = props->getFloatValue("center/y-m", 0);
-  _center[2] = props->getFloatValue("center/z-m", 0);
+  set_rotation(_matrix, _position_deg, _center, _axis);
+  ((ssgTransform *)_branch)->setTransform(_matrix);
+}
+
+
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of TranslateAnimation
+////////////////////////////////////////////////////////////////////////
+
+TranslateAnimation::TranslateAnimation (SGPropertyNode_ptr props)
+  : Animation(props, new ssgTransform),
+    _prop(fgGetNode(props->getStringValue("property", "/null"), true)),
+    _offset_m(props->getDoubleValue("offset-m", 0.0)),
+    _factor(props->getDoubleValue("factor", 1.0)),
+    _table(read_interpolation_table(props)),
+    _has_min(props->hasValue("min-m")),
+    _min_m(props->getDoubleValue("min-m")),
+    _has_max(props->hasValue("max-m")),
+    _max_m(props->getDoubleValue("max-m")),
+    _position_m(props->getDoubleValue("starting-position-m", 0))
+{
   _axis[0] = props->getFloatValue("axis/x", 0);
   _axis[1] = props->getFloatValue("axis/y", 0);
   _axis[2] = props->getFloatValue("axis/z", 0);
   sgNormalizeVec3(_axis);
 }
 
-void
-FG3DModel::RotateAnimation::update (int dt)
+TranslateAnimation::~TranslateAnimation ()
 {
-  _position_deg = ((_prop->getDoubleValue() + _offset_deg) * _factor);
-  if (_has_min && _position_deg < _min_deg)
-    _position_deg = _min_deg;
-  if (_has_max && _position_deg > _max_deg)
-    _position_deg = _max_deg;
-  set_rotation(_matrix, _position_deg, _center, _axis);
-  _transform->setTransform(_matrix);
+  delete _table;
+}
+
+void
+TranslateAnimation::update ()
+{
+  if (_table == 0) {
+    _position_m = (_prop->getDoubleValue() + _offset_m) * _factor;
+    if (_has_min && _position_m < _min_m)
+      _position_m = _min_m;
+    if (_has_max && _position_m > _max_m)
+      _position_m = _max_m;
+  } else {
+    _position_m = _table->interpolate(_prop->getDoubleValue());
+  }
+  set_translation(_matrix, _position_m, _axis);
+  ((ssgTransform *)_branch)->setTransform(_matrix);
 }
 
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of FG3DModel::TranslateAnimation
+// Implementation of FGModelPlacement.
 ////////////////////////////////////////////////////////////////////////
 
-FG3DModel::TranslateAnimation::TranslateAnimation ()
-  : _prop(0),
-    _offset_m(0.0),
-    _factor(1.0),
-    _has_min(false),
-    _min_m(0.0),
-    _has_max(false),
-    _max_m(1.0),
-    _position_m(0.0),
-    _transform(new ssgTransform)
+FGModelPlacement::FGModelPlacement ()
+  : _lon_deg(0),
+    _lat_deg(0),
+    _elev_ft(0),
+    _roll_deg(0),
+    _pitch_deg(0),
+    _heading_deg(0),
+    _selector(new ssgSelector),
+    _position(new ssgTransform),
+    _location(new FGLocation)
 {
 }
 
-FG3DModel::TranslateAnimation::~TranslateAnimation ()
+FGModelPlacement::~FGModelPlacement ()
 {
-  _transform = 0;
 }
 
 void
-FG3DModel::TranslateAnimation::init (ssgEntity * object,
-                                       SGPropertyNode * props)
-{
-                               // Splice in the new transform node
-  splice_branch(_transform, object);
-  _transform->setName(props->getStringValue("name", 0));
-  _prop = fgGetNode(props->getStringValue("property", "/null"), true);
-  _offset_m = props->getDoubleValue("offset-m", 0.0);
-  _factor = props->getDoubleValue("factor", 1.0);
-  if (props->hasValue("min-m")) {
-    _has_min = true;
-    _min_m = props->getDoubleValue("min-m");
-  }
-  if (props->hasValue("max-m")) {
-    _has_max = true;
-    _max_m = props->getDoubleValue("max-m");
+FGModelPlacement::init (const string &path)
+{
+  ssgBranch * model = fgLoad3DModel(path);
+  if (model != 0)
+      _position->addKid(model);
+  _selector->addKid(_position);
+  _selector->clrTraversalMaskBits(SSGTRAV_HOT);
+}
+
+void
+FGModelPlacement::update ()
+{
+  _location->setPosition( _lon_deg, _lat_deg, _elev_ft );
+  _location->setOrientation( _roll_deg, _pitch_deg, _heading_deg );
+
+  sgMat4 POS;
+  sgCopyMat4(POS, _location->getTransformMatrix());
+  
+  sgVec3 trans;
+  sgCopyVec3(trans, _location->get_view_pos());
+
+  for(int i = 0; i < 4; i++) {
+    float tmp = POS[i][3];
+    for( int j=0; j<3; j++ ) {
+      POS[i][j] += (tmp * trans[j]);
+    }
   }
-  _position_m = props->getDoubleValue("starting-position-m", 0);
-  _axis[0] = props->getFloatValue("axis/x", 0);
-  _axis[1] = props->getFloatValue("axis/y", 0);
-  _axis[2] = props->getFloatValue("axis/z", 0);
-  sgNormalizeVec3(_axis);
+  _position->setTransform(POS);
+}
+
+bool
+FGModelPlacement::getVisible () const
+{
+  return (_selector->getSelect() != 0);
 }
 
 void
-FG3DModel::TranslateAnimation::update (int dt)
+FGModelPlacement::setVisible (bool visible)
 {
-  _position_m = ((_prop->getDoubleValue() + _offset_m) * _factor);
-  if (_has_min && _position_m < _min_m)
-    _position_m = _min_m;
-  if (_has_max && _position_m > _max_m)
-    _position_m = _max_m;
-  set_translation(_matrix, _position_m, _axis);
-  _transform->setTransform(_matrix);
+  _selector->select(visible);
 }
 
+void
+FGModelPlacement::setLongitudeDeg (double lon_deg)
+{
+  _lon_deg = lon_deg;
+}
 
-// end of model.cxx
+void
+FGModelPlacement::setLatitudeDeg (double lat_deg)
+{
+  _lat_deg = lat_deg;
+}
 
+void
+FGModelPlacement::setElevationFt (double elev_ft)
+{
+  _elev_ft = elev_ft;
+}
 
+void
+FGModelPlacement::setPosition (double lon_deg, double lat_deg, double elev_ft)
+{
+  _lon_deg = lon_deg;
+  _lat_deg = lat_deg;
+  _elev_ft = elev_ft;
+}
+
+void
+FGModelPlacement::setRollDeg (double roll_deg)
+{
+  _roll_deg = roll_deg;
+}
+
+void
+FGModelPlacement::setPitchDeg (double pitch_deg)
+{
+  _pitch_deg = pitch_deg;
+}
+
+void
+FGModelPlacement::setHeadingDeg (double heading_deg)
+{
+  _heading_deg = heading_deg;
+}
+
+void
+FGModelPlacement::setOrientation (double roll_deg, double pitch_deg,
+                                 double heading_deg)
+{
+  _roll_deg = roll_deg;
+  _pitch_deg = pitch_deg;
+  _heading_deg = heading_deg;
+}
+
+// end of model.cxx