]> 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 31d086229208f17b3c04d634fd77daf404e0f508..b6748fe036c0bee7df286f530aab5401f1fad7f9 100644 (file)
 #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/TexMat>
 #include <osg/Texture2D>
 #include <osg/Transform>
+#include <osg/Uniform>
 #include <osgDB/ReadFile>
+#include <osgDB/Registry>
+#include <osgDB/Input>
+#include <osgDB/ParameterOutput>
+
 
 #include <simgear/math/interpolater.hxx>
 #include <simgear/props/condition.hxx>
 #include <simgear/props/props.hxx>
 #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 "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.
@@ -101,36 +127,6 @@ set_translation (osg::Matrix &matrix, double position_m, const SGVec3d &axis)
   matrix(3, 2) = xyz[2];
 }
 
-/**
- * Modify property value by step and scroll settings in texture translations
- */
-static double
-apply_mods(double property, double step, double scroll, double bias)
-{
-
-  double modprop;
-  property += bias;
-  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);
-  } else {
-     modprop = property;
-  }
-  return modprop;
-
-}
-
 /**
  * Read an interpolation table from properties.
  */
@@ -143,202 +139,113 @@ read_interpolation_table(const SGPropertyNode* props)
   return new SGInterpTable(table_node);
 }
 
-////////////////////////////////////////////////////////////////////////
-// Utility value classes
-////////////////////////////////////////////////////////////////////////
-class SGScaleOffsetValue : public SGDoubleValue {
-public:
-  SGScaleOffsetValue(SGPropertyNode const* propertyNode) :
-    _propertyNode(propertyNode),
-    _scale(1),
-    _offset(0),
-    _min(-SGLimitsd::max()),
-    _max(SGLimitsd::max())
-  { }
-  void setScale(double scale)
-  { _scale = scale; }
-  void setOffset(double offset)
-  { _offset = offset; }
-  void setMin(double min)
-  { _min = min; }
-  void setMax(double max)
-  { _max = max; }
-
-  virtual double getValue() const
-  {
-    double value = _propertyNode ? _propertyNode->getDoubleValue() : 0;
-    return std::min(_max, std::max(_min, _offset + _scale*value));
-  }
-private:
-  SGSharedPtr<SGPropertyNode const> _propertyNode;
-  double _scale;
-  double _offset;
-  double _min;
-  double _max;
-};
+static std::string
+unit_string(const char* value, const char* unit)
+{
+  return std::string(value) + unit;
+}
 
-class SGPersScaleOffsetValue : public SGDoubleValue {
+class SGPersonalityScaleOffsetExpression : public SGUnaryExpression<double> {
 public:
-  SGPersScaleOffsetValue(SGPropertyNode const* propertyNode,
-                         SGPropertyNode const* config,
-                         const char* scalename, const char* offsetname,
-                         double defScale = 1, double defOffset = 0) :
-    _propertyNode(propertyNode),
-    _scale(config, scalename, defScale),
-    _offset(config, offsetname, defOffset),
-    _min(-SGLimitsd::max()),
-    _max(SGLimitsd::max())
+  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; }
-  void setMin(double min)
-  { _min = min; }
-  void setMax(double max)
-  { _max = max; }
 
-  virtual double getValue() const
+  virtual void eval(double& value, const simgear::expression::Binding* b) const
   {
     _offset.shuffle();
     _scale.shuffle();
-    double value = _propertyNode ? _propertyNode->getDoubleValue() : 0;
-    return SGMiscd::clip(_offset + _scale*value, _min, _max);
+    value = _offset + _scale*getOperand()->getValue(b);
   }
+
+  virtual bool isConst() const { return false; }
+
 private:
-  SGSharedPtr<SGPropertyNode const> _propertyNode;
   mutable SGPersonalityParameter<double> _scale;
   mutable SGPersonalityParameter<double> _offset;
-  double _min;
-  double _max;
-};
-
-class SGInterpTableValue : public SGDoubleValue {
-public:
-  SGInterpTableValue(SGPropertyNode const* propertyNode,
-                     SGInterpTable const* interpTable) :
-    _propertyNode(propertyNode),
-    _interpTable(interpTable)
-  { }
-  virtual double getValue() const
-  { return _interpTable->interpolate(_propertyNode ? _propertyNode->getDoubleValue() : 0); }
-private:
-  SGSharedPtr<SGPropertyNode const> _propertyNode;
-  SGSharedPtr<SGInterpTable const> _interpTable;
 };
 
-class SGTexScaleOffsetValue : public SGDoubleValue {
-public:
-  SGTexScaleOffsetValue(const SGPropertyNode* propertyNode) :
-    _propertyNode(propertyNode),
-    _scale(1),
-    _offset(0),
-    _step(0),
-    _scroll(0),
-    _bias(0),
-    _min(-SGLimitsd::max()),
-    _max(SGLimitsd::max())
-  { }
-  void setScale(double scale)
-  { _scale = scale; }
-  void setOffset(double offset)
-  { _offset = offset; }
-  void setStep(double step)
-  { _step = step; }
-  void setScroll(double scroll)
-  { _scroll = scroll; }
-  void setBias(double bias)
-  { _bias = bias; }
-  void setMin(double min)
-  { _min = min; }
-  void setMax(double max)
-  { _max = max; }
-
-  virtual double getValue() const
-  {
-    double value = _propertyNode ? _propertyNode->getDoubleValue() : 0;
-    value = apply_mods(value, _step, _scroll, _bias);
-    return SGMiscd::clip(_scale*(_offset + value), _min, _max);
-  }
-private:
-  SGSharedPtr<const SGPropertyNode> _propertyNode;
-  double _scale;
-  double _offset;
-  double _step;
-  double _scroll;
-  double _bias;
-  double _min;
-  double _max;
-};
 
