]> git.mxchange.org Git - simgear.git/blobdiff - simgear/scene/model/animation.cxx
Add preliminary spot light animation
[simgear.git] / simgear / scene / model / animation.cxx
index a068d4c1d722ac455c40c74c1a4c327cb940f1ea..b6748fe036c0bee7df286f530aab5401f1fad7f9 100644 (file)
@@ -3,23 +3,67 @@
 //
 // This file is in the Public Domain, and comes with no warranty.
 
+#ifdef HAVE_CONFIG_H
+#  include <simgear_config.h>
+#endif
 
 #include <string.h>             // for strcmp()
 #include <math.h>
+#include <algorithm>
+#include <functional>
+
+#include <OpenThreads/Mutex>
+#include <OpenThreads/ReentrantMutex>
+#include <OpenThreads/ScopedLock>
+
+#include <osg/AlphaFunc>
+#include <osg/Drawable>
+#include <osg/Geode>
+#include <osg/Geometry>
+#include <osg/LOD>
+#include <osg/Math>
+#include <osg/Object>
+#include <osg/PolygonMode>
+#include <osg/PolygonOffset>
+#include <osg/StateSet>
+#include <osg/Switch>
+#include <osg/TexMat>
+#include <osg/Texture2D>
+#include <osg/Transform>
+#include <osg/Uniform>
+#include <osgDB/ReadFile>
+#include <osgDB/Registry>
+#include <osgDB/Input>
+#include <osgDB/ParameterOutput>
 
-#include <plib/sg.h>
-#include <plib/ssg.h>
-#include <plib/ul.h>
 
 #include <simgear/math/interpolater.hxx>
 #include <simgear/props/condition.hxx>
 #include <simgear/props/props.hxx>
-#include <simgear/math/sg_random.h>
+#include <simgear/structure/SGBinding.hxx>
+#include <simgear/scene/material/EffectGeode.hxx>
+#include <simgear/scene/util/OsgMath.hxx>
+#include <simgear/scene/util/SGNodeMasks.hxx>
+#include <simgear/scene/util/SGSceneUserData.hxx>
+#include <simgear/scene/util/SGStateAttributeVisitor.hxx>
+#include <simgear/scene/util/StateAttributeFactory.hxx>
 
 #include "animation.hxx"
-#include "flash.hxx"
-#include "personality.hxx"
+#include "model.hxx"
 
+#include "SGTranslateTransform.hxx"
+#include "SGMaterialAnimation.hxx"
+#include "SGRotateTransform.hxx"
+#include "SGScaleTransform.hxx"
+#include "SGInteractionAnimation.hxx"
+
+#include "ConditionNode.hxx"
+
+using OpenThreads::Mutex;
+using OpenThreads::ReentrantMutex;
+using OpenThreads::ScopedLock;
+
+using namespace simgear;
 \f
 ////////////////////////////////////////////////////////////////////////
 // Static utility functions.
  * Set up the transform matrix for a spin or rotation.
  */
 static void
