// animation.cxx - classes to manage model animation.
// Written by David Megginson, started 2002.
//
// 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/PolygonMode>
#include <osg/PolygonOffset>
#include <osg/StateSet>
#include <osg/Switch>
#include <osg/TexMat>
#include <osg/Texture2D>
#include <osg/Transform>
#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/util/SGNodeMasks.hxx>
#include <simgear/scene/util/SGSceneUserData.hxx>
#include <simgear/scene/util/SGStateAttributeVisitor.hxx>

#include "animation.hxx"
#include "model.hxx"

#include "SGTranslateTransform.hxx"
#include "SGMaterialAnimation.hxx"
#include "SGRotateTransform.hxx"
#include "SGScaleTransform.hxx"
#include "SGInteractionAnimation.hxx"

using OpenThreads::Mutex;
using OpenThreads::ReentrantMutex;
using OpenThreads::ScopedLock;


////////////////////////////////////////////////////////////////////////
// Static utility functions.
////////////////////////////////////////////////////////////////////////

/**
 * Set up the transform matrix for a spin or rotation.
 */
static void
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) = 1;
}

/**
 * Set up the transform matrix for a translation.
 */
static void
set_translation (osg::Matrix &matrix, double position_m, const SGVec3d &axis)
{
  SGVec3d xyz = axis * position_m;
  matrix.makeIdentity();
  matrix(3, 0) = xyz[0];
  matrix(3, 1) = xyz[1];
  matrix(3, 2) = xyz[2];
}

/**
 * Read an interpolation table from properties.
 */
static SGInterpTable *
read_interpolation_table(const SGPropertyNode* props)
{
  const SGPropertyNode* table_node = props->getNode("interpolation");
  if (!table_node)
    return 0;
  return new SGInterpTable(table_node);
}

static std::string
unit_string(const char* value, const char* unit)
{
  return std::string(value) + unit;
}

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);
  }

  virtual bool isConst() const { return false; }

private:
  mutable SGPersonalityParameter<double> _scale;
  mutable SGPersonalityParameter<double> _offset;
};


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 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)
{
  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, 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 {
      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;
}


////////////////////////////////////////////////////////////////////////
// Animation installer
////////////////////////////////////////////////////////////////////////

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)));
    }
  }
};

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) :
  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());
}

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");
}

bool
SGAnimation::animate(osg::Node* node, const SGPropertyNode* configNode,
                     SGPropertyNode* modelRoot,
                     const osgDB::ReaderWriter::Options* options)
{
  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 == "null" || type == "none" || type.empty()) {
    SGGroupAnimation animInst(configNode, modelRoot);
    animInst.apply(node);
  } else
    return false;

  return true;
}
  
  
void
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);
}

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());
}

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;
}

void
SGAnimation::apply(osg::Group& group)
{
  // 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);
}

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);
      }

      // 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);
    }
  }
}

void
SGAnimation::removeMode(osg::Node& node, osg::StateAttribute::GLMode mode)
{
  RemoveModeVisitor visitor(mode);
  node.accept(visitor);
}

void
SGAnimation::removeAttribute(osg::Node& node, osg::StateAttribute::Type type)
{
  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);
}

void
SGAnimation::setRenderBinToInherit(osg::Node& node)
{
  BinToInheritVisitor visitor;
  node.accept(visitor);
}

void
SGAnimation::cloneDrawables(osg::Node& node)
{
  DrawableCloneVisitor visitor;
  node.accept(visitor);
}

const SGCondition*
SGAnimation::getCondition() const
{
  const SGPropertyNode* conditionNode = _configNode->getChild("condition");
  if (!conditionNode)
    return 0;
  return sgReadCondition(_modelRoot, conditionNode);
}



////////////////////////////////////////////////////////////////////////
// Implementation of null animation
////////////////////////////////////////////////////////////////////////

// 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)
{
}

osg::Group*
SGGroupAnimation::createAnimationGroup(osg::Group& parent)
{
  osg::Group* group = new osg::Group;
  parent.addChild(group);
  return group;
}


////////////////////////////////////////////////////////////////////////
// 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)
{
  _condition = getCondition();
  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);
}

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;
}


////////////////////////////////////////////////////////////////////////
// Implementation of rotate/spin animation
////////////////////////////////////////////////////////////////////////

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());
    }
    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);
    }
    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]);
}

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;
}


////////////////////////////////////////////////////////////////////////
// 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)
{
  _condition = getCondition();

  // default offset/factor for all directions
  double offset = configNode->getDoubleValue("offset", 0);
  double factor = configNode->getDoubleValue("factor", 1);

  SGSharedPtr<SGExpressiond> inPropExpr;

  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();
  }
  _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;
}