-class SGTexTableValue : public SGDoubleValue {
-public:
-  SGTexTableValue(const SGPropertyNode* propertyNode,
-                  const SGInterpTable* interpTable) :
-    _propertyNode(propertyNode),
-    _interpTable(interpTable)
-  { }
-  void setStep(double step)
-  { _step = step; }
-  void setScroll(double scroll)
-  { _scroll = scroll; }
-  void setBias(double bias)
-  { _bias = bias; }
-  virtual double getValue() const
-  {
-    double value = _propertyNode ? _propertyNode->getDoubleValue() : 0;
-    value = apply_mods(value, _step, _scroll, _bias);
-    return _interpTable->interpolate(value);
-  }
-private:
-  SGSharedPtr<SGPropertyNode const> _propertyNode;
-  SGSharedPtr<SGInterpTable const> _interpTable;
-  double _step;
-  double _scroll;
-  double _bias;
-};
+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;
+}
 
-static std::string
-unit_string(const char* value, const char* unit)
+static SGExpressiond*
+read_offset_factor(const SGPropertyNode* configNode, SGExpressiond* expr,
+                   const std::string& factor, const std::string& offset)
 {
-  return std::string(value) + unit;
+  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;
 }
 
-static SGDoubleValue*
+SGExpressiond*
 read_value(const SGPropertyNode* configNode, SGPropertyNode* modelRoot,
            const char* unit, double defMin, double defMax)
 {
-  std::string inputPropertyName;
-  inputPropertyName = configNode->getStringValue("property", "");
-  if (!inputPropertyName.empty()) {
+  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 {
     SGPropertyNode* inputProperty;
-    inputProperty = modelRoot->getNode(inputPropertyName.c_str(), true);
-    SGInterpTable* interpTable = read_interpolation_table(configNode);
-    if (interpTable) {
-      SGInterpTableValue* value;
-      value = new SGInterpTableValue(inputProperty, interpTable);
-      return value;
+    inputProperty = modelRoot->getNode(inputPropertyName, true);
+    value = new SGPropertyExpression<double>(inputProperty);
+  }
+
+  SGInterpTable* interpTable = read_interpolation_table(configNode);
+  if (interpTable) {
+    return new SGInterpTableExpression<double>(value, interpTable);
+  } else {
+    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 {
-      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)) {
-        SGPersScaleOffsetValue* value;
-        value = new SGPersScaleOffsetValue(inputProperty, configNode,
-                                           "factor", offset.c_str());
-        value->setMin(configNode->getDoubleValue(min.c_str(), defMin));
-        value->setMax(configNode->getDoubleValue(max.c_str(), defMax));
-        return value;
-      } else {
-        SGScaleOffsetValue* value = new SGScaleOffsetValue(inputProperty);
-        value->setScale(configNode->getDoubleValue("factor", 1));
-        value->setOffset(configNode->getDoubleValue(offset.c_str(), 0));
-        value->setMin(configNode->getDoubleValue(min.c_str(), defMin));
-        value->setMax(configNode->getDoubleValue(max.c_str(), defMax));
-        return value;
-      }
+      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;
 }
@@ -443,6 +350,24 @@ public:
   }
 };
 
+namespace
+{
+// 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(const SGPropertyNode* configNode,
                                            SGPropertyNode* modelRoot) :
@@ -462,19 +387,30 @@ SGAnimation::SGAnimation(const SGPropertyNode* configNode,
 
 SGAnimation::~SGAnimation()
 {
-  if (_found)
-    return;
-
-  SG_LOG(SG_IO, SG_ALERT, "Could not find at least one of the following"
-         " objects for animation:\n");
-  std::list<std::string>::const_iterator i;
-  for (i = _objectNames.begin(); i != _objectNames.end(); ++i)
-    SG_LOG(SG_IO, SG_ALERT, *i << "\n");
+  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);
+      }
+  }
 }
 
 bool
 SGAnimation::animate(osg::Node* node, const SGPropertyNode* configNode,
-                     SGPropertyNode* modelRoot)
+                     SGPropertyNode* modelRoot,
+                     const osgDB::Options* options)
 {
   std::string type = configNode->getStringValue("type", "none");
   if (type == "alpha-test") {
@@ -492,8 +428,11 @@ SGAnimation::animate(osg::Node* node, const SGPropertyNode* configNode,
   } 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);
+    SGMaterialAnimation animInst(configNode, modelRoot, options);
     animInst.apply(node);
   } else if (type == "noshadow") {
     SGShadowAnimation animInst(configNode, modelRoot);
@@ -514,7 +453,7 @@ SGAnimation::animate(osg::Node* node, const SGPropertyNode* configNode,
     SGSelectAnimation animInst(configNode, modelRoot);
     animInst.apply(node);
   } else if (type == "shader") {
-    SGShaderAnimation animInst(configNode, modelRoot);
+    SGShaderAnimation animInst(configNode, modelRoot, options);
     animInst.apply(node);
   } else if (type == "textranslate" || type == "texrotate" ||
              type == "texmultiple") {
@@ -526,6 +465,9 @@ SGAnimation::animate(osg::Node* node, const SGPropertyNode* configNode,
   } 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);
@@ -711,58 +653,25 @@ SGGroupAnimation::createAnimationGroup(osg::Group& parent)
 // Implementation of translate animation
 ////////////////////////////////////////////////////////////////////////
 
-class SGTranslateAnimation::Transform : public osg::Transform {
-public:
-  Transform() :
-    _axis(0, 0, 0),
-    _value(0)
-  { setReferenceFrame(RELATIVE_RF); }
-  void setAxis(const SGVec3d& axis)
-  { _axis = axis; dirtyBound(); }
-  void setValue(double value)
-  { _value = value; dirtyBound(); }
-  virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,
-                                         osg::NodeVisitor* nv) const 
-  {
-    assert(_referenceFrame == RELATIVE_RF);
-    osg::Matrix tmp;
-    set_translation(tmp, _value, _axis);
-    matrix.preMult(tmp);
-    return true;
-  }
-  virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,
-                                         osg::NodeVisitor* nv) const
-  {
-    assert(_referenceFrame == RELATIVE_RF);
-    osg::Matrix tmp;
-    set_translation(tmp, -_value, _axis);
-    matrix.postMult(tmp);
-    return true;
-  }
-private:
-  SGVec3d _axis;
-  double _value;
-};
-
 class SGTranslateAnimation::UpdateCallback : public osg::NodeCallback {
 public:
   UpdateCallback(SGCondition const* condition,
-                 SGDoubleValue const* animationValue) :
+                 SGExpressiond const* animationValue) :
     _condition(condition),
     _animationValue(animationValue)
   { }
   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
   {
     if (!_condition || _condition->test()) {
-      SGTranslateAnimation::Transform* transform;
-      transform = static_cast<SGTranslateAnimation::Transform*>(node);
+      SGTranslateTransform* transform;
+      transform = static_cast<SGTranslateTransform*>(node);
       transform->setValue(_animationValue->getValue());
     }
     traverse(node, nv);
   }
 public:
   SGSharedPtr<SGCondition const> _condition;