-set_rotation (sgMat4 &matrix, double position_deg,
-              sgVec3 &center, sgVec3 &axis)
-{
float temp_angle = -position_deg * SG_DEGREES_TO_RADIANS ;
float s = (float) sin ( temp_angle ) ;
float c = (float) cos ( temp_angle ) ;
float t = SG_ONE - c ;
-
- // axis was normalized at load time 
- // hint to the compiler to put these into FP registers
float x = axis[0];
float y = axis[1];
float z = axis[2];
-
matrix[0][0] = t * x * x + c ;
matrix[0][1] = t * y * x - s * z ;
matrix[0][2] = t * z * x + s * y ;
matrix[0][3] = SG_ZERO;
matrix[1][0] = t * x * y + s * z ;
matrix[1][1] = t * y * y + c ;
matrix[1][2] = t * z * y - s * x ;
matrix[1][3] = SG_ZERO;
matrix[2][0] = t * x * z - s * y ;
matrix[2][1] = t * y * z + s * x ;
matrix[2][2] = t * z * z + c ;
matrix[2][3] = SG_ZERO;
-
+set_rotation (osg::Matrix &matrix, double position_deg,
+              const SGVec3d &center, const SGVec3d &axis)
+{
 double temp_angle = -SGMiscd::deg2rad(position_deg);
+  
 double s = sin(temp_angle);
 double c = cos(temp_angle);
 double t = 1 - c;
+  
 // axis was normalized at load time 
 // hint to the compiler to put these into FP registers
 double x = axis[0];
 double y = axis[1];
 double z = axis[2];
+  
 matrix(0, 0) = t * x * x + c ;
 matrix(0, 1) = t * y * x - s * z ;
 matrix(0, 2) = t * z * x + s * y ;
 matrix(0, 3) = 0;
+  
 matrix(1, 0) = t * x * y + s * z ;
 matrix(1, 1) = t * y * y + c ;
 matrix(1, 2) = t * z * y - s * x ;
 matrix(1, 3) = 0;
+  
 matrix(2, 0) = t * x * z - s * y ;
 matrix(2, 1) = t * y * z + s * x ;
 matrix(2, 2) = t * z * z + c ;
 matrix(2, 3) = 0;
+  
   // hint to the compiler to put these into FP registers
- x = center[0];
- y = center[1];
- z = center[2];
matrix[3][0] = x - x*matrix[0][0] - y*matrix[1][0] - z*matrix[2][0];
matrix[3][1] = y - x*matrix[0][1] - y*matrix[1][1] - z*matrix[2][1];
matrix[3][2] = z - x*matrix[0][2] - y*matrix[1][2] - z*matrix[2][2];
matrix[3][3] = SG_ONE;
 x = center[0];
 y = center[1];
 z = center[2];
+  
 matrix(3, 0) = x - x*matrix(0, 0) - y*matrix(1, 0) - z*matrix(2, 0);
 matrix(3, 1) = y - x*matrix(0, 1) - y*matrix(1, 1) - z*matrix(2, 1);
 matrix(3, 2) = z - x*matrix(0, 2) - y*matrix(1, 2) - z*matrix(2, 2);
 matrix(3, 3) = 1;
 }
 
 /**
  * Set up the transform matrix for a translation.
  */
 static void
-set_translation (sgMat4 &matrix, double position_m, sgVec3 &axis)
+set_translation (osg::Matrix &matrix, double position_m, const SGVec3d &axis)
 {
-  sgVec3 xyz;
-  sgScaleVec3(xyz, axis, position_m);
-  sgMakeTransMat4(matrix, xyz);
+  SGVec3d xyz = axis * position_m;
+  matrix.makeIdentity();
+  matrix(3, 0) = xyz[0];
+  matrix(3, 1) = xyz[1];
+  matrix(3, 2) = xyz[2];
 }
 
 /**
- * Set up the transform matrix for a scale operation.
+ * Read an interpolation table from properties.
  */
-static void
-set_scale (sgMat4 &matrix, double x, double y, double z)
+static SGInterpTable *
+read_interpolation_table(const SGPropertyNode* props)
 {
-  sgMakeIdentMat4( matrix );
-  matrix[0][0] = x;
-  matrix[1][1] = y;
-  matrix[2][2] = z;
+  const SGPropertyNode* table_node = props->getNode("interpolation");
+  if (!table_node)
+    return 0;
+  return new SGInterpTable(table_node);
 }
 
-/**
- * Recursively process all kids to change the alpha values
- */
-static void
-change_alpha( ssgBase *_branch, float _blend )
+static std::string
+unit_string(const char* value, const char* unit)
 {
-  int i;
+  return std::string(value) + unit;
+}
 
-  for (i = 0; i < ((ssgBranch *)_branch)->getNumKids(); i++)
-    change_alpha( ((ssgBranch *)_branch)->getKid(i), _blend );
+class SGPersonalityScaleOffsetExpression : public SGUnaryExpression<double> {
+public:
+  SGPersonalityScaleOffsetExpression(SGExpression<double>* expr,
+                                     SGPropertyNode const* config,
+                                     const std::string& scalename,
+                                     const std::string& offsetname,
+                                     double defScale = 1,
+                                     double defOffset = 0) :
+    SGUnaryExpression<double>(expr),
+    _scale(config, scalename.c_str(), defScale),
+    _offset(config, offsetname.c_str(), defOffset)
+  { }
+  void setScale(double scale)
+  { _scale = scale; }
+  void setOffset(double offset)
+  { _offset = offset; }
+
+  virtual void eval(double& value, const simgear::expression::Binding* b) const
+  {
+    _offset.shuffle();
+    _scale.shuffle();
+    value = _offset + _scale*getOperand()->getValue(b);
+  }
 
-  if ( !_branch->isAKindOf(ssgTypeLeaf())
-       && !_branch->isAKindOf(ssgTypeVtxTable())
-       && !_branch->isAKindOf(ssgTypeVTable()) )
-    return;
+  virtual bool isConst() const { return false; }
 
-  int num_colors = ((ssgLeaf *)_branch)->getNumColours();
-// unsigned int select_ = (_blend == 1.0) ? false : true;
+private:
+  mutable SGPersonalityParameter<double> _scale;
+  mutable SGPersonalityParameter<double> _offset;
+};
 
-  for (i = 0; i < num_colors; i++)
-  {
-//    ((ssgSelector *)_branch)->select( select_ );
-    float *color =  ((ssgLeaf *)_branch)->getColour(i);
-    color[3] = _blend;
-  }
+
+static SGExpressiond*
+read_factor_offset(const SGPropertyNode* configNode, SGExpressiond* expr,
+                   const std::string& factor, const std::string& offset)
+{
+  double factorValue = configNode->getDoubleValue(factor, 1);
+  if (factorValue != 1)
+    expr = new SGScaleExpression<double>(expr, factorValue);
+  double offsetValue = configNode->getDoubleValue(offset, 0);
+  if (offsetValue != 0)
+    expr = new SGBiasExpression<double>(expr, offsetValue);
+  return expr;
 }
 
-/**
- * Modify property value by step and scroll settings in texture translations
- */
-static double
-apply_mods(double property, double step, double scroll)
-{
-
-  double modprop;
-  if(step > 0) {
-    double scrollval = 0.0;
-    if(scroll > 0) {
-      // calculate scroll amount (for odometer like movement)
-      double remainder  =  step - fmod(fabs(property), step);
-      if (remainder < scroll) {
-        scrollval = (scroll - remainder) / scroll * step;
-      }
-    }
-  // apply stepping of input value
-  if(property > 0) 
-     modprop = ((floor(property/step) * step) + scrollval);
-  else
-     modprop = ((ceil(property/step) * step) + scrollval);
+static SGExpressiond*
+read_offset_factor(const SGPropertyNode* configNode, SGExpressiond* expr,
+                   const std::string& factor, const std::string& offset)
+{
+  double offsetValue = configNode->getDoubleValue(offset, 0);
+  if (offsetValue != 0)
+    expr = new SGBiasExpression<double>(expr, offsetValue);
+  double factorValue = configNode->getDoubleValue(factor, 1);
+  if (factorValue != 1)
+    expr = new SGScaleExpression<double>(expr, factorValue);
+  return expr;
+}
+
+SGExpressiond*
+read_value(const SGPropertyNode* configNode, SGPropertyNode* modelRoot,
+           const char* unit, double defMin, double defMax)
+{
+  const SGPropertyNode * expression = configNode->getNode( "expression" );
+  if( expression != NULL )
+    return SGReadDoubleExpression( modelRoot, expression->getChild(0) );
+
+  SGExpression<double>* value = 0;
+
+  std::string inputPropertyName = configNode->getStringValue("property", "");
+  if (inputPropertyName.empty()) {
+    std::string spos = unit_string("starting-position", unit);
+    double initPos = configNode->getDoubleValue(spos, 0);
+    value = new SGConstExpression<double>(initPos);
   } else {
-     modprop = property;
+    SGPropertyNode* inputProperty;
+    inputProperty = modelRoot->getNode(inputPropertyName, true);
+    value = new SGPropertyExpression<double>(inputProperty);
   }
-  return modprop;
-
-}
 
-/**
- * Read an interpolation table from properties.
- */
-static SGInterpTable *
-read_interpolation_table (SGPropertyNode_ptr props)
-{
-  SGPropertyNode_ptr 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;
+  SGInterpTable* interpTable = read_interpolation_table(configNode);
+  if (interpTable) {
+    return new SGInterpTableExpression<double>(value, interpTable);
   } else {
-    return 0;
+    std::string offset = unit_string("offset", unit);
+    std::string min = unit_string("min", unit);
+    std::string max = unit_string("max", unit);
+    
+    if (configNode->getBoolValue("use-personality", false)) {
+      value = new SGPersonalityScaleOffsetExpression(value, configNode,
+                                                     "factor", offset);
+    } else {
+      value = read_factor_offset(configNode, value, "factor", offset);
+    }
+    
+    double minClip = configNode->getDoubleValue(min, defMin);
+    double maxClip = configNode->getDoubleValue(max, defMax);
+    if (minClip > SGMiscd::min(SGLimitsd::min(), -SGLimitsd::max()) ||
+        maxClip < SGLimitsd::max())
+      value = new SGClipExpression<double>(value, minClip, maxClip);
+    
+    return value;
   }
+  return 0;
 }
 
-
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of SGAnimation
+// Animation installer
 ////////////////////////////////////////////////////////////////////////
 
-// Initialize the static data member
-double SGAnimation::sim_time_sec = 0.0;
-SGPersonalityBranch *SGAnimation::current_object = 0;
+class SGAnimation::RemoveModeVisitor : public SGStateAttributeVisitor {
+public:
+  RemoveModeVisitor(osg::StateAttribute::GLMode mode) :
+    _mode(mode)
+  { }
+  virtual void apply(osg::StateSet* stateSet)
+  {
+    if (!stateSet)
+      return;
+    stateSet->removeMode(_mode);
+  }
+private:
+  osg::StateAttribute::GLMode _mode;
+};
+
+class SGAnimation::RemoveAttributeVisitor : public SGStateAttributeVisitor {
+public:
+  RemoveAttributeVisitor(osg::StateAttribute::Type type) :
+    _type(type)
+  { }
+  virtual void apply(osg::StateSet* stateSet)
+  {
+    if (!stateSet)
+      return;
+    while (stateSet->getAttribute(_type)) {
+      stateSet->removeAttribute(_type);
+    }
+  }
+private:
+  osg::StateAttribute::Type _type;
+};
+
+class SGAnimation::RemoveTextureModeVisitor : public SGStateAttributeVisitor {
+public:
+  RemoveTextureModeVisitor(unsigned unit, osg::StateAttribute::GLMode mode) :
+    _unit(unit),
+    _mode(mode)
+  { }
+  virtual void apply(osg::StateSet* stateSet)
+  {
+    if (!stateSet)
+      return;
+    stateSet->removeTextureMode(_unit, _mode);
+  }
+private:
+  unsigned _unit;
+  osg::StateAttribute::GLMode _mode;
+};
+
+class SGAnimation::RemoveTextureAttributeVisitor :
+  public SGStateAttributeVisitor {
+public:
+  RemoveTextureAttributeVisitor(unsigned unit,
+                                osg::StateAttribute::Type type) :
+    _unit(unit),
+    _type(type)
+  { }
+  virtual void apply(osg::StateSet* stateSet)
+  {
+    if (!stateSet)
+      return;
+    while (stateSet->getTextureAttribute(_unit, _type)) {
+      stateSet->removeTextureAttribute(_unit, _type);
+    }
+  }
+private:
+  unsigned _unit;
+  osg::StateAttribute::Type _type;
+};
+
+class SGAnimation::BinToInheritVisitor : public SGStateAttributeVisitor {
+public:
+  virtual void apply(osg::StateSet* stateSet)
+  {
+    if (!stateSet)
+      return;
+    stateSet->setRenderBinToInherit();
+  }
+};
+
+class SGAnimation::DrawableCloneVisitor : public osg::NodeVisitor {
+public:
+  DrawableCloneVisitor() :
+    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
+  {}
+  void apply(osg::Geode& geode)
+  {
+    for (unsigned i = 0 ; i < geode.getNumDrawables(); ++i) {
+      osg::CopyOp copyOp(osg::CopyOp::DEEP_COPY_ALL &
+                         ~osg::CopyOp::DEEP_COPY_TEXTURES);
+      geode.setDrawable(i, copyOp(geode.getDrawable(i)));
+    }
+  }
+};
 
-SGAnimation::SGAnimation (SGPropertyNode_ptr props, ssgBranch * branch)
-    : _branch(branch)
+namespace
 {
-    _branch->setName(props->getStringValue("name", 0));
+// Set all drawables to not use display lists. OSG will use
+// glDrawArrays instead.
+struct DoDrawArraysVisitor : public osg::NodeVisitor {
+    DoDrawArraysVisitor() :
+        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
+    {}
+    void apply(osg::Geode& geode)
+    {
+        using namespace osg;
+        using namespace std;
+
+        for (int i = 0; i < (int)geode.getNumDrawables(); ++i)
+            geode.getDrawable(i)->setUseDisplayList(false);
+    }
+};
 }
 
-SGAnimation::~SGAnimation ()
+SGAnimation::SGAnimation(const SGPropertyNode* configNode,
+                                           SGPropertyNode* modelRoot) :
+  osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+  _found(false),
+  _configNode(configNode),
+  _modelRoot(modelRoot)
 {
+  _name = configNode->getStringValue("name", "");
+  _enableHOT = configNode->getBoolValue("enable-hot", true);
+  _disableShadow = configNode->getBoolValue("disable-shadow", false);
+  std::vector<SGPropertyNode_ptr> objectNames =
+    configNode->getChildren("object-name");
+  for (unsigned i = 0; i < objectNames.size(); ++i)
+    _objectNames.push_back(objectNames[i]->getStringValue());
 }
 
-void
-SGAnimation::init ()
+SGAnimation::~SGAnimation()
 {
+  if (!_found)
+  {
+      std::list<std::string>::const_iterator i;
+      string info;
+      for (i = _objectNames.begin(); i != _objectNames.end(); ++i)
+      {
+          if (!info.empty())
+              info.append(", ");
+          info.append("'");
+          info.append(*i);
+          info.append("'");
+      }
+      if (!info.empty())
+      {
+          SG_LOG(SG_IO, SG_ALERT, "Could not find at least one of the following"
+                  " objects for animation: " << info);
+      }
+  }
 }
 
-int
-SGAnimation::update()
+bool
+SGAnimation::animate(osg::Node* node, const SGPropertyNode* configNode,
+                     SGPropertyNode* modelRoot,
+                     const osgDB::Options* options)
 {
-    return 1;
+  std::string type = configNode->getStringValue("type", "none");
+  if (type == "alpha-test") {
+    SGAlphaTestAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "billboard") {
+    SGBillboardAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "blend") {
+    SGBlendAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "dist-scale") {
+    SGDistScaleAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "flash") {
+    SGFlashAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "interaction") {
+    SGInteractionAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "material") {
+    SGMaterialAnimation animInst(configNode, modelRoot, options);
+    animInst.apply(node);
+  } else if (type == "noshadow") {
+    SGShadowAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "pick") {
+    SGPickAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "range") {
+    SGRangeAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "rotate" || type == "spin") {
+    SGRotateAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "scale") {
+    SGScaleAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "select") {
+    SGSelectAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "shader") {
+    SGShaderAnimation animInst(configNode, modelRoot, options);
+    animInst.apply(node);
+  } else if (type == "textranslate" || type == "texrotate" ||
+             type == "texmultiple") {
+    SGTexTransformAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "timed") {
+    SGTimedAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "translate") {
+    SGTranslateAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "light") {
+    SGLightAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else if (type == "null" || type == "none" || type.empty()) {
+    SGGroupAnimation animInst(configNode, modelRoot);
+    animInst.apply(node);
+  } else
+    return false;
+
+  return true;
 }
-
+  
+  
 void
-SGAnimation::restore()
+SGAnimation::apply(osg::Node* node)
 {
+  // duh what a special case ...
+  if (_objectNames.empty()) {
+    osg::Group* group = node->asGroup();
+    if (group) {
+      osg::ref_ptr<osg::Group> animationGroup;
+      installInGroup(std::string(), *group, animationGroup);
+    }
+  } else
+    node->accept(*this);
 }
 
-
-\f
-////////////////////////////////////////////////////////////////////////
-// Implementation of SGNullAnimation
-////////////////////////////////////////////////////////////////////////
-
-SGNullAnimation::SGNullAnimation (SGPropertyNode_ptr props)
-  : SGAnimation(props, new ssgBranch)
+void
+SGAnimation::install(osg::Node& node)
 {
+  _found = true;
+  if (_enableHOT)
+    node.setNodeMask( SG_NODEMASK_TERRAIN_BIT | node.getNodeMask());
+  else
+    node.setNodeMask(~SG_NODEMASK_TERRAIN_BIT & node.getNodeMask());
+  if (!_disableShadow)
+    node.setNodeMask( SG_NODEMASK_CASTSHADOW_BIT | node.getNodeMask());
+  else
+    node.setNodeMask(~SG_NODEMASK_CASTSHADOW_BIT & node.getNodeMask());
 }
 
-SGNullAnimation::~SGNullAnimation ()
+osg::Group*
+SGAnimation::createAnimationGroup(osg::Group& parent)
 {
+  // default implementation, we do not need a new group
+  // for every animation type. Usually animations that just change
+  // the StateSet of some parts of the model
+  return 0;
 }
 
-
-\f
-////////////////////////////////////////////////////////////////////////
-// Implementation of SGRangeAnimation
-////////////////////////////////////////////////////////////////////////
-
-SGRangeAnimation::SGRangeAnimation (SGPropertyNode *prop_root,
-                                    SGPropertyNode_ptr props)
-  : SGAnimation(props, new ssgRangeSelector),
-    _min(0.0), _max(0.0), _min_factor(1.0), _max_factor(1.0),
-    _condition(0)
+void
+SGAnimation::apply(osg::Group& group)
 {
-    SGPropertyNode_ptr node = props->getChild("condition");
-    if (node != 0)
-       _condition = sgReadCondition(prop_root, node);
+  // the trick is to first traverse the children and then
+  // possibly splice in a new group node if required.
+  // Else we end up in a recursive loop where we infinitly insert new
+  // groups in between
+  traverse(group);
+
+  // Note that this algorithm preserves the order of the child objects
+  // like they appear in the object-name tags.
+  // The timed animations require this
+  osg::ref_ptr<osg::Group> animationGroup;
+  std::list<std::string>::const_iterator nameIt;
+  for (nameIt = _objectNames.begin(); nameIt != _objectNames.end(); ++nameIt)
+    installInGroup(*nameIt, group, animationGroup);
+}
 
-    float ranges[2];
+void
+SGAnimation::installInGroup(const std::string& name, osg::Group& group,
+                            osg::ref_ptr<osg::Group>& animationGroup)
+{
+  int i = group.getNumChildren() - 1;
+  for (; 0 <= i; --i) {
+    osg::Node* child = group.getChild(i);
+
+    // Check if this one is already processed
+    if (std::find(_installedAnimations.begin(),
+                  _installedAnimations.end(), child)
+        != _installedAnimations.end())
+      continue;
+
+    if (name.empty() || child->getName() == name) {
+      // fire the installation of the animation
+      install(*child);
+      
+      // create a group node on demand
+      if (!animationGroup.valid()) {
+        animationGroup = createAnimationGroup(group);
+        // Animation type that does not require a new group,
+        // in this case we can stop and look for the next object
+        if (animationGroup.valid() && !_name.empty())
+          animationGroup->setName(_name);
+      }
+      if (animationGroup.valid()) {
+        animationGroup->addChild(child);
+        group.removeChild(i);
+      }
 
-    node = props->getChild( "min-factor" );
-    if (node != 0) {
-       _min_factor = props->getFloatValue("min-factor", 1.0);
-    }
-    node = props->getChild( "max-factor" );
-    if (node != 0) {
-       _max_factor = props->getFloatValue("max-factor", 1.0);
-    }
-    node = props->getChild( "min-property" );
-    if (node != 0) {
-       _min_prop = (SGPropertyNode *)prop_root->getNode(node->getStringValue(), true);
-       ranges[0] = _min_prop->getFloatValue() * _min_factor;
-    } else {
-       _min = props->getFloatValue("min-m", 0);
-       ranges[0] = _min * _min_factor;
-    }
-    node = props->getChild( "max-property" );
-    if (node != 0) {
-       _max_prop = (SGPropertyNode *)prop_root->getNode(node->getStringValue(), true);
-       ranges[1] = _max_prop->getFloatValue() * _max_factor;
-    } else {
-       _max = props->getFloatValue("max-m", 0);
-       ranges[1] = _max * _max_factor;
+      // store that we already have processed this child node
+      // We can hit this one twice if an animation references some
+      // part of a subtree twice
+      _installedAnimations.push_back(child);
     }
-    ((ssgRangeSelector *)_branch)->setRanges(ranges, 2);
+  }
 }
 
-SGRangeAnimation::~SGRangeAnimation ()
+void
+SGAnimation::removeMode(osg::Node& node, osg::StateAttribute::GLMode mode)
 {
+  RemoveModeVisitor visitor(mode);
+  node.accept(visitor);
 }
 
-int
-SGRangeAnimation::update()
+void
+SGAnimation::removeAttribute(osg::Node& node, osg::StateAttribute::Type type)
 {
-  float ranges[2];
-  if ( _condition == 0 || _condition->test() ) {
-    if (_min_prop != 0) {
-      ranges[0] = _min_prop->getFloatValue() * _min_factor;
-    } else {
-      ranges[0] = _min * _min_factor;
-    }
-    if (_max_prop != 0) {
-      ranges[1] = _max_prop->getFloatValue() * _max_factor;
-    } else {
-      ranges[1] = _max * _max_factor;
-    }
-  } else {
-    ranges[0] = 0.f;
-    ranges[1] = 1000000000.f;
-  }
-  ((ssgRangeSelector *)_branch)->setRanges(ranges, 2);
-  return 1;
+  RemoveAttributeVisitor visitor(type);
+  node.accept(visitor);
+}
+
+void
+SGAnimation::removeTextureMode(osg::Node& node, unsigned unit,
+                               osg::StateAttribute::GLMode mode)
+{
+  RemoveTextureModeVisitor visitor(unit, mode);
+  node.accept(visitor);
 }
 
+void
+SGAnimation::removeTextureAttribute(osg::Node& node, unsigned unit,
+                                    osg::StateAttribute::Type type)
+{
+  RemoveTextureAttributeVisitor visitor(unit, type);
+  node.accept(visitor);
+}
 
-\f
-////////////////////////////////////////////////////////////////////////
-// Implementation of SGBillboardAnimation
-////////////////////////////////////////////////////////////////////////
+void
+SGAnimation::setRenderBinToInherit(osg::Node& node)
+{
+  BinToInheritVisitor visitor;
+  node.accept(visitor);
+}
 
-SGBillboardAnimation::SGBillboardAnimation (SGPropertyNode_ptr props)
-    : SGAnimation(props, new ssgCutout(props->getBoolValue("spherical", true)))
+void
+SGAnimation::cloneDrawables(osg::Node& node)
 {
+  DrawableCloneVisitor visitor;
+  node.accept(visitor);
 }
 
-SGBillboardAnimation::~SGBillboardAnimation ()
+const SGCondition*
+SGAnimation::getCondition() const
 {
+  const SGPropertyNode* conditionNode = _configNode->getChild("condition");
+  if (!conditionNode)
+    return 0;
+  return sgReadCondition(_modelRoot, conditionNode);
 }
 
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of SGSelectAnimation
+// Implementation of null animation
 ////////////////////////////////////////////////////////////////////////
 
-SGSelectAnimation::SGSelectAnimation( SGPropertyNode *prop_root,
-                                  SGPropertyNode_ptr props )
-  : SGAnimation(props, new ssgSelector),
-    _condition(0)
+// Ok, that is to build a subgraph from different other
+// graph nodes. I guess that this stems from the time where modellers
+// could not build hierarchical trees ...
+SGGroupAnimation::SGGroupAnimation(const SGPropertyNode* configNode,
+                                   SGPropertyNode* modelRoot):
+  SGAnimation(configNode, modelRoot)
 {
-  SGPropertyNode_ptr node = props->getChild("condition");
-  if (node != 0)
-    _condition = sgReadCondition(prop_root, node);
 }
 
-SGSelectAnimation::~SGSelectAnimation ()
+osg::Group*
+SGGroupAnimation::createAnimationGroup(osg::Group& parent)
 {
-  delete _condition;
+  osg::Group* group = new osg::Group;
+  parent.addChild(group);
+  return group;
 }
 
-int
-SGSelectAnimation::update()
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of translate animation
+////////////////////////////////////////////////////////////////////////
+
+class SGTranslateAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+  UpdateCallback(SGCondition const* condition,
+                 SGExpressiond const* animationValue) :
+    _condition(condition),
+    _animationValue(animationValue)
+  { }
+  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+  {
+    if (!_condition || _condition->test()) {
+      SGTranslateTransform* transform;
+      transform = static_cast<SGTranslateTransform*>(node);
+      transform->setValue(_animationValue->getValue());
+    }
+    traverse(node, nv);
+  }
+public:
+  SGSharedPtr<SGCondition const> _condition;
+  SGSharedPtr<SGExpressiond const> _animationValue;
+};
+
+SGTranslateAnimation::SGTranslateAnimation(const SGPropertyNode* configNode,
+                                           SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
 {
-  if (_condition != 0 && _condition->test()) 
-      ((ssgSelector *)_branch)->select(0xffff);
+  _condition = getCondition();
+  SGSharedPtr<SGExpressiond> value;
+  value = read_value(configNode, modelRoot, "-m",
+                     -SGLimitsd::max(), SGLimitsd::max());
+  _animationValue = value->simplify();
+  if (_animationValue)
+    _initialValue = _animationValue->getValue();
   else
-      ((ssgSelector *)_branch)->select(0x0000);
-  return 1;
+    _initialValue = 0;
+
+  if (configNode->hasValue("axis/x1-m")) {
+    SGVec3d v1, v2;
+    v1[0] = configNode->getDoubleValue("axis/x1-m", 0);
+    v1[1] = configNode->getDoubleValue("axis/y1-m", 0);
+    v1[2] = configNode->getDoubleValue("axis/z1-m", 0);
+    v2[0] = configNode->getDoubleValue("axis/x2-m", 0);
+    v2[1] = configNode->getDoubleValue("axis/y2-m", 0);
+    v2[2] = configNode->getDoubleValue("axis/z2-m", 0);
+    _axis = v2 - v1;
+  } else {
+    _axis[0] = configNode->getDoubleValue("axis/x", 0);
+    _axis[1] = configNode->getDoubleValue("axis/y", 0);
+    _axis[2] = configNode->getDoubleValue("axis/z", 0);
+  }
+  if (8*SGLimitsd::min() < norm(_axis))
+    _axis = normalize(_axis);
 }
 
+osg::Group*
+SGTranslateAnimation::createAnimationGroup(osg::Group& parent)
+{
+  SGTranslateTransform* transform = new SGTranslateTransform;
+  transform->setName("translate animation");
+  if (_animationValue && !_animationValue->isConst()) {
+    UpdateCallback* uc = new UpdateCallback(_condition, _animationValue);
+    transform->setUpdateCallback(uc);
+  }
+  transform->setAxis(_axis);
+  transform->setValue(_initialValue);
+  parent.addChild(transform);
+  return transform;
+}
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of SGSpinAnimation
+// Implementation of rotate/spin animation
 ////////////////////////////////////////////////////////////////////////
 
-SGSpinAnimation::SGSpinAnimation( SGPropertyNode *prop_root,
-                              SGPropertyNode_ptr props,
-                              double sim_time_sec )
-  : SGAnimation(props, new ssgTransform),
-    _prop((SGPropertyNode *)prop_root->getNode(props->getStringValue("property", "/null"), true)),
-    _factor(props->getDoubleValue("factor", 1.0)),
-    _position_deg(props->getDoubleValue("starting-position-deg", 0)),
-    _last_time_sec( sim_time_sec ),
-    _condition(0)
-{
-    SGPropertyNode_ptr node = props->getChild("condition");
-    if (node != 0)
-        _condition = sgReadCondition(prop_root, node);
-
-    _center[0] = 0;
-    _center[1] = 0;
-    _center[2] = 0;
-    if (props->hasValue("axis/x1-m")) {
-        double x1,y1,z1,x2,y2,z2;
-        x1 = props->getFloatValue("axis/x1-m");
-        y1 = props->getFloatValue("axis/y1-m");
-        z1 = props->getFloatValue("axis/z1-m");
-        x2 = props->getFloatValue("axis/x2-m");
-        y2 = props->getFloatValue("axis/y2-m");
-        z2 = props->getFloatValue("axis/z2-m");
-        _center[0] = (x1+x2)/2;
-        _center[1]= (y1+y2)/2;
-        _center[2] = (z1+z2)/2;
-        float vector_length = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1));
-        _axis[0] = (x2-x1)/vector_length;
-        _axis[1] = (y2-y1)/vector_length;
-        _axis[2] = (z2-z1)/vector_length;
-    } else {
-       _axis[0] = props->getFloatValue("axis/x", 0);
-       _axis[1] = props->getFloatValue("axis/y", 0);
-       _axis[2] = props->getFloatValue("axis/z", 0);
+class SGRotateAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+  UpdateCallback(SGCondition const* condition,
+                 SGExpressiond const* animationValue) :
+    _condition(condition),
+    _animationValue(animationValue)
+  { }
+  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+  {
+    if (!_condition || _condition->test()) {
+      SGRotateTransform* transform;
+      transform = static_cast<SGRotateTransform*>(node);
+      transform->setAngleDeg(_animationValue->getValue());
     }
-    if (props->hasValue("center/x-m")) {
-       _center[0] = props->getFloatValue("center/x-m", 0);
-       _center[1] = props->getFloatValue("center/y-m", 0);
-       _center[2] = props->getFloatValue("center/z-m", 0);
+    traverse(node, nv);
+  }
+public:
+  SGSharedPtr<SGCondition const> _condition;
+  SGSharedPtr<SGExpressiond const> _animationValue;
+};
+
+class SGRotateAnimation::SpinUpdateCallback : public osg::NodeCallback {
+public:
+  SpinUpdateCallback(SGCondition const* condition,
+                     SGExpressiond const* animationValue) :
+    _condition(condition),
+    _animationValue(animationValue),
+    _lastTime(-1)
+  { }
+  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+  {
+    if (!_condition || _condition->test()) {
+      SGRotateTransform* transform;
+      transform = static_cast<SGRotateTransform*>(node);
+
+      double t = nv->getFrameStamp()->getReferenceTime();
+      double dt = 0;
+      if (0 <= _lastTime)
+        dt = t - _lastTime;
+      _lastTime = t;
+      double velocity_rpms = _animationValue->getValue()/60;
+      double angle = transform->getAngleDeg();
+      angle += dt*velocity_rpms*360;
+      angle -= 360*floor(angle/360);
+      transform->setAngleDeg(angle);
     }
-    sgNormalizeVec3(_axis);
+    traverse(node, nv);
+  }
+public:
+  SGSharedPtr<SGCondition const> _condition;
+  SGSharedPtr<SGExpressiond const> _animationValue;
+  double _lastTime;
+};
+
+SGRotateAnimation::SGRotateAnimation(const SGPropertyNode* configNode,
+                                     SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
+{
+  std::string type = configNode->getStringValue("type", "");
+  _isSpin = (type == "spin");
+
+  _condition = getCondition();
+  SGSharedPtr<SGExpressiond> value;
+  value = read_value(configNode, modelRoot, "-deg",
+                     -SGLimitsd::max(), SGLimitsd::max());
+  _animationValue = value->simplify();
+  if (_animationValue)
+    _initialValue = _animationValue->getValue();
+  else
+    _initialValue = 0;
+
+  _center = SGVec3d::zeros();
+  if (configNode->hasValue("axis/x1-m")) {
+    SGVec3d v1, v2;
+    v1[0] = configNode->getDoubleValue("axis/x1-m", 0);
+    v1[1] = configNode->getDoubleValue("axis/y1-m", 0);
+    v1[2] = configNode->getDoubleValue("axis/z1-m", 0);
+    v2[0] = configNode->getDoubleValue("axis/x2-m", 0);
+    v2[1] = configNode->getDoubleValue("axis/y2-m", 0);
+    v2[2] = configNode->getDoubleValue("axis/z2-m", 0);
+    _center = 0.5*(v1+v2);
+    _axis = v2 - v1;
+  } else {
+    _axis[0] = configNode->getDoubleValue("axis/x", 0);
+    _axis[1] = configNode->getDoubleValue("axis/y", 0);
+    _axis[2] = configNode->getDoubleValue("axis/z", 0);
+  }
+  if (8*SGLimitsd::min() < norm(_axis))
+    _axis = normalize(_axis);
+
+  _center[0] = configNode->getDoubleValue("center/x-m", _center[0]);
+  _center[1] = configNode->getDoubleValue("center/y-m", _center[1]);
+  _center[2] = configNode->getDoubleValue("center/z-m", _center[2]);
 }
 
-SGSpinAnimation::~SGSpinAnimation ()
+osg::Group*
+SGRotateAnimation::createAnimationGroup(osg::Group& parent)
 {
+  SGRotateTransform* transform = new SGRotateTransform;
+  transform->setName("rotate animation");
+  if (_isSpin) {
+    SpinUpdateCallback* uc;
+    uc = new SpinUpdateCallback(_condition, _animationValue);
+    transform->setUpdateCallback(uc);
+  } else if (_animationValue || !_animationValue->isConst()) {
+    UpdateCallback* uc = new UpdateCallback(_condition, _animationValue);
+    transform->setUpdateCallback(uc);
+  }
+  transform->setCenter(_center);
+  transform->setAxis(_axis);
+  transform->setAngleDeg(_initialValue);
+  parent.addChild(transform);
+  return transform;
 }
 
-int
-SGSpinAnimation::update()
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of scale animation
+////////////////////////////////////////////////////////////////////////
+
+class SGScaleAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+  UpdateCallback(const SGCondition* condition,
+                 SGSharedPtr<const SGExpressiond> animationValue[3]) :
+    _condition(condition)
+  {
+    _animationValue[0] = animationValue[0];
+    _animationValue[1] = animationValue[1];
+    _animationValue[2] = animationValue[2];
+  }
+  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+  {
+    if (!_condition || _condition->test()) {
+      SGScaleTransform* transform;
+      transform = static_cast<SGScaleTransform*>(node);
+      SGVec3d scale(_animationValue[0]->getValue(),
+                    _animationValue[1]->getValue(),
+                    _animationValue[2]->getValue());
+      transform->setScaleFactor(scale);
+    }
+    traverse(node, nv);
+  }
+public:
+  SGSharedPtr<SGCondition const> _condition;
+  SGSharedPtr<SGExpressiond const> _animationValue[3];
+};
+
+SGScaleAnimation::SGScaleAnimation(const SGPropertyNode* configNode,
+                                   SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
 {
-  if ( _condition == 0 || _condition->test() ) {
-    double dt = sim_time_sec - _last_time_sec;
-    _last_time_sec = sim_time_sec;
+  _condition = getCondition();
+
+  // default offset/factor for all directions
+  double offset = configNode->getDoubleValue("offset", 0);
+  double factor = configNode->getDoubleValue("factor", 1);
+
+  SGSharedPtr<SGExpressiond> inPropExpr;
 
-    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);
-    ((ssgTransform *)_branch)->setTransform(_matrix);
+  std::string inputPropertyName;
+  inputPropertyName = configNode->getStringValue("property", "");
+  if (inputPropertyName.empty()) {
+    inPropExpr = new SGConstExpression<double>(0);
+  } else {
+    SGPropertyNode* inputProperty;
+    inputProperty = modelRoot->getNode(inputPropertyName, true);
+    inPropExpr = new SGPropertyExpression<double>(inputProperty);
+  }
+
+  SGInterpTable* interpTable = read_interpolation_table(configNode);
+  if (interpTable) {
+    SGSharedPtr<SGExpressiond> value;
+    value = new SGInterpTableExpression<double>(inPropExpr, interpTable);
+    _animationValue[0] = value->simplify();
+    _animationValue[1] = value->simplify();
+    _animationValue[2] = value->simplify();
+  } else if (configNode->getBoolValue("use-personality", false)) {
+    SGSharedPtr<SGExpressiond> value;
+    value = new SGPersonalityScaleOffsetExpression(inPropExpr, configNode,
+                                                   "x-factor", "x-offset",
+                                                   factor, offset);
+    double minClip = configNode->getDoubleValue("x-min", 0);
+    double maxClip = configNode->getDoubleValue("x-max", SGLimitsd::max());
+    value = new SGClipExpression<double>(value, minClip, maxClip);
+    _animationValue[0] = value->simplify();
+    
+    value = new SGPersonalityScaleOffsetExpression(inPropExpr, configNode,
+                                                   "y-factor", "y-offset",
+                                                   factor, offset);
+    minClip = configNode->getDoubleValue("y-min", 0);
+    maxClip = configNode->getDoubleValue("y-max", SGLimitsd::max());
+    value = new SGClipExpression<double>(value, minClip, maxClip);
+    _animationValue[1] = value->simplify();
+    
+    value = new SGPersonalityScaleOffsetExpression(inPropExpr, configNode,
+                                                   "z-factor", "z-offset",
+                                                   factor, offset);
+    minClip = configNode->getDoubleValue("z-min", 0);
+    maxClip = configNode->getDoubleValue("z-max", SGLimitsd::max());
+    value = new SGClipExpression<double>(value, minClip, maxClip);
+    _animationValue[2] = value->simplify();
+  } else {
+    SGSharedPtr<SGExpressiond> value;
+    value = read_factor_offset(configNode, inPropExpr, "x-factor", "x-offset");
+    double minClip = configNode->getDoubleValue("x-min", 0);
+    double maxClip = configNode->getDoubleValue("x-max", SGLimitsd::max());
+    value = new SGClipExpression<double>(value, minClip, maxClip);
+    _animationValue[0] = value->simplify();
+
+    value = read_factor_offset(configNode, inPropExpr, "y-factor", "y-offset");
+    minClip = configNode->getDoubleValue("y-min", 0);
+    maxClip = configNode->getDoubleValue("y-max", SGLimitsd::max());
+    value = new SGClipExpression<double>(value, minClip, maxClip);
+    _animationValue[1] = value->simplify();
+
+    value = read_factor_offset(configNode, inPropExpr, "z-factor", "z-offset");
+    minClip = configNode->getDoubleValue("z-min", 0);
+    maxClip = configNode->getDoubleValue("z-max", SGLimitsd::max());
+    value = new SGClipExpression<double>(value, minClip, maxClip);
+    _animationValue[2] = value->simplify();
   }
-  return 1;
+  _initialValue[0] = configNode->getDoubleValue("x-starting-scale", 1);
+  _initialValue[0] *= configNode->getDoubleValue("x-factor", factor);
+  _initialValue[0] += configNode->getDoubleValue("x-offset", offset);
+  _initialValue[1] = configNode->getDoubleValue("y-starting-scale", 1);
+  _initialValue[1] *= configNode->getDoubleValue("y-factor", factor);
+  _initialValue[1] += configNode->getDoubleValue("y-offset", offset);
+  _initialValue[2] = configNode->getDoubleValue("z-starting-scale", 1);
+  _initialValue[2] *= configNode->getDoubleValue("z-factor", factor);
+  _initialValue[2] += configNode->getDoubleValue("z-offset", offset);
+  _center[0] = configNode->getDoubleValue("center/x-m", 0);
+  _center[1] = configNode->getDoubleValue("center/y-m", 0);
+  _center[2] = configNode->getDoubleValue("center/z-m", 0);
 }
 
+osg::Group*
+SGScaleAnimation::createAnimationGroup(osg::Group& parent)
+{
+  SGScaleTransform* transform = new SGScaleTransform;
+  transform->setName("scale animation");
+  transform->setCenter(_center);
+  transform->setScaleFactor(_initialValue);
+  UpdateCallback* uc = new UpdateCallback(_condition, _animationValue);
+  transform->setUpdateCallback(uc);
+  parent.addChild(transform);
+  return transform;
+}
 
 \f
-////////////////////////////////////////////////////////////////////////
-// Implementation of SGTimedAnimation
-////////////////////////////////////////////////////////////////////////
+// Don't create a new state state everytime we need GL_NORMALIZE!
 
-SGTimedAnimation::SGTimedAnimation (SGPropertyNode_ptr props)
-  : SGAnimation(props, new ssgSelector),
-    _use_personality( props->getBoolValue("use-personality",false) ),
-    _duration_sec(props->getDoubleValue("duration-sec", 1.0)),
-    _last_time_sec( sim_time_sec ),
-    _total_duration_sec( 0 ),
-    _step( 0 )
-    
+namespace
 {
-    vector<SGPropertyNode_ptr> nodes = props->getChildren( "branch-duration-sec" );
-    size_t nb = nodes.size();
-    for ( size_t i = 0; i < nb; i++ ) {
-        size_t ind = nodes[ i ]->getIndex();
-        while ( ind >= _branch_duration_specs.size() ) {
-            _branch_duration_specs.push_back( DurationSpec( _duration_sec ) );
-        }
-        SGPropertyNode_ptr rNode = nodes[ i ]->getChild("random");
-        if ( rNode == 0 ) {
-            _branch_duration_specs[ ind ] = DurationSpec( nodes[ i ]->getDoubleValue() );
-        } else {
-            _branch_duration_specs[ ind ] = DurationSpec( rNode->getDoubleValue( "min", 0.0 ),
-                                                          rNode->getDoubleValue( "max", 1.0 ) );
-        }
-    }
-    if ( !_use_personality ) {
-        for ( size_t i = 0; i < _branch_duration_specs.size(); i++ ) {
-            DurationSpec &sp = _branch_duration_specs[ i ];
-            double v = sp._min + sg_random() * ( sp._max - sp._min );
-            _branch_duration_sec.push_back( v );
-            _total_duration_sec += v;
-        }
-    }
-    ((ssgSelector *)getBranch())->selectStep(_step);
-}
-
-SGTimedAnimation::~SGTimedAnimation ()
-{
-}
-
-int
-SGTimedAnimation::update()
-{
-    if ( _use_personality ) {
-        SGPersonalityBranch *key = current_object;
-        if ( !key->getIntValue( this, INIT ) ) {
-            double total = 0;
-            for ( size_t i = 0; i < _branch_duration_specs.size(); i++ ) {
-                DurationSpec &sp = _branch_duration_specs[ i ];
-                double v = sp._min + sg_random() * ( sp._max - sp._min );
-                key->setDoubleValue( v, this, BRANCH_DURATION_SEC, i );
-                total += v;
-            }
-            key->setDoubleValue( sim_time_sec, this, LAST_TIME_SEC );
-            key->setDoubleValue( total, this, TOTAL_DURATION_SEC );
-            key->setIntValue( 0, this, STEP );
-            key->setIntValue( 1, this, INIT );
-        }
-
-        _step = key->getIntValue( this, STEP );
-        _last_time_sec = key->getDoubleValue( this, LAST_TIME_SEC );
-        _total_duration_sec = key->getDoubleValue( this, TOTAL_DURATION_SEC );
-        while ( ( sim_time_sec - _last_time_sec ) >= _total_duration_sec ) {
-            _last_time_sec += _total_duration_sec;
-        }
-        double duration = _duration_sec;
-        if ( _step < (int)_branch_duration_specs.size() ) {
-            duration = key->getDoubleValue( this, BRANCH_DURATION_SEC, _step );
-        }
-        if ( ( sim_time_sec - _last_time_sec ) >= duration ) {
-            _last_time_sec += duration;
-            _step += 1;
-            if ( _step >= getBranch()->getNumKids() )
-                _step = 0;
-        }
-        ((ssgSelector *)getBranch())->selectStep( _step );
-        key->setDoubleValue( _last_time_sec, this, LAST_TIME_SEC );
-        key->setIntValue( _step, this, STEP );
-    } else {
-        while ( ( sim_time_sec - _last_time_sec ) >= _total_duration_sec ) {
-            _last_time_sec += _total_duration_sec;
-        }
-        double duration = _duration_sec;
-        if ( _step < (int)_branch_duration_sec.size() ) {
-            duration = _branch_duration_sec[ _step ];
-        }
-        if ( ( sim_time_sec - _last_time_sec ) >= duration ) {
-            _last_time_sec += duration;
-            _step += 1;
-            if ( _step >= getBranch()->getNumKids() )
-                _step = 0;
-            ((ssgSelector *)getBranch())->selectStep( _step );
-        }
+Mutex normalizeMutex;
+
+osg::StateSet* getNormalizeStateSet()
+{
+    static osg::ref_ptr<osg::StateSet> normalizeStateSet;
+    ScopedLock<Mutex> lock(normalizeMutex);
+    if (!normalizeStateSet.valid()) {
+        normalizeStateSet = new osg::StateSet;
+        normalizeStateSet->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
+        normalizeStateSet->setDataVariance(osg::Object::STATIC);
     }
-    return 1;
+    return normalizeStateSet.get();
+}
 }
 
-
-\f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of SGRotateAnimation
+// Implementation of dist scale animation
 ////////////////////////////////////////////////////////////////////////
 
-SGRotateAnimation::SGRotateAnimation( SGPropertyNode *prop_root,
-                                  SGPropertyNode_ptr props )
-    : SGAnimation(props, new ssgTransform),
-      _prop((SGPropertyNode *)prop_root->getNode(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)),
-      _condition(0)
-{
-    SGPropertyNode_ptr node = props->getChild("condition");
-    if (node != 0)
-      _condition = sgReadCondition(prop_root, node);
-
-    _center[0] = 0;
-    _center[1] = 0;
-    _center[2] = 0;
-    if (props->hasValue("axis/x1-m")) {
-        double x1,y1,z1,x2,y2,z2;
-        x1 = props->getFloatValue("axis/x1-m");
-        y1 = props->getFloatValue("axis/y1-m");
-        z1 = props->getFloatValue("axis/z1-m");
-        x2 = props->getFloatValue("axis/x2-m");
-        y2 = props->getFloatValue("axis/y2-m");
-        z2 = props->getFloatValue("axis/z2-m");
-        _center[0] = (x1+x2)/2;
-        _center[1]= (y1+y2)/2;
-        _center[2] = (z1+z2)/2;
-        float vector_length = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1));
-        _axis[0] = (x2-x1)/vector_length;
-        _axis[1] = (y2-y1)/vector_length;
-        _axis[2] = (z2-z1)/vector_length;
-    } else {
-       _axis[0] = props->getFloatValue("axis/x", 0);
-       _axis[1] = props->getFloatValue("axis/y", 0);
-       _axis[2] = props->getFloatValue("axis/z", 0);
-    }
-    if (props->hasValue("center/x-m")) {
-       _center[0] = props->getFloatValue("center/x-m", 0);
-       _center[1] = props->getFloatValue("center/y-m", 0);
-       _center[2] = props->getFloatValue("center/z-m", 0);
-    }
-    sgNormalizeVec3(_axis);
-}
+class SGDistScaleAnimation::Transform : public osg::Transform {
+public:
+  Transform() : _min_v(0.0), _max_v(0.0), _factor(0.0), _offset(0.0) {}
+  Transform(const Transform& rhs,
+            const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
+    : osg::Transform(rhs, copyOp), _table(rhs._table), _center(rhs._center),
+      _min_v(rhs._min_v), _max_v(rhs._max_v), _factor(rhs._factor),
+      _offset(rhs._offset)
+  {
+  }
+  META_Node(simgear, SGDistScaleAnimation::Transform);
+  Transform(const SGPropertyNode* configNode)
+  {
+    setName(configNode->getStringValue("name", "dist scale animation"));
+    setReferenceFrame(RELATIVE_RF);
+    setStateSet(getNormalizeStateSet());
+    _factor = configNode->getFloatValue("factor", 1);
+    _offset = configNode->getFloatValue("offset", 0);
+    _min_v = configNode->getFloatValue("min", SGLimitsf::epsilon());
+    _max_v = configNode->getFloatValue("max", SGLimitsf::max());
+    _table = read_interpolation_table(configNode);
+    _center[0] = configNode->getFloatValue("center/x-m", 0);
+    _center[1] = configNode->getFloatValue("center/y-m", 0);
+    _center[2] = configNode->getFloatValue("center/z-m", 0);
+  }
+  virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,
+                                         osg::NodeVisitor* nv) const 
+  {
+    osg::Matrix transform;
+    double scale_factor = computeScaleFactor(nv);
+    transform(0,0) = scale_factor;
+    transform(1,1) = scale_factor;
+    transform(2,2) = scale_factor;
+    transform(3,0) = _center[0]*(1 - scale_factor);
+    transform(3,1) = _center[1]*(1 - scale_factor);
+    transform(3,2) = _center[2]*(1 - scale_factor);
+    matrix.preMult(transform);
+    return true;
+  }
+  
+  virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,
+                                         osg::NodeVisitor* nv) const
+  {
+    double scale_factor = computeScaleFactor(nv);
+    if (fabs(scale_factor) <= SGLimits<double>::min())
+      return false;
+    osg::Matrix transform;
+    double rScaleFactor = 1/scale_factor;
+    transform(0,0) = rScaleFactor;
+    transform(1,1) = rScaleFactor;
+    transform(2,2) = rScaleFactor;
+    transform(3,0) = _center[0]*(1 - rScaleFactor);
+    transform(3,1) = _center[1]*(1 - rScaleFactor);
+    transform(3,2) = _center[2]*(1 - rScaleFactor);
+    matrix.postMult(transform);
+    return true;
+  }
 
-SGRotateAnimation::~SGRotateAnimation ()
-{
-  delete _table;
-}
+  static bool writeLocalData(const osg::Object& obj, osgDB::Output& fw)
+  {
+    const Transform& trans = static_cast<const Transform&>(obj);
+    fw.indent() << "center " << trans._center << "\n";
+    fw.indent() << "min_v " << trans._min_v << "\n";
+    fw.indent() << "max_v " << trans._max_v << "\n";
+    fw.indent() << "factor " << trans._factor << "\n";
+    fw.indent() << "offset " << trans._offset << "\n";
+    return true;
+  }
+private:
+  double computeScaleFactor(osg::NodeVisitor* nv) const
+  {
+    if (!nv)
+      return 1;
 
-int
-SGRotateAnimation::update()
-{
-  if (_condition == 0 || _condition->test()) {
+    double scale_factor = (toOsg(_center) - nv->getEyePoint()).length();
     if (_table == 0) {
-      _position_deg = _prop->getDoubleValue() * _factor + _offset_deg;
-      if (_has_min && _position_deg < _min_deg)
-        _position_deg = _min_deg;
-      if (_has_max && _position_deg > _max_deg)
-        _position_deg = _max_deg;
+      scale_factor = _factor * scale_factor + _offset;
     } else {
-      _position_deg = _table->interpolate(_prop->getDoubleValue());
+      scale_factor = _table->interpolate( scale_factor );
     }
-    set_rotation(_matrix, _position_deg, _center, _axis);
-    ((ssgTransform *)_branch)->setTransform(_matrix);
+    if (scale_factor < _min_v)
+      scale_factor = _min_v;
+    if (scale_factor > _max_v)
+      scale_factor = _max_v;
+
+    return scale_factor;
   }
-  return 1;
-}
 
-\f
-////////////////////////////////////////////////////////////////////////
-// Implementation of SGBlendAnimation
-////////////////////////////////////////////////////////////////////////
+  SGSharedPtr<SGInterpTable> _table;
+  SGVec3d _center;
+  double _min_v;
+  double _max_v;
+  double _factor;
+  double _offset;
+};
 
-SGBlendAnimation::SGBlendAnimation( SGPropertyNode *prop_root,
-                                        SGPropertyNode_ptr props )
-  : SGAnimation(props, new ssgTransform),
-    _prop((SGPropertyNode *)prop_root->getNode(props->getStringValue("property", "/null"), true)),
-    _table(read_interpolation_table(props)),
-    _prev_value(1.0),
-    _offset(props->getDoubleValue("offset", 0.0)),
-    _factor(props->getDoubleValue("factor", 1.0)),
-    _has_min(props->hasValue("min")),
-    _min(props->getDoubleValue("min", 0.0)),
-    _has_max(props->hasValue("max")),
-    _max(props->getDoubleValue("max", 1.0))
+
+SGDistScaleAnimation::SGDistScaleAnimation(const SGPropertyNode* configNode,
+                                           SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
 {
 }
 
-SGBlendAnimation::~SGBlendAnimation ()
+osg::Group*
+SGDistScaleAnimation::createAnimationGroup(osg::Group& parent)
 {
-    delete _table;
+  Transform* transform = new Transform(getConfig());
+  parent.addChild(transform);
+  return transform;
 }
 
-int
-SGBlendAnimation::update()
+namespace
 {
-  double _blend;
+  osgDB::RegisterDotOsgWrapperProxy distScaleAnimationTransformProxy
+  (
+   new SGDistScaleAnimation::Transform,
+   "SGDistScaleAnimation::Transform",
+   "Object Node Transform SGDistScaleAnimation::Transform Group",
+   0,
+   &SGDistScaleAnimation::Transform::writeLocalData
+   );
+}
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of flash animation
+////////////////////////////////////////////////////////////////////////
 
-  if (_table == 0) {
-    _blend = 1.0 - (_prop->getDoubleValue() * _factor + _offset);
+class SGFlashAnimation::Transform : public osg::Transform {
+public:
+  Transform() : _power(0.0), _factor(0.0), _offset(0.0), _min_v(0.0),
+                _max_v(0.0), _two_sides(false)
+  {}
+
+  Transform(const Transform& rhs,
+            const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
+    : osg::Transform(rhs, copyOp), _center(rhs._center), _axis(rhs._axis),
+      _power(rhs._power), _factor(rhs._factor), _offset(rhs._offset),
+      _min_v(rhs._min_v), _max_v(rhs._max_v), _two_sides(rhs._two_sides)
+  {
+  }
+  META_Node(simgear, SGFlashAnimation::Transform);
 
-    if (_has_min && (_blend < _min))
-      _blend = _min;
-    if (_has_max && (_blend > _max))
-      _blend = _max;
-  } else {
-    _blend = _table->interpolate(_prop->getDoubleValue());
+  Transform(const SGPropertyNode* configNode)
+  {
+    setReferenceFrame(RELATIVE_RF);
+    setName(configNode->getStringValue("name", "flash animation"));
+    setStateSet(getNormalizeStateSet());
+
+    _axis[0] = configNode->getFloatValue("axis/x", 0);
+    _axis[1] = configNode->getFloatValue("axis/y", 0);
+    _axis[2] = configNode->getFloatValue("axis/z", 1);
+    _axis.normalize();
+    
+    _center[0] = configNode->getFloatValue("center/x-m", 0);
+    _center[1] = configNode->getFloatValue("center/y-m", 0);
+    _center[2] = configNode->getFloatValue("center/z-m", 0);
+    
+    _offset = configNode->getFloatValue("offset", 0);
+    _factor = configNode->getFloatValue("factor", 1);
+    _power = configNode->getFloatValue("power", 1);
+    _two_sides = configNode->getBoolValue("two-sides", false);
+    
+    _min_v = configNode->getFloatValue("min", SGLimitsf::epsilon());
+    _max_v = configNode->getFloatValue("max", 1);
+  }
+  virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,
+                                         osg::NodeVisitor* nv) const 
+  {
+    osg::Matrix transform;
+    double scale_factor = computeScaleFactor(nv);
+    transform(0,0) = scale_factor;
+    transform(1,1) = scale_factor;
+    transform(2,2) = scale_factor;
+    transform(3,0) = _center[0]*(1 - scale_factor);
+    transform(3,1) = _center[1]*(1 - scale_factor);
+    transform(3,2) = _center[2]*(1 - scale_factor);
+    matrix.preMult(transform);
+    return true;
+  }
+  
+  virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,
+                                         osg::NodeVisitor* nv) const
+  {
+    double scale_factor = computeScaleFactor(nv);
+    if (fabs(scale_factor) <= SGLimits<double>::min())
+      return false;
+    osg::Matrix transform;
+    double rScaleFactor = 1/scale_factor;
+    transform(0,0) = rScaleFactor;
+    transform(1,1) = rScaleFactor;
+    transform(2,2) = rScaleFactor;
+    transform(3,0) = _center[0]*(1 - rScaleFactor);
+    transform(3,1) = _center[1]*(1 - rScaleFactor);
+    transform(3,2) = _center[2]*(1 - rScaleFactor);
+    matrix.postMult(transform);
+    return true;
   }
 
-  if (_blend != _prev_value) {
-    _prev_value = _blend;
-    change_alpha( _branch, _blend );
+  static bool writeLocalData(const osg::Object& obj, osgDB::Output& fw)
+  {
+    const Transform& trans = static_cast<const Transform&>(obj);
+    fw.indent() << "center " << trans._center[0] << " "
+                << trans._center[1] << " " << trans._center[2] << " " << "\n";
+    fw.indent() << "axis " << trans._axis[0] << " "
+                << trans._axis[1] << " " << trans._axis[2] << " " << "\n";
+    fw.indent() << "power " << trans._power << " \n";
+    fw.indent() << "min_v " << trans._min_v << "\n";
+    fw.indent() << "max_v " << trans._max_v << "\n";
+    fw.indent() << "factor " << trans._factor << "\n";
+    fw.indent() << "offset " << trans._offset << "\n";
+    fw.indent() << "twosides " << (trans._two_sides ? "true" : "false") << "\n";
+    return true;
   }
-  return 1;
-}
+private:
+  double computeScaleFactor(osg::NodeVisitor* nv) const
+  {
+    if (!nv)
+      return 1;
+
+    osg::Vec3 localEyeToCenter = nv->getEyePoint() - _center;
+    localEyeToCenter.normalize();
+
+    double cos_angle = localEyeToCenter*_axis;
+    double scale_factor = 0;
+    if ( _two_sides && cos_angle < 0 )
+      scale_factor = _factor * pow( -cos_angle, _power ) + _offset;
+    else if ( cos_angle > 0 )
+      scale_factor = _factor * pow( cos_angle, _power ) + _offset;
+    
+    if ( scale_factor < _min_v )
+      scale_factor = _min_v;
+    if ( scale_factor > _max_v )
+      scale_factor = _max_v;
 
+    return scale_factor;
+  }
 
-\f
-////////////////////////////////////////////////////////////////////////
-// Implementation of SGTranslateAnimation
-////////////////////////////////////////////////////////////////////////
+  virtual osg::BoundingSphere computeBound() const
+  {
+    // avoid being culled away by small feature culling
+    osg::BoundingSphere bs = osg::Group::computeBound();
+    bs.radius() *= _max_v;
+    return bs;
+  }
+
+private:
+  osg::Vec3 _center;
+  osg::Vec3 _axis;
+  double _power, _factor, _offset, _min_v, _max_v;
+  bool _two_sides;
+};
 
-SGTranslateAnimation::SGTranslateAnimation( SGPropertyNode *prop_root,
-                                        SGPropertyNode_ptr props )
-  : SGAnimation(props, new ssgTransform),
-      _prop((SGPropertyNode *)prop_root->getNode(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)),
-    _condition(0)
-{
-  SGPropertyNode_ptr node = props->getChild("condition");
-  if (node != 0)
-    _condition = sgReadCondition(prop_root, node);
 
-  _axis[0] = props->getFloatValue("axis/x", 0);
-  _axis[1] = props->getFloatValue("axis/y", 0);
-  _axis[2] = props->getFloatValue("axis/z", 0);
-  sgNormalizeVec3(_axis);
+SGFlashAnimation::SGFlashAnimation(const SGPropertyNode* configNode,
+                                   SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
+{
 }
 
-SGTranslateAnimation::~SGTranslateAnimation ()
+osg::Group*
+SGFlashAnimation::createAnimationGroup(osg::Group& parent)
 {
-  delete _table;
+  Transform* transform = new Transform(getConfig());
+  parent.addChild(transform);
+  return transform;
 }
 
-int
-SGTranslateAnimation::update()
+namespace
 {
-  if (_condition == 0 || _condition->test()) {
-    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;
+  osgDB::RegisterDotOsgWrapperProxy flashAnimationTransformProxy
+  (
+   new SGFlashAnimation::Transform,
+   "SGFlashAnimation::Transform",
+   "Object Node Transform SGFlashAnimation::Transform Group",
+   0,
+   &SGFlashAnimation::Transform::writeLocalData
+   );
+}
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of billboard animation
+////////////////////////////////////////////////////////////////////////
+
+class SGBillboardAnimation::Transform : public osg::Transform {
+public:
+  Transform() : _spherical(true) {}
+  Transform(const Transform& rhs,
+            const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
+    : osg::Transform(rhs, copyOp), _spherical(rhs._spherical) {}
+  META_Node(simgear, SGBillboardAnimation::Transform);
+  Transform(const SGPropertyNode* configNode) :
+    _spherical(configNode->getBoolValue("spherical", true))
+  {
+    setReferenceFrame(RELATIVE_RF);
+    setName(configNode->getStringValue("name", "billboard animation"));
+  }
+  virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,
+                                         osg::NodeVisitor* nv) const 
+  {
+    // More or less taken from plibs ssgCutout
+    if (_spherical) {
+      matrix(0,0) = 1; matrix(0,1) = 0; matrix(0,2) = 0;
+      matrix(1,0) = 0; matrix(1,1) = 0; matrix(1,2) = -1;
+      matrix(2,0) = 0; matrix(2,1) = 1; matrix(2,2) = 0;
     } else {
-      _position_m = _table->interpolate(_prop->getDoubleValue());
+      osg::Vec3 zAxis(matrix(2, 0), matrix(2, 1), matrix(2, 2));
+      osg::Vec3 xAxis = osg::Vec3(0, 0, -1)^zAxis;
+      osg::Vec3 yAxis = zAxis^xAxis;
+      
+      xAxis.normalize();
+      yAxis.normalize();
+      zAxis.normalize();
+      
+      matrix(0,0) = xAxis[0]; matrix(0,1) = xAxis[1]; matrix(0,2) = xAxis[2];
+      matrix(1,0) = yAxis[0]; matrix(1,1) = yAxis[1]; matrix(1,2) = yAxis[2];
+      matrix(2,0) = zAxis[0]; matrix(2,1) = zAxis[1]; matrix(2,2) = zAxis[2];
     }
-    set_translation(_matrix, _position_m, _axis);
-    ((ssgTransform *)_branch)->setTransform(_matrix);
+    return true;
+  }
+  
+  virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,
+                                         osg::NodeVisitor* nv) const
+  {
+    // Hmm, don't yet know how to get that back ...
+    return false;
+  }
+  static bool writeLocalData(const osg::Object& obj, osgDB::Output& fw)
+  {
+    const Transform& trans = static_cast<const Transform&>(obj);
+
+    fw.indent() << (trans._spherical ? "true" : "false") << "\n";
+    return true;
   }
-  return 1;
+private:
+  bool _spherical;
+};
+
+
+SGBillboardAnimation::SGBillboardAnimation(const SGPropertyNode* configNode,
+                                           SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
+{
 }
 
+osg::Group*
+SGBillboardAnimation::createAnimationGroup(osg::Group& parent)
+{
+  Transform* transform = new Transform(getConfig());
+  parent.addChild(transform);
+  return transform;
+}
 
+namespace
+{
+  osgDB::RegisterDotOsgWrapperProxy billboardAnimationTransformProxy
+  (
+   new SGBillboardAnimation::Transform,
+   "SGBillboardAnimation::Transform",
+   "Object Node Transform SGBillboardAnimation::Transform Group",
+   0,
+   &SGBillboardAnimation::Transform::writeLocalData
+   );
+}
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of SGScaleAnimation
+// Implementation of a range animation
 ////////////////////////////////////////////////////////////////////////
 
-SGScaleAnimation::SGScaleAnimation( SGPropertyNode *prop_root,
-                                        SGPropertyNode_ptr props )
-  : SGAnimation(props, new ssgTransform),
-      _prop((SGPropertyNode *)prop_root->getNode(props->getStringValue("property", "/null"), true)),
-    _x_factor(props->getDoubleValue("x-factor", 1.0)),
-    _y_factor(props->getDoubleValue("y-factor", 1.0)),
-    _z_factor(props->getDoubleValue("z-factor", 1.0)),
-    _x_offset(props->getDoubleValue("x-offset", 1.0)),
-    _y_offset(props->getDoubleValue("y-offset", 1.0)),
-    _z_offset(props->getDoubleValue("z-offset", 1.0)),
-    _table(read_interpolation_table(props)),
-    _has_min_x(props->hasValue("x-min")),
-    _has_min_y(props->hasValue("y-min")),
-    _has_min_z(props->hasValue("z-min")),
-    _min_x(props->getDoubleValue("x-min")),
-    _min_y(props->getDoubleValue("y-min")),
-    _min_z(props->getDoubleValue("z-min")),
-    _has_max_x(props->hasValue("x-max")),
-    _has_max_y(props->hasValue("y-max")),
-    _has_max_z(props->hasValue("z-max")),
-    _max_x(props->getDoubleValue("x-max")),
-    _max_y(props->getDoubleValue("y-max")),
-    _max_z(props->getDoubleValue("z-max"))
-{
-}
-
-SGScaleAnimation::~SGScaleAnimation ()
-{
-  delete _table;
-}
-
-int
-SGScaleAnimation::update()
-{
-  if (_table == 0) {
-      _x_scale = _prop->getDoubleValue() * _x_factor + _x_offset;
-    if (_has_min_x && _x_scale < _min_x)
-      _x_scale = _min_x;
-    if (_has_max_x && _x_scale > _max_x)
-      _x_scale = _max_x;
-  } else {
-    _x_scale = _table->interpolate(_prop->getDoubleValue());
+class SGRangeAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+  UpdateCallback(const SGCondition* condition,
+                 const SGExpressiond* minAnimationValue,
+                 const SGExpressiond* maxAnimationValue,
+                 double minValue, double maxValue) :
+    _condition(condition),
+    _minAnimationValue(minAnimationValue),
+    _maxAnimationValue(maxAnimationValue),
+    _minStaticValue(minValue),
+    _maxStaticValue(maxValue)
+  {}
+  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+  {
+    osg::LOD* lod = static_cast<osg::LOD*>(node);
+    if (!_condition || _condition->test()) {
+      double minRange;
+      if (_minAnimationValue)
+        minRange = _minAnimationValue->getValue();
+      else
+        minRange = _minStaticValue;
+      double maxRange;
+      if (_maxAnimationValue)
+        maxRange = _maxAnimationValue->getValue();
+      else
+        maxRange = _maxStaticValue;
+      lod->setRange(0, minRange, maxRange);
+    } else {
+      lod->setRange(0, 0, SGLimitsf::max());
+    }
+    traverse(node, nv);
   }
 
-  if (_table == 0) {
-    _y_scale = _prop->getDoubleValue() * _y_factor + _y_offset;
-    if (_has_min_y && _y_scale < _min_y)
-      _y_scale = _min_y;
-    if (_has_max_y && _y_scale > _max_y)
-      _y_scale = _max_y;
-  } else {
-    _y_scale = _table->interpolate(_prop->getDoubleValue());
+private:
+  SGSharedPtr<const SGCondition> _condition;
+  SGSharedPtr<const SGExpressiond> _minAnimationValue;
+  SGSharedPtr<const SGExpressiond> _maxAnimationValue;
+  double _minStaticValue;
+  double _maxStaticValue;
+};
+
+SGRangeAnimation::SGRangeAnimation(const SGPropertyNode* configNode,
+                                   SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
+{
+  _condition = getCondition();
+
+  std::string inputPropertyName;
+  inputPropertyName = configNode->getStringValue("min-property", "");
+  if (!inputPropertyName.empty()) {
+    SGPropertyNode* inputProperty;
+    inputProperty = modelRoot->getNode(inputPropertyName, true);
+    SGSharedPtr<SGExpressiond> value;
+    value = new SGPropertyExpression<double>(inputProperty);
+
+    value = read_factor_offset(configNode, value, "min-factor", "min-offset");
+    _minAnimationValue = value->simplify();
   }
+  inputPropertyName = configNode->getStringValue("max-property", "");
+  if (!inputPropertyName.empty()) {
+    SGPropertyNode* inputProperty;
+    inputProperty = modelRoot->getNode(inputPropertyName.c_str(), true);
 
-  if (_table == 0) {
-    _z_scale = _prop->getDoubleValue() * _z_factor + _z_offset;
-    if (_has_min_z && _z_scale < _min_z)
-      _z_scale = _min_z;
-    if (_has_max_z && _z_scale > _max_z)
-      _z_scale = _max_z;
-  } else {
-    _z_scale = _table->interpolate(_prop->getDoubleValue());
+    SGSharedPtr<SGExpressiond> value;
+    value = new SGPropertyExpression<double>(inputProperty);
+
+    value = read_factor_offset(configNode, value, "max-factor", "max-offset");
+    _maxAnimationValue = value->simplify();
   }
 
-  set_scale(_matrix, _x_scale, _y_scale, _z_scale );
-  ((ssgTransform *)_branch)->setTransform(_matrix);
-  return 1;
+  _initialValue[0] = configNode->getDoubleValue("min-m", 0);
+  _initialValue[0] *= configNode->getDoubleValue("min-factor", 1);
+  _initialValue[1] = configNode->getDoubleValue("max-m", SGLimitsf::max());
+  _initialValue[1] *= configNode->getDoubleValue("max-factor", 1);
 }
 
+osg::Group*
+SGRangeAnimation::createAnimationGroup(osg::Group& parent)
+{
+  osg::Group* group = new osg::Group;
+  group->setName("range animation group");
+
+  osg::LOD* lod = new osg::LOD;
+  lod->setName("range animation node");
+  parent.addChild(lod);
+
+  lod->addChild(group, _initialValue[0], _initialValue[1]);
+  lod->setCenterMode(osg::LOD::USE_BOUNDING_SPHERE_CENTER);
+  lod->setRangeMode(osg::LOD::DISTANCE_FROM_EYE_POINT);
+  if (_minAnimationValue || _maxAnimationValue || _condition) {
+    UpdateCallback* uc;
+    uc = new UpdateCallback(_condition, _minAnimationValue, _maxAnimationValue,
+                            _initialValue[0], _initialValue[1]);
+    lod->setUpdateCallback(uc);
+  }
+  return group;
+}
 
+\f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of SGTexRotateAnimation
+// Implementation of a select animation
 ////////////////////////////////////////////////////////////////////////
 
-SGTexRotateAnimation::SGTexRotateAnimation( SGPropertyNode *prop_root,
-                                  SGPropertyNode_ptr props )
-    : SGAnimation(props, new ssgTexTrans),
-      _prop((SGPropertyNode *)prop_root->getNode(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", 0);
-  _center[1] = props->getFloatValue("center/y", 0);
-  _center[2] = props->getFloatValue("center/z", 0);
-  _axis[0] = props->getFloatValue("axis/x", 0);
-  _axis[1] = props->getFloatValue("axis/y", 0);
-  _axis[2] = props->getFloatValue("axis/z", 0);
-  sgNormalizeVec3(_axis);
-}
-
-SGTexRotateAnimation::~SGTexRotateAnimation ()
-{
-  delete _table;
-}
-
-int
-SGTexRotateAnimation::update()
-{
-  if (_table == 0) {
-   _position_deg = _prop->getDoubleValue() * _factor + _offset_deg;
-   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());
-  }
-  set_rotation(_matrix, _position_deg, _center, _axis);
-  ((ssgTexTrans *)_branch)->setTransform(_matrix);
-  return 1;
+SGSelectAnimation::SGSelectAnimation(const SGPropertyNode* configNode,
+                                     SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
+{
 }
 
+osg::Group*
+SGSelectAnimation::createAnimationGroup(osg::Group& parent)
+{
+  // if no condition given, this is a noop.
+  SGSharedPtr<SGCondition const> condition = getCondition();
+  // trick, gets deleted with all its 'animated' children
+  // when the animation installer returns
+  if (!condition)
+    return new osg::Group;
+  simgear::ConditionNode* cn = new simgear::ConditionNode;
+  cn->setName("select animation node");
+  cn->setCondition(condition.ptr());
+  osg::Group* grp = new osg::Group;
+  cn->addChild(grp);
+  parent.addChild(cn);
+  return grp;
+}
 
+
+\f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of SGTexTranslateAnimation
+// Implementation of alpha test animation
 ////////////////////////////////////////////////////////////////////////
 
-SGTexTranslateAnimation::SGTexTranslateAnimation( SGPropertyNode *prop_root,
-                                        SGPropertyNode_ptr props )
-  : SGAnimation(props, new ssgTexTrans),
-      _prop((SGPropertyNode *)prop_root->getNode(props->getStringValue("property", "/null"), true)),
-    _offset(props->getDoubleValue("offset", 0.0)),
-    _factor(props->getDoubleValue("factor", 1.0)),
-    _step(props->getDoubleValue("step",0.0)),
-    _scroll(props->getDoubleValue("scroll",0.0)),
-    _table(read_interpolation_table(props)),
-    _has_min(props->hasValue("min")),
-    _min(props->getDoubleValue("min")),
-    _has_max(props->hasValue("max")),
-    _max(props->getDoubleValue("max")),
-    _position(props->getDoubleValue("starting-position", 0))
-{
-  _axis[0] = props->getFloatValue("axis/x", 0);
-  _axis[1] = props->getFloatValue("axis/y", 0);
-  _axis[2] = props->getFloatValue("axis/z", 0);
-  sgNormalizeVec3(_axis);
-}
-
-SGTexTranslateAnimation::~SGTexTranslateAnimation ()
-{
-  delete _table;
-}
-
-int
-SGTexTranslateAnimation::update()
-{
-  if (_table == 0) {
-    _position = (apply_mods(_prop->getDoubleValue(), _step, _scroll) + _offset) * _factor;
-    if (_has_min && _position < _min)
-      _position = _min;
-    if (_has_max && _position > _max)
-      _position = _max;
-  } else {
-    _position = _table->interpolate(apply_mods(_prop->getDoubleValue(), _step, _scroll));
-  }
-  set_translation(_matrix, _position, _axis);
-  ((ssgTexTrans *)_branch)->setTransform(_matrix);
-  return 1;
+SGAlphaTestAnimation::SGAlphaTestAnimation(const SGPropertyNode* configNode,
+                                           SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
+{
 }
 
+namespace
+{
+// Keep one copy of the most common alpha test its state set.
+ReentrantMutex alphaTestMutex;
+osg::ref_ptr<osg::AlphaFunc> standardAlphaFunc;
+osg::ref_ptr<osg::StateSet> alphaFuncStateSet;
 
-////////////////////////////////////////////////////////////////////////
-// Implementation of SGTexMultipleAnimation
-////////////////////////////////////////////////////////////////////////
+osg::AlphaFunc* makeAlphaFunc(float clamp)
+{
+    ScopedLock<ReentrantMutex> lock(alphaTestMutex);
+    if (osg::equivalent(clamp, 0.01f)) {
+        if (standardAlphaFunc.valid())
+            return standardAlphaFunc.get();
+        clamp = .01;
+    }
+    osg::AlphaFunc* alphaFunc = new osg::AlphaFunc;
+    alphaFunc->setFunction(osg::AlphaFunc::GREATER);
+    alphaFunc->setReferenceValue(clamp);
+    alphaFunc->setDataVariance(osg::Object::STATIC);
+    if (osg::equivalent(clamp, 0.01f))
+        standardAlphaFunc = alphaFunc;
+    return alphaFunc;
+}
 
-SGTexMultipleAnimation::SGTexMultipleAnimation( SGPropertyNode *prop_root,
-                                        SGPropertyNode_ptr props )
-  : SGAnimation(props, new ssgTexTrans),
-      _prop((SGPropertyNode *)prop_root->getNode(props->getStringValue("property", "/null"), true))
-{
-  unsigned int i;
-  // Load animations
-  vector<SGPropertyNode_ptr> transform_nodes = props->getChildren("transform");
-  _transform = new TexTransform [transform_nodes.size()];
-  _num_transforms = 0;
-  for (i = 0; i < transform_nodes.size(); i++) {
-    SGPropertyNode_ptr transform_props = transform_nodes[i];
-
-    if (!strcmp("textranslate",transform_props->getStringValue("subtype", 0))) {
-
-      // transform is a translation
-      _transform[i].subtype = 0;
-
-      _transform[i].prop = (SGPropertyNode *)prop_root->getNode(transform_props->getStringValue("property", "/null"), true);
-
-      _transform[i].offset = transform_props->getDoubleValue("offset", 0.0);
-      _transform[i].factor = transform_props->getDoubleValue("factor", 1.0);
-      _transform[i].step = transform_props->getDoubleValue("step",0.0);
-      _transform[i].scroll = transform_props->getDoubleValue("scroll",0.0);
-      _transform[i].table = read_interpolation_table(transform_props);
-      _transform[i].has_min = transform_props->hasValue("min");
-      _transform[i].min = transform_props->getDoubleValue("min");
-      _transform[i].has_max = transform_props->hasValue("max");
-      _transform[i].max = transform_props->getDoubleValue("max");
-      _transform[i].position = transform_props->getDoubleValue("starting-position", 0);
-
-      _transform[i].axis[0] = transform_props->getFloatValue("axis/x", 0);
-      _transform[i].axis[1] = transform_props->getFloatValue("axis/y", 0);
-      _transform[i].axis[2] = transform_props->getFloatValue("axis/z", 0);
-      sgNormalizeVec3(_transform[i].axis);
-      _num_transforms++;
-    } else if (!strcmp("texrotate",transform_nodes[i]->getStringValue("subtype", 0))) {
-
-      // transform is a rotation
-      _transform[i].subtype = 1;
-
-      _transform[i].prop = (SGPropertyNode *)prop_root->getNode(transform_props->getStringValue("property", "/null"), true);
-      _transform[i].offset = transform_props->getDoubleValue("offset-deg", 0.0);
-      _transform[i].factor = transform_props->getDoubleValue("factor", 1.0);
-      _transform[i].table = read_interpolation_table(transform_props);
-      _transform[i].has_min = transform_props->hasValue("min-deg");
-      _transform[i].min = transform_props->getDoubleValue("min-deg");
-      _transform[i].has_max = transform_props->hasValue("max-deg");
-      _transform[i].max = transform_props->getDoubleValue("max-deg");
-      _transform[i].position = transform_props->getDoubleValue("starting-position-deg", 0);
-
-      _transform[i].center[0] = transform_props->getFloatValue("center/x", 0);
-      _transform[i].center[1] = transform_props->getFloatValue("center/y", 0);
-      _transform[i].center[2] = transform_props->getFloatValue("center/z", 0);
-      _transform[i].axis[0] = transform_props->getFloatValue("axis/x", 0);
-      _transform[i].axis[1] = transform_props->getFloatValue("axis/y", 0);
-      _transform[i].axis[2] = transform_props->getFloatValue("axis/z", 0);
-      sgNormalizeVec3(_transform[i].axis);
-      _num_transforms++;
+osg::StateSet* makeAlphaTestStateSet(float clamp)
+{
+    using namespace OpenThreads;
+    ScopedLock<ReentrantMutex> lock(alphaTestMutex);
+    if (osg::equivalent(clamp, 0.01f)) {
+        if (alphaFuncStateSet.valid())
+            return alphaFuncStateSet.get();
     }
+    osg::AlphaFunc* alphaFunc = makeAlphaFunc(clamp);
+    osg::StateSet* stateSet = new osg::StateSet;
+    stateSet->setAttributeAndModes(alphaFunc,
+                                   (osg::StateAttribute::ON
+                                    | osg::StateAttribute::OVERRIDE));
+    stateSet->setDataVariance(osg::Object::STATIC);
+    if (osg::equivalent(clamp, 0.01f))
+        alphaFuncStateSet = stateSet;
+    return stateSet;
+}
+}
+void
+SGAlphaTestAnimation::install(osg::Node& node)
+{
+  SGAnimation::install(node);
+
+  float alphaClamp = getConfig()->getFloatValue("alpha-factor", 0);
+  osg::StateSet* stateSet = node.getStateSet();
+  if (!stateSet) {
+      node.setStateSet(makeAlphaTestStateSet(alphaClamp));
+  } else {
+      stateSet->setAttributeAndModes(makeAlphaFunc(alphaClamp),
+                                     (osg::StateAttribute::ON
+                                      | osg::StateAttribute::OVERRIDE));
   }
 }
 
-SGTexMultipleAnimation::~SGTexMultipleAnimation ()
+\f
+//////////////////////////////////////////////////////////////////////
+// Blend animation installer
+//////////////////////////////////////////////////////////////////////
+
+// XXX This needs to be replaced by something using TexEnvCombine to
+// change the blend factor. Changing the alpha values in the geometry
+// is bogus.
+class SGBlendAnimation::BlendVisitor : public osg::NodeVisitor {
+public:
+  BlendVisitor(float blend) :
+    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+    _blend(blend)
+  { setVisitorType(osg::NodeVisitor::NODE_VISITOR); }
+  virtual void apply(osg::Node& node)
+  {
+    updateStateSet(node.getStateSet());
+    traverse(node);
+  }
+  virtual void apply(osg::Geode& node)
+  {
+    apply((osg::Node&)node);
+    unsigned nDrawables = node.getNumDrawables();
+    for (unsigned i = 0; i < nDrawables; ++i) {
+      osg::Drawable* drawable = node.getDrawable(i);
+      osg::Geometry* geometry = drawable->asGeometry();
+      if (!geometry)
+        continue;
+      osg::Array* array = geometry->getColorArray();
+      if (!array)
+        continue;
+      osg::Vec4Array* vec4Array = dynamic_cast<osg::Vec4Array*>(array);
+      if (!vec4Array)
+        continue;
+      for (unsigned k = 0; k < vec4Array->size(); ++k) {
+        (*vec4Array)[k][3] = _blend;
+      }
+      vec4Array->dirty();
+      updateStateSet(drawable->getStateSet());
+    }
+  }
+  void updateStateSet(osg::StateSet* stateSet)
+  {
+    if (!stateSet)
+      return;
+    osg::StateAttribute* stateAttribute;
+    stateAttribute = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
+    if (!stateAttribute)
+      return;
+    osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
+    if (!material)
+      return;
+    material->setAlpha(osg::Material::FRONT_AND_BACK, _blend);
+    if (_blend < 1) {
+      stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
+      stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
+    } else {
+      stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
+    }
+  }
+private:
+  float _blend;
+};
+
+class SGBlendAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+  UpdateCallback(const SGPropertyNode* configNode, const SGExpressiond* v) :
+    _prev_value(-1),
+    _animationValue(v)
+  { }
+  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+  {
+    double blend = _animationValue->getValue();
+    if (blend != _prev_value) {
+      _prev_value = blend;
+      BlendVisitor visitor(1-blend);
+      node->accept(visitor);
+    }
+    traverse(node, nv);
+  }
+public:
+  double _prev_value;
+  SGSharedPtr<SGExpressiond const> _animationValue;
+};
+
+
+SGBlendAnimation::SGBlendAnimation(const SGPropertyNode* configNode,
+                                   SGPropertyNode* modelRoot)
+  : SGAnimation(configNode, modelRoot),
+    _animationValue(read_value(configNode, modelRoot, "", 0, 1))
 {
-  // delete _table;
-  delete _transform;
 }
 
-int
-SGTexMultipleAnimation::update()
+osg::Group*
+SGBlendAnimation::createAnimationGroup(osg::Group& parent)
 {
-  int i;
-  sgMat4 tmatrix;
-  sgMakeIdentMat4(tmatrix);
-  for (i = 0; i < _num_transforms; i++) {
+  if (!_animationValue)
+    return 0;
 
-    if(_transform[i].subtype == 0) {
+  osg::Group* group = new osg::Switch;
+  group->setName("blend animation node");
+  group->setUpdateCallback(new UpdateCallback(getConfig(), _animationValue));
+  parent.addChild(group);
+  return group;
+}
 
-      // subtype 0 is translation
-      if (_transform[i].table == 0) {
-        _transform[i].position = (apply_mods(_transform[i].prop->getDoubleValue(), _transform[i].step,_transform[i].scroll) + _transform[i].offset) * _transform[i].factor;
-        if (_transform[i].has_min && _transform[i].position < _transform[i].min)
-          _transform[i].position = _transform[i].min;
-        if (_transform[i].has_max && _transform[i].position > _transform[i].max)
-          _transform[i].position = _transform[i].max;
+void
+SGBlendAnimation::install(osg::Node& node)
+{
+  SGAnimation::install(node);
+  // make sure we do not change common geometries,
+  // that also creates new display lists for these subgeometries.
+  cloneDrawables(node);
+  DoDrawArraysVisitor visitor;
+  node.accept(visitor);
+}
+
+\f
+//////////////////////////////////////////////////////////////////////
+// Timed animation installer
+//////////////////////////////////////////////////////////////////////
+
+
+
+class SGTimedAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+  UpdateCallback(const SGPropertyNode* configNode) :
+    _current_index(0),
+    _reminder(0),
+    _duration_sec(configNode->getDoubleValue("duration-sec", 1)),
+    _last_time_sec(SGLimitsd::max()),
+    _use_personality(configNode->getBoolValue("use-personality", false))
+  {
+    std::vector<SGSharedPtr<SGPropertyNode> > nodes;
+    nodes = configNode->getChildren("branch-duration-sec");
+    for (size_t i = 0; i < nodes.size(); ++i) {
+      unsigned ind = nodes[ i ]->getIndex();
+      while ( ind >= _durations.size() ) {
+        _durations.push_back(DurationSpec(_duration_sec));
+      }
+      SGPropertyNode_ptr rNode = nodes[i]->getChild("random");
+      if ( rNode == 0 ) {
+        _durations[ind] = DurationSpec(nodes[ i ]->getDoubleValue());
       } else {
-         _transform[i].position = _transform[i].table->interpolate(apply_mods(_transform[i].prop->getDoubleValue(), _transform[i].step,_transform[i].scroll));
+        _durations[ind] = DurationSpec(rNode->getDoubleValue( "min", 0),
+                                       rNode->getDoubleValue( "max", 1));
       }
-      set_translation(_transform[i].matrix, _transform[i].position, _transform[i].axis);
-      sgPreMultMat4(tmatrix, _transform[i].matrix);
+    }
+  }
+  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+  {
+    assert(dynamic_cast<osg::Switch*>(node));
+    osg::Switch* sw = static_cast<osg::Switch*>(node);
 
-    } else if (_transform[i].subtype == 1) {
+    unsigned nChildren = sw->getNumChildren();
 
-      // subtype 1 is rotation
+    // blow up the durations vector to the required size
+    while (_durations.size() < nChildren) {
+      _durations.push_back(_duration_sec);
+    }
+    // make sure the current index is an duration that really exists
+    _current_index = _current_index % nChildren;
 
-      if (_transform[i].table == 0) {
-        _transform[i].position = _transform[i].prop->getDoubleValue() * _transform[i].factor + _transform[i].offset;
-        if (_transform[i].has_min && _transform[i].position < _transform[i].min)
-         _transform[i].position = _transform[i].min;
-       if (_transform[i].has_max && _transform[i].position > _transform[i].max)
-         _transform[i].position = _transform[i].max;
-     } else {
-        _transform[i].position = _transform[i].table->interpolate(_transform[i].prop->getDoubleValue());
-      }
-      set_rotation(_transform[i].matrix, _transform[i].position, _transform[i].center, _transform[i].axis);
-      sgPreMultMat4(tmatrix, _transform[i].matrix);
+    // update the time and compute the current systems time value
+    double t = nv->getFrameStamp()->getReferenceTime();
+    if (_last_time_sec == SGLimitsd::max()) {
+      _last_time_sec = t;
+    } else {
+      double dt = t - _last_time_sec;
+      if (_use_personality)
+        dt *= 1 + 0.2*(0.5 - sg_random());
+      _reminder += dt;
+      _last_time_sec = t;
+    }
+
+    double currentDuration = _durations[_current_index].get();
+    while (currentDuration < _reminder) {
+      _reminder -= currentDuration;
+      _current_index = (_current_index + 1) % nChildren;
+      currentDuration = _durations[_current_index].get();
     }
+
+    sw->setSingleChildOn(_current_index);
+
+    traverse(node, nv);
+  }
+
+private:
+  struct DurationSpec {
+    DurationSpec(double t) :
+      minTime(SGMiscd::max(0.01, t)),
+      maxTime(SGMiscd::max(0.01, t))
+    {}
+    DurationSpec(double t0, double t1) :
+      minTime(SGMiscd::max(0.01, t0)),
+      maxTime(SGMiscd::max(0.01, t1))
+    {}
+    double get() const
+    { return minTime + sg_random()*(maxTime - minTime); }
+    double minTime;
+    double maxTime;
+  };
+  std::vector<DurationSpec> _durations;
+  unsigned _current_index;
+  double _reminder;
+  double _duration_sec;
+  double _last_time_sec;
+  bool _use_personality;
+};
+
+
+SGTimedAnimation::SGTimedAnimation(const SGPropertyNode* configNode,
+                                   SGPropertyNode* modelRoot)
+  : SGAnimation(configNode, modelRoot)
+{
+}
+
+osg::Group*
+SGTimedAnimation::createAnimationGroup(osg::Group& parent)
+{
+  osg::Switch* sw = new osg::Switch;
+  sw->setName("timed animation node");
+  sw->setUpdateCallback(new UpdateCallback(getConfig()));
+  parent.addChild(sw);
+  return sw;
+}
+
+\f
+////////////////////////////////////////////////////////////////////////
+// dynamically switch on/off shadows
+////////////////////////////////////////////////////////////////////////
+
+class SGShadowAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+  UpdateCallback(const SGCondition* condition) :
+    _condition(condition)
+  {}
+  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+  {
+    if (_condition->test())
+      node->setNodeMask( SG_NODEMASK_CASTSHADOW_BIT | node->getNodeMask());
+    else
+      node->setNodeMask(~SG_NODEMASK_CASTSHADOW_BIT & node->getNodeMask());
+    traverse(node, nv);
   }
-  ((ssgTexTrans *)_branch)->setTransform(tmatrix);
-  return 1;
+
+private:
+  SGSharedPtr<const SGCondition> _condition;
+};
+
+SGShadowAnimation::SGShadowAnimation(const SGPropertyNode* configNode,
+                                     SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
+{
 }
 
+osg::Group*
+SGShadowAnimation::createAnimationGroup(osg::Group& parent)
+{
+  SGSharedPtr<SGCondition const> condition = getCondition();
+  if (!condition)
+    return 0;
+
+  osg::Group* group = new osg::Group;
+  group->setName("shadow animation");
+  group->setUpdateCallback(new UpdateCallback(condition));
+  parent.addChild(group);
+  return group;
+}
 
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of SGAlphaTestAnimation
+// Implementation of SGTexTransformAnimation
 ////////////////////////////////////////////////////////////////////////
 
-SGAlphaTestAnimation::SGAlphaTestAnimation(SGPropertyNode_ptr props)
-  : SGAnimation(props, new ssgBranch)
+class SGTexTransformAnimation::Transform : public SGReferenced {
+public:
+  Transform() :
+    _value(0)
+  {}
+  virtual ~Transform()
+  { }
+  void setValue(double value)
+  { _value = value; }
+  virtual void transform(osg::Matrix&) = 0;
+protected:
+  double _value;
+};
+
+class SGTexTransformAnimation::Translation :
+  public SGTexTransformAnimation::Transform {
+public:
+  Translation(const SGVec3d& axis) :
+    _axis(axis)
+  { }
+  virtual void transform(osg::Matrix& matrix)
+  {
+    osg::Matrix tmp;
+    set_translation(tmp, _value, _axis);
+    matrix.preMult(tmp);
+  }
+private:
+  SGVec3d _axis;
+};
+
+class SGTexTransformAnimation::Rotation :
+  public SGTexTransformAnimation::Transform {
+public:
+  Rotation(const SGVec3d& axis, const SGVec3d& center) :
+    _axis(axis),
+    _center(center)
+  { }
+  virtual void transform(osg::Matrix& matrix)
+  {
+    osg::Matrix tmp;
+    set_rotation(tmp, _value, _center, _axis);
+    matrix.preMult(tmp);
+  }
+private:
+  SGVec3d _axis;
+  SGVec3d _center;
+};
+
+class SGTexTransformAnimation::UpdateCallback :
+  public osg::StateAttribute::Callback {
+public:
+  UpdateCallback(const SGCondition* condition) :
+    _condition(condition)
+  { }
+  virtual void operator () (osg::StateAttribute* sa, osg::NodeVisitor*)
+  {
+    if (!_condition || _condition->test()) {
+      TransformList::const_iterator i;
+      for (i = _transforms.begin(); i != _transforms.end(); ++i)
+        i->transform->setValue(i->value->getValue());
+    }
+    assert(dynamic_cast<osg::TexMat*>(sa));
+    osg::TexMat* texMat = static_cast<osg::TexMat*>(sa);
+    texMat->getMatrix().makeIdentity();
+    TransformList::const_iterator i;
+    for (i = _transforms.begin(); i != _transforms.end(); ++i)
+      i->transform->transform(texMat->getMatrix());
+  }
+  void appendTransform(Transform* transform, SGExpressiond* value)
+  {
+    Entry entry = { transform, value };
+    transform->transform(_matrix);
+    _transforms.push_back(entry);
+  }
+
+private:
+  struct Entry {
+    SGSharedPtr<Transform> transform;
+    SGSharedPtr<const SGExpressiond> value;
+  };
+  typedef std::vector<Entry> TransformList;
+  TransformList _transforms;
+  SGSharedPtr<const SGCondition> _condition;
+  osg::Matrix _matrix;
+};
+
+SGTexTransformAnimation::SGTexTransformAnimation(const SGPropertyNode* configNode,
+                                                 SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
 {
-  _alpha_clamp = props->getFloatValue("alpha-factor", 0.0);
 }
 
-SGAlphaTestAnimation::~SGAlphaTestAnimation ()
+osg::Group*
+SGTexTransformAnimation::createAnimationGroup(osg::Group& parent)
 {
+  osg::Group* group = new osg::Group;
+  group->setName("texture transform group");
+  osg::StateSet* stateSet = group->getOrCreateStateSet();
+  stateSet->setDataVariance(osg::Object::DYNAMIC);  
+  osg::TexMat* texMat = new osg::TexMat;
+  UpdateCallback* updateCallback = new UpdateCallback(getCondition());
+  // interpret the configs ...
+  std::string type = getType();
+
+  if (type == "textranslate") {
+    appendTexTranslate(getConfig(), updateCallback);
+  } else if (type == "texrotate") {
+    appendTexRotate(getConfig(), updateCallback);
+  } else if (type == "texmultiple") {
+    std::vector<SGSharedPtr<SGPropertyNode> > transformConfigs;
+    transformConfigs = getConfig()->getChildren("transform");
+    for (unsigned i = 0; i < transformConfigs.size(); ++i) {
+      std::string subtype = transformConfigs[i]->getStringValue("subtype", "");
+      if (subtype == "textranslate")
+        appendTexTranslate(transformConfigs[i], updateCallback);
+      else if (subtype == "texrotate")
+        appendTexRotate(transformConfigs[i], updateCallback);
+      else
+        SG_LOG(SG_INPUT, SG_ALERT,
+               "Ignoring unknown texture transform subtype");
+    }
+  } else {
+    SG_LOG(SG_INPUT, SG_ALERT, "Ignoring unknown texture transform type");
+  }
+
+  texMat->setUpdateCallback(updateCallback);
+  stateSet->setTextureAttribute(0, texMat);
+  parent.addChild(group);
+  return group;
 }
 
-void SGAlphaTestAnimation::init()
+void
+SGTexTransformAnimation::appendTexTranslate(const SGPropertyNode* config,
+                                            UpdateCallback* updateCallback)
 {
-  setAlphaClampToBranch(_branch,_alpha_clamp);
+  std::string propertyName = config->getStringValue("property", "");
+  SGSharedPtr<SGExpressiond> value;
+  if (propertyName.empty())
+    value = new SGConstExpression<double>(0);
+  else {
+    SGPropertyNode* inputProperty = getModelRoot()->getNode(propertyName, true);
+    value = new SGPropertyExpression<double>(inputProperty);
+  }
+
+  SGInterpTable* table = read_interpolation_table(config);
+  if (table) {
+    value = new SGInterpTableExpression<double>(value, table);
+    double biasValue = config->getDoubleValue("bias", 0);
+    if (biasValue != 0)
+      value = new SGBiasExpression<double>(value, biasValue);
+    value = new SGStepExpression<double>(value,
+                                         config->getDoubleValue("step", 0),
+                                         config->getDoubleValue("scroll", 0));
+    value = value->simplify();
+  } else {
+    double biasValue = config->getDoubleValue("bias", 0);
+    if (biasValue != 0)
+      value = new SGBiasExpression<double>(value, biasValue);
+    value = new SGStepExpression<double>(value,
+                                         config->getDoubleValue("step", 0),
+                                         config->getDoubleValue("scroll", 0));
+    value = read_offset_factor(config, value, "factor", "offset");
+
+    if (config->hasChild("min") || config->hasChild("max")) {
+      double minClip = config->getDoubleValue("min", -SGLimitsd::max());
+      double maxClip = config->getDoubleValue("max", SGLimitsd::max());
+      value = new SGClipExpression<double>(value, minClip, maxClip);
+    }
+    value = value->simplify();
+  }
+  SGVec3d axis(config->getDoubleValue("axis/x", 0),
+               config->getDoubleValue("axis/y", 0),
+               config->getDoubleValue("axis/z", 0));
+  Translation* translation;
+  translation = new Translation(normalize(axis));
+  translation->setValue(config->getDoubleValue("starting-position", 0));
+  updateCallback->appendTransform(translation, value);
 }
 
-void SGAlphaTestAnimation::setAlphaClampToBranch(ssgBranch *b, float clamp)
+void
+SGTexTransformAnimation::appendTexRotate(const SGPropertyNode* config,
+                                         UpdateCallback* updateCallback)
 {
-  int nb = b->getNumKids();
-  for (int i = 0; i<nb; i++) {
-    ssgEntity *e = b->getKid(i);
-    if (e->isAKindOf(ssgTypeLeaf())) {
-      ssgSimpleState*s = (ssgSimpleState*)((ssgLeaf*)e)->getState();
-      s->enable( GL_ALPHA_TEST );
-      s->setAlphaClamp( clamp );
-    } else if (e->isAKindOf(ssgTypeBranch())) {
-      setAlphaClampToBranch( (ssgBranch*)e, clamp );
+  std::string propertyName = config->getStringValue("property", "");
+  SGSharedPtr<SGExpressiond> value;
+  if (propertyName.empty())
+    value = new SGConstExpression<double>(0);
+  else {
+    SGPropertyNode* inputProperty = getModelRoot()->getNode(propertyName, true);
+    value = new SGPropertyExpression<double>(inputProperty);
+  }
+
+  SGInterpTable* table = read_interpolation_table(config);
+  if (table) {
+    value = new SGInterpTableExpression<double>(value, table);
+    double biasValue = config->getDoubleValue("bias", 0);
+    if (biasValue != 0)
+      value = new SGBiasExpression<double>(value, biasValue);
+    value = new SGStepExpression<double>(value,
+                                         config->getDoubleValue("step", 0),
+                                         config->getDoubleValue("scroll", 0));
+    value = value->simplify();
+  } else {
+    double biasValue = config->getDoubleValue("bias", 0);
+    if (biasValue != 0)
+      value = new SGBiasExpression<double>(value, biasValue);
+    value = new SGStepExpression<double>(value,
+                                         config->getDoubleValue("step", 0),
+                                         config->getDoubleValue("scroll", 0));
+    value = read_offset_factor(config, value, "factor", "offset-deg");
+
+    if (config->hasChild("min-deg") || config->hasChild("max-deg")) {
+      double minClip = config->getDoubleValue("min-deg", -SGLimitsd::max());
+      double maxClip = config->getDoubleValue("max-deg", SGLimitsd::max());
+      value = new SGClipExpression<double>(value, minClip, maxClip);
     }
+    value = value->simplify();
   }
+  SGVec3d axis(config->getDoubleValue("axis/x", 0),
+               config->getDoubleValue("axis/y", 0),
+               config->getDoubleValue("axis/z", 0));
+  SGVec3d center(config->getDoubleValue("center/x", 0),
+                 config->getDoubleValue("center/y", 0),
+                 config->getDoubleValue("center/z", 0));
+  Rotation* rotation;
+  rotation = new Rotation(normalize(axis), center);
+  rotation->setValue(config->getDoubleValue("starting-position-deg", 0));
+  updateCallback->appendTransform(rotation, value);
 }
 
 
-\f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of SGFlashAnimation
+// Implementation of SGPickAnimation
 ////////////////////////////////////////////////////////////////////////
-SGFlashAnimation::SGFlashAnimation(SGPropertyNode_ptr props)
-  : SGAnimation( props, new SGFlash )
-{
-  sgVec3 axis;
-  axis[0] = props->getFloatValue("axis/x", 0);
-  axis[1] = props->getFloatValue("axis/y", 0);
-  axis[2] = props->getFloatValue("axis/z", 1);
-  ((SGFlash *)_branch)->setAxis( axis );
 
-  sgVec3 center;
-  center[0] = props->getFloatValue("center/x-m", 0);
-  center[1] = props->getFloatValue("center/y-m", 0);
-  center[2] = props->getFloatValue("center/z-m", 0);
-  ((SGFlash *)_branch)->setCenter( center );
+class SGPickAnimation::PickCallback : public SGPickCallback {
+public:
+  PickCallback(const SGPropertyNode* configNode,
+               SGPropertyNode* modelRoot) :
+    _repeatable(configNode->getBoolValue("repeatable", false)),
+    _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
+  {
+    SG_LOG(SG_INPUT, SG_DEBUG, "Reading all bindings");
+    std::vector<SGPropertyNode_ptr> bindings;
 
-  float offset = props->getFloatValue("offset", 0.0);
-  float factor = props->getFloatValue("factor", 1.0);
-  float power = props->getFloatValue("power", 1.0);
-  bool two_sides = props->getBoolValue("two-sides", false);
-  ((SGFlash *)_branch)->setParameters( power, factor, offset, two_sides );
+    bindings = configNode->getChildren("button");
+    for (unsigned int i = 0; i < bindings.size(); ++i) {
+      _buttons.push_back( bindings[i]->getIntValue() );
+    }
+    bindings = configNode->getChildren("binding");
+    for (unsigned int i = 0; i < bindings.size(); ++i) {
+      _bindingsDown.push_back(new SGBinding(bindings[i], modelRoot));
+    }
+
+    const SGPropertyNode* upNode = configNode->getChild("mod-up");
+    if (!upNode)
+      return;
+    bindings = upNode->getChildren("binding");
+    for (unsigned int i = 0; i < bindings.size(); ++i) {
+      _bindingsUp.push_back(new SGBinding(bindings[i], modelRoot));
+    }
+  }
+  virtual bool buttonPressed(int button, const Info&)
+  {
+    bool found = false;
+    for( std::vector<int>::iterator it = _buttons.begin(); it != _buttons.end(); ++it ) {
+      if( *it == button ) {
+        found = true;
+        break;
+      }
+    }
+    if (!found )
+      return false;
+    SGBindingList::const_iterator i;
+    for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
+      (*i)->fire();
+    _repeatTime = -_repeatInterval;    // anti-bobble: delay start of repeat
+    return true;
+  }
+  virtual void buttonReleased(void)
+  {
+    SGBindingList::const_iterator i;
+    for (i = _bindingsUp.begin(); i != _bindingsUp.end(); ++i)
+      (*i)->fire();
+  }
+  virtual void update(double dt)
+  {
+    if (!_repeatable)
+      return;
+
+    _repeatTime += dt;
+    while (_repeatInterval < _repeatTime) {
+      _repeatTime -= _repeatInterval;
+      SGBindingList::const_iterator i;
+      for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
+        (*i)->fire();
+    }
+  }
+private:
+  SGBindingList _bindingsDown;
+  SGBindingList _bindingsUp;
+  std::vector<int> _buttons;
+  bool _repeatable;
+  double _repeatInterval;
+  double _repeatTime;
+};
+
+class VncVisitor : public osg::NodeVisitor {
+ public:
+  VncVisitor(double x, double y, int mask) :
+    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+    _texX(x), _texY(y), _mask(mask), _done(false)
+  {
+    SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
+      << x << "," << y << " mask " << mask);
+  }
 
-  float v_min = props->getFloatValue("min", 0.0);
-  float v_max = props->getFloatValue("max", 1.0);
-  ((SGFlash *)_branch)->setClampValues( v_min, v_max );
+  virtual void apply(osg::Node &node)
+  {
+    // Some nodes have state sets attached
+    touchStateSet(node.getStateSet());
+    if (!_done)
+      traverse(node);
+    if (_done) return;
+    // See whether we are a geode worth exploring
+    osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
+    if (!g) return;
+    // Go find all its drawables
+    int i = g->getNumDrawables();
+    while (--i >= 0) {
+      osg::Drawable *d = g->getDrawable(i);
+      if (d) touchDrawable(*d);
+    }
+    // Out of optimism, do the same for EffectGeode
+    simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
+    if (!eg) return;
+    for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
+         di != eg->drawablesEnd(); di++) {
+      touchDrawable(**di);
+    }
+    // Now see whether the EffectGeode has an Effect
+    simgear::Effect *e = eg->getEffect();
+    if (e) {
+      touchStateSet(e->getDefaultStateSet());
+    }
+  }
+
+  inline void touchDrawable(osg::Drawable &d)
+  {
+    osg::StateSet *ss = d.getStateSet();
+    touchStateSet(ss);
+  }
+
+  void touchStateSet(osg::StateSet *ss)
+  {
+    if (!ss) return;
+    osg::StateAttribute *sa = ss->getTextureAttribute(0,
+      osg::StateAttribute::TEXTURE);
+    if (!sa) return;
+    osg::Texture *t = sa->asTexture();
+    if (!t) return;
+    osg::Image *img = t->getImage(0);
+    if (!img) return;
+    if (!_done) {
+      int pixX = _texX * img->s();
+      int pixY = _texY * img->t();
+      _done = img->sendPointerEvent(pixX, pixY, _mask);
+      SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor image said " << _done
+        << " to coord " << pixX << "," << pixY);
+    }
+  }
+
+  inline bool wasSuccessful()
+  {
+    return _done;
+  }
+
+ private:
+  double _texX, _texY;
+  int _mask;
+  bool _done;
+};
+
+
+class SGPickAnimation::VncCallback : public SGPickCallback {
+public:
+  VncCallback(const SGPropertyNode* configNode,
+               SGPropertyNode* modelRoot,
+               osg::Group *node)
+      : _node(node)
+  {
+    SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback");
+    const char *cornernames[3] = {"top-left", "top-right", "bottom-left"};
+    SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown};
+    for (int c =0; c < 3; c++) {
+      const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]);
+      *cornercoords[c] = SGVec3d(
+        cornerNode->getDoubleValue("x"),
+        cornerNode->getDoubleValue("y"),
+        cornerNode->getDoubleValue("z"));
+    }
+    _toRight -= _topLeft;
+    _toDown -= _topLeft;
+    _squaredRight = dot(_toRight, _toRight);
+    _squaredDown = dot(_toDown, _toDown);
+  }
+
+  virtual bool buttonPressed(int button, const Info& info)
+  {
+    SGVec3d loc(info.local);
+    SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
+    loc -= _topLeft;
+    _x = dot(loc, _toRight) / _squaredRight;
+    _y = dot(loc, _toDown) / _squaredDown;
+    if (_x<0) _x = 0; else if (_x > 1) _x = 1;
+    if (_y<0) _y = 0; else if (_y > 1) _y = 1;
+    VncVisitor vv(_x, _y, 1 << button);
+    _node->accept(vv);
+    return vv.wasSuccessful();
+
+  }
+  virtual void buttonReleased(void)
+  {
+    SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
+    VncVisitor vv(_x, _y, 0);
+    _node->accept(vv);
+  }
+  virtual void update(double dt)
+  {
+  }
+private:
+  double _x, _y;
+  osg::ref_ptr<osg::Group> _node;
+  SGVec3d _topLeft, _toRight, _toDown;
+  double _squaredRight, _squaredDown;
+};
+
+SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
+                                 SGPropertyNode* modelRoot) :
+  SGAnimation(configNode, modelRoot)
+{
 }
 
-SGFlashAnimation::~SGFlashAnimation()
+namespace
 {
+Mutex colorModeUniformMutex;
+osg::ref_ptr<osg::Uniform> colorModeUniform;
 }
 
-// end of animation.cxx
+osg::Group*
+SGPickAnimation::createAnimationGroup(osg::Group& parent)
+{
+  osg::Group* commonGroup = new osg::Group;
+
+  // Contains the normal geometry that is interactive
+  osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
+  normalGroup->setName("pick normal group");
+  normalGroup->addChild(commonGroup);
+
+  // Used to render the geometry with just yellow edges
+  osg::Group* highlightGroup = new osg::Group;
+  highlightGroup->setName("pick highlight group");
+  highlightGroup->setNodeMask(SG_NODEMASK_PICK_BIT);
+  highlightGroup->addChild(commonGroup);
+  SGSceneUserData* ud;
+  ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
+
+  // add actions that become macro and command invocations
+  std::vector<SGPropertyNode_ptr> actions;
+  actions = getConfig()->getChildren("action");
+  for (unsigned int i = 0; i < actions.size(); ++i)
+    ud->addPickCallback(new PickCallback(actions[i], getModelRoot()));
+  // Look for the VNC sessions that want raw mouse input
+  actions = getConfig()->getChildren("vncaction");
+  for (unsigned int i = 0; i < actions.size(); ++i)
+    ud->addPickCallback(new VncCallback(actions[i], getModelRoot(),
+      &parent));
+
+  // prepare a state set that paints the edges of this object yellow
+  // The material and texture attributes are set with
+  // OVERRIDE|PROTECTED in case there is a material animation on a
+  // higher node in the scene graph, which would have its material
+  // attribute set with OVERRIDE.
+  osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
+  osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
+  stateSet->setTextureAttributeAndModes(0, white,
+                                        (osg::StateAttribute::ON
+                                         | osg::StateAttribute::OVERRIDE
+                                         | osg::StateAttribute::PROTECTED));
+  osg::PolygonOffset* polygonOffset = new osg::PolygonOffset;
+  polygonOffset->setFactor(-1);
+  polygonOffset->setUnits(-1);
+  stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
+  stateSet->setMode(GL_POLYGON_OFFSET_LINE,
+                    osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
+  osg::PolygonMode* polygonMode = new osg::PolygonMode;
+  polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK,
+                       osg::PolygonMode::LINE);
+  stateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
+  osg::Material* material = new osg::Material;
+  material->setColorMode(osg::Material::OFF);
+  material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
+  // XXX Alpha < 1.0 in the diffuse material value is a signal to the
+  // default shader to take the alpha value from the material value
+  // and not the glColor. In many cases the pick animation geometry is
+  // transparent, so the outline would not be visible without this hack.
+  material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, .95));
+  material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
+  material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0));
+  stateSet->setAttribute(
+      material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
+  // The default shader has a colorMode uniform that mimics the
+  // behavior of Material color mode.
+  osg::Uniform* cmUniform = 0;
+  {
+      ScopedLock<Mutex> lock(colorModeUniformMutex);
+      if (!colorModeUniform.valid()) {
+          colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
+          colorModeUniform->set(0); // MODE_OFF
+          colorModeUniform->setDataVariance(osg::Object::STATIC);
+      }
+      cmUniform = colorModeUniform.get();
+  }
+  stateSet->addUniform(cmUniform,
+                       osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
+  // Only add normal geometry if configured
+  if (getConfig()->getBoolValue("visible", true))
+    parent.addChild(normalGroup.get());
+  parent.addChild(highlightGroup);
+
+  return commonGroup;
+}