// 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);
    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;
  }

  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 = (toOsg(_center) - nv->getEyePoint()).length();
    if (_table == 0) {
      scale_factor = _factor * scale_factor + _offset;
    } else {
      scale_factor = _table->interpolate( scale_factor );
    }
    if (scale_factor < _min_v)
      scale_factor = _min_v;
    if (scale_factor > _max_v)
      scale_factor = _max_v;

    return scale_factor;
  }

  SGSharedPtr<SGInterpTable> _table;
  SGVec3d _center;
  double _min_v;
  double _max_v;
  double _factor;
  double _offset;
};


SGDistScaleAnimation::SGDistScaleAnimation(const SGPropertyNode* configNode,
                                           SGPropertyNode* modelRoot) :
  SGAnimation(configNode, modelRoot)
{
}

osg::Group*
SGDistScaleAnimation::createAnimationGroup(osg::Group& parent)
{
  Transform* transform = new Transform(getConfig());
  parent.addChild(transform);
  return transform;
}

namespace
{
  osgDB::RegisterDotOsgWrapperProxy distScaleAnimationTransformProxy
  (
   new SGDistScaleAnimation::Transform,
   "SGDistScaleAnimation::Transform",
   "Object Node Transform SGDistScaleAnimation::Transform Group",
   0,
   &SGDistScaleAnimation::Transform::writeLocalData
   );
}

////////////////////////////////////////////////////////////////////////
// Implementation of flash animation
////////////////////////////////////////////////////////////////////////

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"));
    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;
  }

  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
  {
    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;
  }

  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;
};


SGFlashAnimation::SGFlashAnimation(const SGPropertyNode* configNode,
                                   SGPropertyNode* modelRoot) :
  SGAnimation(configNode, modelRoot)
{
}

osg::Group*
SGFlashAnimation::createAnimationGroup(osg::Group& parent)
{
  Transform* transform = new Transform(getConfig());
  parent.addChild(transform);
  return transform;
}

namespace
{
  osgDB::RegisterDotOsgWrapperProxy flashAnimationTransformProxy
  (
   new SGFlashAnimation::Transform,
   "SGFlashAnimation::Transform",
   "Object Node Transform SGFlashAnimation::Transform Group",
   0,
   &SGFlashAnimation::Transform::writeLocalData
   );
}

////////////////////////////////////////////////////////////////////////
// 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 {
      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];
    }
    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;
  }
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
   );
}

////////////////////////////////////////////////////////////////////////
// Implementation of a range animation
////////////////////////////////////////////////////////////////////////

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);
  }

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);

    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);
  _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;
}


////////////////////////////////////////////////////////////////////////
// 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)
{
}

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;

  osg::Switch* sw = new osg::Switch;
  sw->setName("select animation node");
  sw->setUpdateCallback(new UpdateCallback(condition));
  parent.addChild(sw);
  return sw;
}



////////////////////////////////////////////////////////////////////////
// Implementation of alpha test animation
////////////////////////////////////////////////////////////////////////

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;

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);

  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));
  }
}


//////////////////////////////////////////////////////////////////////
// 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))
{
}

osg::Group*
SGBlendAnimation::createAnimationGroup(osg::Group& parent)
{
  if (!_animationValue)
    return 0;

  osg::Group* group = new osg::Switch;
  group->setName("blend animation node");
  group->setUpdateCallback(new UpdateCallback(getConfig(), _animationValue));
  parent.addChild(group);
  return group;
}

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);
}


//////////////////////////////////////////////////////////////////////
// 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 {
        _durations[ind] = DurationSpec(rNode->getDoubleValue( "min", 0),
                                       rNode->getDoubleValue( "max", 1));
      }
    }
  }
  virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
  {
    assert(dynamic_cast<osg::Switch*>(node));
    osg::Switch* sw = static_cast<osg::Switch*>(node);

    unsigned nChildren = sw->getNumChildren();

    // 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;

    // 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;
}


////////////////////////////////////////////////////////////////////////
// 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);
  }

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;
}


////////////////////////////////////////////////////////////////////////
// Implementation of SGTexTransformAnimation
////////////////////////////////////////////////////////////////////////

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)
{
}

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
SGTexTransformAnimation::appendTexTranslate(const SGPropertyNode* config,
                                            UpdateCallback* updateCallback)
{
  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
SGTexTransformAnimation::appendTexRotate(const SGPropertyNode* config,
                                         UpdateCallback* updateCallback)
{
  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);
}


////////////////////////////////////////////////////////////////////////
// Implementation of SGPickAnimation
////////////////////////////////////////////////////////////////////////

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;

    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;
};

SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
                                 SGPropertyNode* modelRoot) :
  SGAnimation(configNode, modelRoot)
{
}

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);
  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()));

  // prepare a state set that paints the edges of this object yellow
  osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
  stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);

  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(1, 1, 0, 1));
  material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
  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);

  // Only add normal geometry if configured
  if (getConfig()->getBoolValue("visible", true))
    parent.addChild(normalGroup.get());
  parent.addChild(highlightGroup);

  return commonGroup;
}