-  SGSharedPtr<SGDoubleValue const> _animationValue;
+  SGSharedPtr<SGExpressiond const> _animationValue;
 };
 
 SGTranslateAnimation::SGTranslateAnimation(const SGPropertyNode* configNode,
@@ -770,25 +679,39 @@ SGTranslateAnimation::SGTranslateAnimation(const SGPropertyNode* configNode,
   SGAnimation(configNode, modelRoot)
 {
   _condition = getCondition();
-  _animationValue = read_value(configNode, modelRoot, "-m",
-                               -SGLimitsd::max(), SGLimitsd::max());
-  _axis[0] = configNode->getDoubleValue("axis/x", 0);
-  _axis[1] = configNode->getDoubleValue("axis/y", 0);
-  _axis[2] = configNode->getDoubleValue("axis/z", 0);
+  SGSharedPtr<SGExpressiond> value;
+  value = read_value(configNode, modelRoot, "-m",
+                     -SGLimitsd::max(), SGLimitsd::max());
+  _animationValue = value->simplify();
+  if (_animationValue)
+    _initialValue = _animationValue->getValue();
+  else
+    _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);
-
-  _initialValue = configNode->getDoubleValue("starting-position-m", 0);
-  _initialValue *= configNode->getDoubleValue("factor", 1);
-  _initialValue += configNode->getDoubleValue("offset-m", 0);
 }
 
 osg::Group*
 SGTranslateAnimation::createAnimationGroup(osg::Group& parent)
 {
-  Transform* transform = new Transform;
+  SGTranslateTransform* transform = new SGTranslateTransform;
   transform->setName("translate animation");
-  if (_animationValue) {
+  if (_animationValue && !_animationValue->isConst()) {
     UpdateCallback* uc = new UpdateCallback(_condition, _animationValue);
     transform->setUpdateCallback(uc);
   }
@@ -803,78 +726,31 @@ SGTranslateAnimation::createAnimationGroup(osg::Group& parent)
 // Implementation of rotate/spin animation
 ////////////////////////////////////////////////////////////////////////
 
-class SGRotateAnimation::Transform : public osg::Transform {
-public:
-  Transform()
-  { setReferenceFrame(RELATIVE_RF); }
-  void setCenter(const SGVec3d& center)
-  { _center = center; dirtyBound(); }
-  void setAxis(const SGVec3d& axis)
-  { _axis = axis; dirtyBound(); }
-  void setAngle(double angle)
-  { _angle = angle; }
-  double getAngle() const
-  { return _angle; }
-  virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,
-                                         osg::NodeVisitor* nv) const 
-  {
-    // This is the fast path, optimize a bit
-    assert(_referenceFrame == RELATIVE_RF);
-    // FIXME optimize
-    osg::Matrix tmp;
-    set_rotation(tmp, _angle, _center, _axis);
-    matrix.preMult(tmp);
-    return true;
-  }
-  virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,
-                                         osg::NodeVisitor* nv) const
-  {
-    assert(_referenceFrame == RELATIVE_RF);
-    // FIXME optimize
-    osg::Matrix tmp;
-    set_rotation(tmp, -_angle, _center, _axis);
-    matrix.postMult(tmp);
-    return true;
-  }
-  virtual osg::BoundingSphere computeBound() const
-  {
-    osg::BoundingSphere bs = osg::Group::computeBound();
-    osg::BoundingSphere centerbs(_center.osg(), bs.radius());
-    centerbs.expandBy(bs);
-    return centerbs;
-  }
-
-private:
-  SGVec3d _center;
-  SGVec3d _axis;
-  double _angle;
-};
-
 class SGRotateAnimation::UpdateCallback : public osg::NodeCallback {
 public:
   UpdateCallback(SGCondition const* condition,
-                 SGDoubleValue const* animationValue) :
+                 SGExpressiond const* animationValue) :
     _condition(condition),
     _animationValue(animationValue)
   { }
   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
   {
     if (!_condition || _condition->test()) {
-      SGRotateAnimation::Transform* transform;
-      transform = static_cast<SGRotateAnimation::Transform*>(node);
-      transform->setAngle(_animationValue->getValue());
+      SGRotateTransform* transform;
+      transform = static_cast<SGRotateTransform*>(node);
+      transform->setAngleDeg(_animationValue->getValue());
     }
     traverse(node, nv);
   }
 public:
   SGSharedPtr<SGCondition const> _condition;
-  SGSharedPtr<SGDoubleValue const> _animationValue;
+  SGSharedPtr<SGExpressiond const> _animationValue;
 };
 
 class SGRotateAnimation::SpinUpdateCallback : public osg::NodeCallback {
 public:
   SpinUpdateCallback(SGCondition const* condition,
-                     SGDoubleValue const* animationValue) :
+                     SGExpressiond const* animationValue) :
     _condition(condition),
     _animationValue(animationValue),
     _lastTime(-1)
@@ -882,8 +758,8 @@ public:
   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
   {
     if (!_condition || _condition->test()) {
-      SGRotateAnimation::Transform* transform;
-      transform = static_cast<SGRotateAnimation::Transform*>(node);
+      SGRotateTransform* transform;
+      transform = static_cast<SGRotateTransform*>(node);
 
       double t = nv->getFrameStamp()->getReferenceTime();
       double dt = 0;
@@ -891,31 +767,35 @@ public:
         dt = t - _lastTime;
       _lastTime = t;
       double velocity_rpms = _animationValue->getValue()/60;
-      double angle = transform->getAngle();
+      double angle = transform->getAngleDeg();
       angle += dt*velocity_rpms*360;
       angle -= 360*floor(angle/360);
-      transform->setAngle(angle);
+      transform->setAngleDeg(angle);
     }
     traverse(node, nv);
   }
 public:
   SGSharedPtr<SGCondition const> _condition;
-  SGSharedPtr<SGDoubleValue const> _animationValue;
+  SGSharedPtr<SGExpressiond const> _animationValue;
   double _lastTime;
 };
 
-SGRotateAnimation::SGRotateAnimation(const SGPropertyNode* configNode, SGPropertyNode* modelRoot) :
+SGRotateAnimation::SGRotateAnimation(const SGPropertyNode* configNode,
+                                     SGPropertyNode* modelRoot) :
   SGAnimation(configNode, modelRoot)
 {
   std::string type = configNode->getStringValue("type", "");
   _isSpin = (type == "spin");
 
   _condition = getCondition();
-  _animationValue = read_value(configNode, modelRoot, "-deg",
-                               -SGLimitsd::max(), SGLimitsd::max());
-  _initialValue = configNode->getDoubleValue("starting-position-deg", 0);
-  _initialValue *= configNode->getDoubleValue("factor", 1);
-  _initialValue += configNode->getDoubleValue("offset-deg", 0);
+  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")) {
@@ -944,19 +824,19 @@ SGRotateAnimation::SGRotateAnimation(const SGPropertyNode* configNode, SGPropert
 osg::Group*
 SGRotateAnimation::createAnimationGroup(osg::Group& parent)
 {
-  Transform* transform = new Transform;
+  SGRotateTransform* transform = new SGRotateTransform;
   transform->setName("rotate animation");
   if (_isSpin) {
     SpinUpdateCallback* uc;
     uc = new SpinUpdateCallback(_condition, _animationValue);
     transform->setUpdateCallback(uc);
-  } else if (_animationValue) {
+  } else if (_animationValue || !_animationValue->isConst()) {
     UpdateCallback* uc = new UpdateCallback(_condition, _animationValue);
     transform->setUpdateCallback(uc);
   }
   transform->setCenter(_center);
   transform->setAxis(_axis);
-  transform->setAngle(_initialValue);
+  transform->setAngleDeg(_initialValue);
   parent.addChild(transform);
   return transform;
 }
@@ -966,87 +846,10 @@ SGRotateAnimation::createAnimationGroup(osg::Group& parent)
 // Implementation of scale animation
 ////////////////////////////////////////////////////////////////////////
 
-class SGScaleAnimation::Transform : public osg::Transform {
-public:
-  Transform() :
-    _center(0, 0, 0),
-    _scaleFactor(1, 1, 1),
-    _boundScale(0)
-  {
-    setReferenceFrame(RELATIVE_RF);
-  }
-  void setCenter(const SGVec3d& center)
-  {
-    _center = center;
-    dirtyBound();
-  }
-  void setScaleFactor(const SGVec3d& scaleFactor)
-  {
-    if (_boundScale < normI(scaleFactor))
-      dirtyBound();
-    _scaleFactor = scaleFactor;
-  }
-  void setScaleFactor(double scaleFactor)
-  { 
-    if (_boundScale < fabs(scaleFactor))
-      dirtyBound();
-    _scaleFactor = SGVec3d(scaleFactor, scaleFactor, scaleFactor);
-  }
-  virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,
-                                         osg::NodeVisitor* nv) const 
-  {
-    assert(_referenceFrame == RELATIVE_RF);
-    osg::Matrix transform;
-    transform(0,0) = _scaleFactor[0];
-    transform(1,1) = _scaleFactor[1];
-    transform(2,2) = _scaleFactor[2];
-    transform(3,0) = _center[0]*(1 - _scaleFactor[0]);
-    transform(3,1) = _center[1]*(1 - _scaleFactor[1]);
-    transform(3,2) = _center[2]*(1 - _scaleFactor[2]);
-    matrix.preMult(transform);
-    return true;
-  }
-  virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,
-                                         osg::NodeVisitor* nv) const
-  {
-    assert(_referenceFrame == RELATIVE_RF);
-    if (fabs(_scaleFactor[0]) < SGLimitsd::min())
-      return false;
-    if (fabs(_scaleFactor[1]) < SGLimitsd::min())
-      return false;
-    if (fabs(_scaleFactor[2]) < SGLimitsd::min())
-      return false;
-    SGVec3d rScaleFactor(1/_scaleFactor[0],
-                         1/_scaleFactor[1],
-                         1/_scaleFactor[2]);
-    osg::Matrix transform;
-    transform(0,0) = rScaleFactor[0];
-    transform(1,1) = rScaleFactor[1];
-    transform(2,2) = rScaleFactor[2];
-    transform(3,0) = _center[0]*(1 - rScaleFactor[0]);
-    transform(3,1) = _center[1]*(1 - rScaleFactor[1]);
-    transform(3,2) = _center[2]*(1 - rScaleFactor[2]);
-    matrix.postMult(transform);
-    return true;
-  }
-  virtual osg::BoundingSphere computeBound() const
-  {
-    osg::BoundingSphere bs = osg::Group::computeBound();
-    _boundScale = normI(_scaleFactor);
-    bs.radius() *= _boundScale;
-    return bs;
-  }
-
-private:
-  SGVec3d _center;
-  SGVec3d _scaleFactor;
-  mutable double _boundScale;
-};
-
 class SGScaleAnimation::UpdateCallback : public osg::NodeCallback {
 public:
   UpdateCallback(const SGCondition* condition,
-                 SGSharedPtr<const SGDoubleValue> animationValue[3]) :
+                 SGSharedPtr<const SGExpressiond> animationValue[3]) :
     _condition(condition)
   {
     _animationValue[0] = animationValue[0];
@@ -1056,8 +859,8 @@ public:
   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
   {
     if (!_condition || _condition->test()) {
-      SGScaleAnimation::Transform* transform;
-      transform = static_cast<SGScaleAnimation::Transform*>(node);
+      SGScaleTransform* transform;
+      transform = static_cast<SGScaleTransform*>(node);
       SGVec3d scale(_animationValue[0]->getValue(),
                     _animationValue[1]->getValue(),
                     _animationValue[2]->getValue());
@@ -1067,7 +870,7 @@ public:
   }
 public:
   SGSharedPtr<SGCondition const> _condition;
-  SGSharedPtr<SGDoubleValue const> _animationValue[3];
+  SGSharedPtr<SGExpressiond const> _animationValue[3];
 };
 
 SGScaleAnimation::SGScaleAnimation(const SGPropertyNode* configNode,
@@ -1077,61 +880,72 @@ SGScaleAnimation::SGScaleAnimation(const SGPropertyNode* configNode,
   _condition = getCondition();
 
   // default offset/factor for all directions
-  double offset = configNode->getDoubleValue("offset", 1);
+  double offset = configNode->getDoubleValue("offset", 0);
   double factor = configNode->getDoubleValue("factor", 1);
 
+  SGSharedPtr<SGExpressiond> inPropExpr;
+
   std::string inputPropertyName;
   inputPropertyName = configNode->getStringValue("property", "");
-  SGPropertyNode* inputProperty = 0;
-  if (!inputPropertyName.empty()) {
-    inputProperty = modelRoot->getNode(inputPropertyName.c_str(), true);
+  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) {
-    SGInterpTableValue* value;
-    value = new SGInterpTableValue(inputProperty, interpTable);
-    _animationValue[0] = value;
-    _animationValue[1] = value;
-    _animationValue[2] = value;
+    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)) {
-    SGPersScaleOffsetValue* value;
-    value = new SGPersScaleOffsetValue(inputProperty, configNode,
-                                        "x-factor", "x-offset",
-                                        factor, offset);
-    value->setMin(configNode->getDoubleValue("x-min", 0));
-    value->setMax(configNode->getDoubleValue("x-max", SGLimitsd::max()));
-    _animationValue[0] = value;
-    value = new SGPersScaleOffsetValue(inputProperty, configNode,
-                                        "y-factor", "y-offset",
-                                        factor, offset);
-    value->setMin(configNode->getDoubleValue("y-min", 0));
-    value->setMax(configNode->getDoubleValue("y-max", SGLimitsd::max()));
-    _animationValue[1] = value;
-    value = new SGPersScaleOffsetValue(inputProperty, configNode,
-                                        "z-factor", "z-offset",
-                                        factor, offset);
-    value->setMin(configNode->getDoubleValue("z-min", 0));
-    value->setMax(configNode->getDoubleValue("z-max", SGLimitsd::max()));
-    _animationValue[2] = value;
+    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 {
-    SGScaleOffsetValue* value = new SGScaleOffsetValue(inputProperty);
-    value->setScale(configNode->getDoubleValue("x-factor", factor));
-    value->setOffset(configNode->getDoubleValue("x-offset", offset));
-    value->setMin(configNode->getDoubleValue("x-min", 0));
-    value->setMax(configNode->getDoubleValue("x-max", SGLimitsd::max()));
-    _animationValue[0] = value;
-    value = new SGScaleOffsetValue(inputProperty);
-    value->setScale(configNode->getDoubleValue("y-factor", factor));
-    value->setOffset(configNode->getDoubleValue("y-offset", offset));
-    value->setMin(configNode->getDoubleValue("y-min", 0));
-    value->setMax(configNode->getDoubleValue("y-max", SGLimitsd::max()));
-    _animationValue[1] = value;
-    value = new SGScaleOffsetValue(inputProperty);
-    value->setScale(configNode->getDoubleValue("z-factor", factor));
-    value->setOffset(configNode->getDoubleValue("z-offset", offset));
-    value->setMin(configNode->getDoubleValue("z-min", 0));
-    value->setMax(configNode->getDoubleValue("z-max", SGLimitsd::max()));
-    _animationValue[2] = value;
+    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();
   }
   _initialValue[0] = configNode->getDoubleValue("x-starting-scale", 1);
   _initialValue[0] *= configNode->getDoubleValue("x-factor", factor);
@@ -1150,7 +964,7 @@ SGScaleAnimation::SGScaleAnimation(const SGPropertyNode* configNode,
 osg::Group*
 SGScaleAnimation::createAnimationGroup(osg::Group& parent)
 {
-  Transform* transform = new Transform;
+  SGScaleTransform* transform = new SGScaleTransform;
   transform->setName("scale animation");
   transform->setCenter(_center);
   transform->setScaleFactor(_initialValue);
@@ -1161,17 +975,45 @@ SGScaleAnimation::createAnimationGroup(osg::Group& parent)
 }
 
 \f
+// Don't create a new state state everytime we need GL_NORMALIZE!
+
+namespace
+{
+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 normalizeStateSet.get();
+}
+}
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of dist scale animation
 ////////////////////////////////////////////////////////////////////////
 
 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);
-    getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
+    setStateSet(getNormalizeStateSet());
     _factor = configNode->getFloatValue("factor", 1);
     _offset = configNode->getFloatValue("offset", 0);
     _min_v = configNode->getFloatValue("min", SGLimitsf::epsilon());
@@ -1214,13 +1056,23 @@ public:
     return true;
   }
 
+  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;
 
-    double scale_factor = (_center.osg() - nv->getEyePoint()).length();
+    double scale_factor = (toOsg(_center) - nv->getEyePoint()).length();
     if (_table == 0) {
       scale_factor = _factor * scale_factor + _offset;
     } else {
@@ -1257,6 +1109,17 @@ SGDistScaleAnimation::createAnimationGroup(osg::Group& parent)
   return transform;
 }
 
+namespace
+{
+  osgDB::RegisterDotOsgWrapperProxy distScaleAnimationTransformProxy
+  (
+   new SGDistScaleAnimation::Transform,
+   "SGDistScaleAnimation::Transform",
+   "Object Node Transform SGDistScaleAnimation::Transform Group",
+   0,
+   &SGDistScaleAnimation::Transform::writeLocalData
+   );
+}
 \f
 ////////////////////////////////////////////////////////////////////////
 // Implementation of flash animation
@@ -1264,11 +1127,24 @@ SGDistScaleAnimation::createAnimationGroup(osg::Group& parent)
 
 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);
+
   Transform(const SGPropertyNode* configNode)
   {
     setReferenceFrame(RELATIVE_RF);
     setName(configNode->getStringValue("name", "flash animation"));
-    getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
+    setStateSet(getNormalizeStateSet());
 
     _axis[0] = configNode->getFloatValue("axis/x", 0);
     _axis[1] = configNode->getFloatValue("axis/y", 0);
@@ -1320,6 +1196,21 @@ public:
     return true;
   }
 
+  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;
+  }
 private:
   double computeScaleFactor(osg::NodeVisitor* nv) const
   {
@@ -1374,13 +1265,29 @@ SGFlashAnimation::createAnimationGroup(osg::Group& parent)
   return transform;
 }
 
+namespace
+{
+  osgDB::RegisterDotOsgWrapperProxy flashAnimationTransformProxy
+  (
+   new SGFlashAnimation::Transform,
+   "SGFlashAnimation::Transform",
+   "Object Node Transform SGFlashAnimation::Transform Group",
+   0,
+   &SGFlashAnimation::Transform::writeLocalData
+   );
+}
 \f
 ////////////////////////////////////////////////////////////////////////
-// Implementation of flash animation
+// 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))
   {
@@ -1417,7 +1324,13 @@ public:
     // 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;
+  }
 private:
   bool _spherical;
 };
@@ -1437,6 +1350,17 @@ SGBillboardAnimation::createAnimationGroup(osg::Group& parent)
   return transform;
 }
 
+namespace
+{
+  osgDB::RegisterDotOsgWrapperProxy billboardAnimationTransformProxy
+  (
+   new SGBillboardAnimation::Transform,
+   "SGBillboardAnimation::Transform",
+   "Object Node Transform SGBillboardAnimation::Transform Group",
+   0,
+   &SGBillboardAnimation::Transform::writeLocalData
+   );
+}
 \f
 ////////////////////////////////////////////////////////////////////////
 // Implementation of a range animation
@@ -1445,8 +1369,8 @@ SGBillboardAnimation::createAnimationGroup(osg::Group& parent)
 class SGRangeAnimation::UpdateCallback : public osg::NodeCallback {
 public:
   UpdateCallback(const SGCondition* condition,
-                 const SGDoubleValue* minAnimationValue,
-                 const SGDoubleValue* maxAnimationValue,
+                 const SGExpressiond* minAnimationValue,
+                 const SGExpressiond* maxAnimationValue,
                  double minValue, double maxValue) :
     _condition(condition),
     _minAnimationValue(minAnimationValue),
@@ -1477,8 +1401,8 @@ public:
 
 private:
   SGSharedPtr<const SGCondition> _condition;
-  SGSharedPtr<const SGDoubleValue> _minAnimationValue;
-  SGSharedPtr<const SGDoubleValue> _maxAnimationValue;
+  SGSharedPtr<const SGExpressiond> _minAnimationValue;
+  SGSharedPtr<const SGExpressiond> _maxAnimationValue;
   double _minStaticValue;
   double _maxStaticValue;
 };
@@ -1493,18 +1417,23 @@ SGRangeAnimation::SGRangeAnimation(const SGPropertyNode* configNode,
   inputPropertyName = configNode->getStringValue("min-property", "");
   if (!inputPropertyName.empty()) {
     SGPropertyNode* inputProperty;
-    inputProperty = modelRoot->getNode(inputPropertyName.c_str(), true);
-    SGScaleOffsetValue* value = new SGScaleOffsetValue(inputProperty);
-    value->setScale(configNode->getDoubleValue("min-factor", 1));
-    _minAnimationValue = value;
+    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);
-    SGScaleOffsetValue* value = new SGScaleOffsetValue(inputProperty);
-    value->setScale(configNode->getDoubleValue("max-factor", 1));
-    _maxAnimationValue = value;
+
+    SGSharedPtr<SGExpressiond> value;
+    value = new SGPropertyExpression<double>(inputProperty);
+
+    value = read_factor_offset(configNode, value, "max-factor", "max-offset");
+    _maxAnimationValue = value->simplify();
   }
 
   _initialValue[0] = configNode->getDoubleValue("min-m", 0);
@@ -1540,25 +1469,6 @@ SGRangeAnimation::createAnimationGroup(osg::Group& parent)
 // Implementation of a select animation
 ////////////////////////////////////////////////////////////////////////
 
-class SGSelectAnimation::UpdateCallback : public osg::NodeCallback {
-public:
-  UpdateCallback(const SGCondition* condition) :
-    _condition(condition)
-  {}
-  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
-  {
-    osg::Switch* sw = static_cast<osg::Switch*>(node);
-    if (_condition->test())
-      sw->setAllChildrenOn();
-    else
-      sw->setAllChildrenOff();
-    traverse(node, nv);
-  }
-
-private:
-  SGSharedPtr<SGCondition const> _condition;
-};
-
 SGSelectAnimation::SGSelectAnimation(const SGPropertyNode* configNode,
                                      SGPropertyNode* modelRoot) :
   SGAnimation(configNode, modelRoot)
@@ -1574,12 +1484,13 @@ SGSelectAnimation::createAnimationGroup(osg::Group& parent)
   // when the animation installer returns
   if (!condition)
     return new osg::Group;
-
-  osg::Switch* sw = new osg::Switch;
-  sw->setName("select animation node");
-  sw->setUpdateCallback(new UpdateCallback(condition));
-  parent.addChild(sw);
-  return sw;
+  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;
 }
 
 
@@ -1594,30 +1505,73 @@ SGAlphaTestAnimation::SGAlphaTestAnimation(const SGPropertyNode* configNode,
 {
 }
 
+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;
+
+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;
+}
+
+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);
-  
-  cloneDrawables(node);
-  removeMode(node, GL_ALPHA_TEST);
-  removeAttribute(node, osg::StateAttribute::ALPHAFUNC);
 
-  osg::StateSet* stateSet = node.getOrCreateStateSet();
-  osg::AlphaFunc* alphaFunc = new osg::AlphaFunc;
-  alphaFunc->setFunction(osg::AlphaFunc::GREATER);
   float alphaClamp = getConfig()->getFloatValue("alpha-factor", 0);
-  alphaFunc->setReferenceValue(alphaClamp);
-  stateSet->setAttribute(alphaFunc);
-  stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
+  osg::StateSet* stateSet = node.getStateSet();
+  if (!stateSet) {
+      node.setStateSet(makeAlphaTestStateSet(alphaClamp));
+  } else {
+      stateSet->setAttributeAndModes(makeAlphaFunc(alphaClamp),
+                                     (osg::StateAttribute::ON
+                                      | osg::StateAttribute::OVERRIDE));
+  }
 }
 
-
 \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) :
@@ -1635,7 +1589,6 @@ public:
     unsigned nDrawables = node.getNumDrawables();
     for (unsigned i = 0; i < nDrawables; ++i) {
       osg::Drawable* drawable = node.getDrawable(i);
-      updateStateSet(drawable->getStateSet());
       osg::Geometry* geometry = drawable->asGeometry();
       if (!geometry)
         continue;
@@ -1645,11 +1598,11 @@ public:
       osg::Vec4Array* vec4Array = dynamic_cast<osg::Vec4Array*>(array);
       if (!vec4Array)
         continue;
-      geometry->dirtyDisplayList();
-      vec4Array->dirty();
       for (unsigned k = 0; k < vec4Array->size(); ++k) {
         (*vec4Array)[k][3] = _blend;
       }
+      vec4Array->dirty();
+      updateStateSet(drawable->getStateSet());
     }
   }
   void updateStateSet(osg::StateSet* stateSet)
@@ -1677,7 +1630,7 @@ private:
 
 class SGBlendAnimation::UpdateCallback : public osg::NodeCallback {
 public:
-  UpdateCallback(const SGPropertyNode* configNode, const SGDoubleValue* v) :
+  UpdateCallback(const SGPropertyNode* configNode, const SGExpressiond* v) :
     _prev_value(-1),
     _animationValue(v)
   { }
@@ -1693,7 +1646,7 @@ public:
   }
 public:
   double _prev_value;
-  SGSharedPtr<SGDoubleValue const> _animationValue;
+  SGSharedPtr<SGExpressiond const> _animationValue;
 };
 
 
@@ -1724,6 +1677,8 @@ SGBlendAnimation::install(osg::Node& 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
@@ -1904,8 +1859,6 @@ public:
   Translation(const SGVec3d& axis) :
     _axis(axis)
   { }
-  void setValue(double value)
-  { _value = value; }
   virtual void transform(osg::Matrix& matrix)
   {
     osg::Matrix tmp;
@@ -1954,7 +1907,7 @@ public:
     for (i = _transforms.begin(); i != _transforms.end(); ++i)
       i->transform->transform(texMat->getMatrix());
   }
-  void appendTransform(Transform* transform, SGDoubleValue* value)
+  void appendTransform(Transform* transform, SGExpressiond* value)
   {
     Entry entry = { transform, value };
     transform->transform(_matrix);
@@ -1964,7 +1917,7 @@ public:
 private:
   struct Entry {
     SGSharedPtr<Transform> transform;
-    SGSharedPtr<const SGDoubleValue> value;
+    SGSharedPtr<const SGExpressiond> value;
   };
   typedef std::vector<Entry> TransformList;
   TransformList _transforms;
@@ -1984,6 +1937,7 @@ 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 ...
@@ -2020,30 +1974,40 @@ void
 SGTexTransformAnimation::appendTexTranslate(const SGPropertyNode* config,
                                             UpdateCallback* updateCallback)
 {
-  std::string propertyName = config->getStringValue("property", "/null");
-  SGPropertyNode* inputNode;
-  inputNode = getModelRoot()->getNode(propertyName.c_str(), true);
+  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);
+  }
 
-  SGDoubleValue* animationValue;
   SGInterpTable* table = read_interpolation_table(config);
   if (table) {
-    SGTexTableValue* value;
-    value = new SGTexTableValue(inputNode, table);
-    value->setStep(config->getDoubleValue("step", 0));
-    value->setScroll(config->getDoubleValue("scroll", 0));
-    value->setBias(config->getDoubleValue("bias", 0));
-    animationValue = value;
+    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 {
-    SGTexScaleOffsetValue* value;
-    value = new SGTexScaleOffsetValue(inputNode);
-    value->setScale(config->getDoubleValue("factor", 1));
-    value->setOffset(config->getDoubleValue("offset", 0));
-    value->setStep(config->getDoubleValue("step", 0));
-    value->setScroll(config->getDoubleValue("scroll", 0));
-    value->setBias(config->getDoubleValue("bias", 0));
-    value->setMin(config->getDoubleValue("min", -SGLimitsd::max()));
-    value->setMax(config->getDoubleValue("max", SGLimitsd::max()));
-    animationValue = value;
+    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),
@@ -2051,37 +2015,47 @@ SGTexTransformAnimation::appendTexTranslate(const SGPropertyNode* config,
   Translation* translation;
   translation = new Translation(normalize(axis));
   translation->setValue(config->getDoubleValue("starting-position", 0));
-  updateCallback->appendTransform(translation, animationValue);
+  updateCallback->appendTransform(translation, value);
 }
 
 void
 SGTexTransformAnimation::appendTexRotate(const SGPropertyNode* config,
                                          UpdateCallback* updateCallback)
 {
-  std::string propertyName = config->getStringValue("property", "/null");
-  SGPropertyNode* inputNode;
-  inputNode = getModelRoot()->getNode(propertyName.c_str(), true);
+  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);
+  }
 
-  SGDoubleValue* animationValue;
   SGInterpTable* table = read_interpolation_table(config);
   if (table) {
-    SGTexTableValue* value;
-    value = new SGTexTableValue(inputNode, table);
-    value->setStep(config->getDoubleValue("step", 0));
-    value->setScroll(config->getDoubleValue("scroll", 0));
-    value->setBias(config->getDoubleValue("bias", 0));
-    animationValue = value;
+    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 {
-    SGTexScaleOffsetValue* value;
-    value = new SGTexScaleOffsetValue(inputNode);
-    value->setScale(config->getDoubleValue("factor", 1));
-    value->setOffset(config->getDoubleValue("offset-deg", 0));
-    value->setStep(config->getDoubleValue("step", 0));
-    value->setScroll(config->getDoubleValue("scroll", 0));
-    value->setBias(config->getDoubleValue("bias", 0));
-    value->setMin(config->getDoubleValue("min-deg", -SGLimitsd::max()));
-    value->setMax(config->getDoubleValue("max-deg", SGLimitsd::max()));
-    animationValue = value;
+    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),
@@ -2092,7 +2066,7 @@ SGTexTransformAnimation::appendTexRotate(const SGPropertyNode* config,
   Rotation* rotation;
   rotation = new Rotation(normalize(axis), center);
   rotation->setValue(config->getDoubleValue("starting-position-deg", 0));
-  updateCallback->appendTransform(rotation, animationValue);
+  updateCallback->appendTransform(rotation, value);
 }
 
 
@@ -2104,12 +2078,16 @@ class SGPickAnimation::PickCallback : public SGPickCallback {
 public:
   PickCallback(const SGPropertyNode* configNode,
                SGPropertyNode* modelRoot) :
-    _button(configNode->getIntValue("button", -1)),
     _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;
+
+    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));
@@ -2125,12 +2103,19 @@ public:
   }
   virtual bool buttonPressed(int button, const Info&)
   {
-    if (0 <= _button && button != _button)
+    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 = 0;
+    _repeatTime = -_repeatInterval;    // anti-bobble: delay start of repeat
     return true;
   }
   virtual void buttonReleased(void)
@@ -2155,18 +2140,154 @@ public:
 private:
   SGBindingList _bindingsDown;
   SGBindingList _bindingsUp;
-  int _button;
+  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);
+  }
+
+  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)
 {
 }
 
+namespace
+{
+Mutex colorModeUniformMutex;
+osg::ref_ptr<osg::Uniform> colorModeUniform;
+}
+
 osg::Group*
 SGPickAnimation::createAnimationGroup(osg::Group& parent)
 {
@@ -2174,42 +2295,75 @@ SGPickAnimation::createAnimationGroup(osg::Group& parent)
 
   // 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(highlightGroup);
+  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();
-  stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
-
+  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);
-
+  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(1, 1, 0, 1));
-  material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
+  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);
-
+  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());