]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/SGPickAnimation.cxx
Ensure every scenery model has own SGModelData.
[simgear.git] / simgear / scene / model / SGPickAnimation.cxx
1 /* -*-c++-*-
2  *
3  * Copyright (C) 2013 James Turner
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18  * MA 02110-1301, USA.
19  *
20  */
21      
22      
23 #include <simgear/scene/model/SGPickAnimation.hxx>
24
25 #include <osg/Geode>
26 #include <osg/PolygonOffset>
27 #include <osg/PolygonMode>
28 #include <osg/Material>
29 #include <osgGA/GUIEventAdapter>
30
31 #include <simgear/sg_inlines.h>
32 #include <simgear/scene/util/SGPickCallback.hxx>
33 #include <simgear/scene/material/EffectGeode.hxx>
34 #include <simgear/scene/util/SGSceneUserData.hxx>
35 #include <simgear/structure/SGBinding.hxx>
36 #include <simgear/scene/util/StateAttributeFactory.hxx>
37 #include <simgear/scene/model/SGRotateTransform.hxx>
38 #include <simgear/scene/model/SGTranslateTransform.hxx>
39
40 using namespace simgear;
41
42 using OpenThreads::Mutex;
43 using OpenThreads::ScopedLock;
44
45 static void readOptionalBindingList(const SGPropertyNode* aNode, SGPropertyNode* modelRoot,
46     const std::string& aName, SGBindingList& aBindings)
47 {
48     const SGPropertyNode* n = aNode->getChild(aName);
49     if (n)
50         aBindings = readBindingList(n->getChildren("binding"), modelRoot);
51     
52 }
53
54
55 osg::Vec2d eventToWindowCoords(const osgGA::GUIEventAdapter* ea)
56 {
57     using namespace osg;
58     const GraphicsContext* gc = ea->getGraphicsContext();
59     const GraphicsContext::Traits* traits = gc->getTraits() ;
60     // Scale x, y to the dimensions of the window
61     double x = (((ea->getX() - ea->getXmin()) / (ea->getXmax() - ea->getXmin()))
62          * (double)traits->width);
63     double y = (((ea->getY() - ea->getYmin()) / (ea->getYmax() - ea->getYmin()))
64          * (double)traits->height);
65     if (ea->getMouseYOrientation() == osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS)
66         y = (double)traits->height - y;
67     
68     return osg::Vec2d(x, y);
69 }
70
71  class SGPickAnimation::PickCallback : public SGPickCallback {
72  public:
73    PickCallback(const SGPropertyNode* configNode,
74                 SGPropertyNode* modelRoot) :
75      SGPickCallback(PriorityPanel),
76      _repeatable(configNode->getBoolValue("repeatable", false)),
77      _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
78    {
79      std::vector<SGPropertyNode_ptr> bindings;
80
81      bindings = configNode->getChildren("button");
82      for (unsigned int i = 0; i < bindings.size(); ++i) {
83        _buttons.insert( bindings[i]->getIntValue() );
84      }
85
86      _bindingsDown = readBindingList(configNode->getChildren("binding"), modelRoot);
87      readOptionalBindingList(configNode, modelRoot, "mod-up", _bindingsUp);
88      
89      
90      if (configNode->hasChild("cursor")) {
91        _cursorName = configNode->getStringValue("cursor");
92      }
93    }
94      
95      void addHoverBindings(const SGPropertyNode* hoverNode,
96                              SGPropertyNode* modelRoot)
97      {
98          _hover = readBindingList(hoverNode->getChildren("binding"), modelRoot);
99      }
100      
101    virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
102    {
103        if (_buttons.find(button) == _buttons.end()) {
104            return false;
105        }
106        
107      fireBindingList(_bindingsDown);
108      _repeatTime = -_repeatInterval;    // anti-bobble: delay start of repeat
109      return true;
110    }
111    virtual void buttonReleased(int keyModState)
112    {
113        SG_UNUSED(keyModState);
114        fireBindingList(_bindingsUp);
115    }
116      
117    virtual void update(double dt, int keyModState)
118    {
119      SG_UNUSED(keyModState);
120      if (!_repeatable)
121        return;
122
123      _repeatTime += dt;
124      while (_repeatInterval < _repeatTime) {
125        _repeatTime -= _repeatInterval;
126          fireBindingList(_bindingsDown);
127      }
128    }
129    
130    virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
131    {
132        if (_hover.empty()) {
133            return false;
134        }
135        
136        SGPropertyNode_ptr params(new SGPropertyNode);
137        params->setDoubleValue("x", windowPos.x());
138        params->setDoubleValue("y", windowPos.y());
139        fireBindingList(_hover, params.ptr());
140        return true;
141    }
142    
143    std::string getCursor() const
144    { return _cursorName; }
145  private:
146    SGBindingList _bindingsDown;
147    SGBindingList _bindingsUp;
148    SGBindingList _hover;
149    std::set<int> _buttons;
150    bool _repeatable;
151    double _repeatInterval;
152    double _repeatTime;
153    std::string _cursorName;
154  };
155
156  class VncVisitor : public osg::NodeVisitor {
157   public:
158    VncVisitor(double x, double y, int mask) :
159      osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
160      _texX(x), _texY(y), _mask(mask), _done(false)
161    {
162      SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
163        << x << "," << y << " mask " << mask);
164    }
165
166    virtual void apply(osg::Node &node)
167    {
168      // Some nodes have state sets attached
169      touchStateSet(node.getStateSet());
170      if (!_done)
171        traverse(node);
172      if (_done) return;
173      // See whether we are a geode worth exploring
174      osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
175      if (!g) return;
176      // Go find all its drawables
177      int i = g->getNumDrawables();
178      while (--i >= 0) {
179        osg::Drawable *d = g->getDrawable(i);
180        if (d) touchDrawable(*d);
181      }
182      // Out of optimism, do the same for EffectGeode
183      simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
184      if (!eg) return;
185      for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
186           di != eg->drawablesEnd(); di++) {
187        touchDrawable(**di);
188      }
189      // Now see whether the EffectGeode has an Effect
190      simgear::Effect *e = eg->getEffect();
191      if (e) {
192        touchStateSet(e->getDefaultStateSet());
193      }
194    }
195
196    inline void touchDrawable(osg::Drawable &d)
197    {
198      osg::StateSet *ss = d.getStateSet();
199      touchStateSet(ss);
200    }
201
202    void touchStateSet(osg::StateSet *ss)
203    {
204      if (!ss) return;
205      osg::StateAttribute *sa = ss->getTextureAttribute(0,
206        osg::StateAttribute::TEXTURE);
207      if (!sa) return;
208      osg::Texture *t = sa->asTexture();
209      if (!t) return;
210      osg::Image *img = t->getImage(0);
211      if (!img) return;
212      if (!_done) {
213        int pixX = _texX * img->s();
214        int pixY = _texY * img->t();
215        _done = img->sendPointerEvent(pixX, pixY, _mask);
216        SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor image said " << _done
217          << " to coord " << pixX << "," << pixY);
218      }
219    }
220
221    inline bool wasSuccessful()
222    {
223      return _done;
224    }
225
226   private:
227    double _texX, _texY;
228    int _mask;
229    bool _done;
230  };
231
232 ///////////////////////////////////////////////////////////////////////////////
233
234  class SGPickAnimation::VncCallback : public SGPickCallback {
235  public:
236    VncCallback(const SGPropertyNode* configNode,
237                 SGPropertyNode* modelRoot,
238                 osg::Group *node)
239        : _node(node)
240    {
241      SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback");
242      const char *cornernames[3] = {"top-left", "top-right", "bottom-left"};
243      SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown};
244      for (int c =0; c < 3; c++) {
245        const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]);
246        *cornercoords[c] = SGVec3d(
247          cornerNode->getDoubleValue("x"),
248          cornerNode->getDoubleValue("y"),
249          cornerNode->getDoubleValue("z"));
250      }
251      _toRight -= _topLeft;
252      _toDown -= _topLeft;
253      _squaredRight = dot(_toRight, _toRight);
254      _squaredDown = dot(_toDown, _toDown);
255    }
256
257    virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
258    {
259      SGVec3d loc(info.local);
260      SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
261      loc -= _topLeft;
262      _x = dot(loc, _toRight) / _squaredRight;
263      _y = dot(loc, _toDown) / _squaredDown;
264      if (_x<0) _x = 0; else if (_x > 1) _x = 1;
265      if (_y<0) _y = 0; else if (_y > 1) _y = 1;
266      VncVisitor vv(_x, _y, 1 << button);
267      _node->accept(vv);
268      return vv.wasSuccessful();
269
270    }
271    virtual void buttonReleased(int keyModState)
272    {
273      SG_UNUSED(keyModState);
274      SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
275      VncVisitor vv(_x, _y, 0);
276      _node->accept(vv);
277    }
278
279  private:
280    double _x, _y;
281    osg::ref_ptr<osg::Group> _node;
282    SGVec3d _topLeft, _toRight, _toDown;
283    double _squaredRight, _squaredDown;
284  };
285
286 ///////////////////////////////////////////////////////////////////////////////
287
288  SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
289                                   SGPropertyNode* modelRoot) :
290    SGAnimation(configNode, modelRoot)
291  {
292  }
293
294  namespace
295  {
296  OpenThreads::Mutex colorModeUniformMutex;
297  osg::ref_ptr<osg::Uniform> colorModeUniform;
298  }
299
300
301 void 
302 SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent)
303 {
304     // Contains the normal geometry that is interactive
305     osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
306     normalGroup->setName("pick normal group");
307     normalGroup->addChild(commonGroup);
308     
309     // Used to render the geometry with just yellow edges
310     osg::Group* highlightGroup = new osg::Group;
311     highlightGroup->setName("pick highlight group");
312     highlightGroup->setNodeMask(simgear::PICK_BIT);
313     highlightGroup->addChild(commonGroup);
314     
315     // prepare a state set that paints the edges of this object yellow
316     // The material and texture attributes are set with
317     // OVERRIDE|PROTECTED in case there is a material animation on a
318     // higher node in the scene graph, which would have its material
319     // attribute set with OVERRIDE.
320     osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
321     osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
322     stateSet->setTextureAttributeAndModes(0, white,
323                                           (osg::StateAttribute::ON
324                                            | osg::StateAttribute::OVERRIDE
325                                            | osg::StateAttribute::PROTECTED));
326     osg::PolygonOffset* polygonOffset = new osg::PolygonOffset;
327     polygonOffset->setFactor(-1);
328     polygonOffset->setUnits(-1);
329     stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
330     stateSet->setMode(GL_POLYGON_OFFSET_LINE,
331                       osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
332     osg::PolygonMode* polygonMode = new osg::PolygonMode;
333     polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK,
334                          osg::PolygonMode::LINE);
335     stateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
336     osg::Material* material = new osg::Material;
337     material->setColorMode(osg::Material::OFF);
338     material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
339     // XXX Alpha < 1.0 in the diffuse material value is a signal to the
340     // default shader to take the alpha value from the material value
341     // and not the glColor. In many cases the pick animation geometry is
342     // transparent, so the outline would not be visible without this hack.
343     material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, .95));
344     material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
345     material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0));
346     stateSet->setAttribute(
347                            material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
348     // The default shader has a colorMode uniform that mimics the
349     // behavior of Material color mode.
350     osg::Uniform* cmUniform = 0;
351     {
352         ScopedLock<Mutex> lock(colorModeUniformMutex);
353         if (!colorModeUniform.valid()) {
354             colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
355             colorModeUniform->set(0); // MODE_OFF
356             colorModeUniform->setDataVariance(osg::Object::STATIC);
357         }
358         cmUniform = colorModeUniform.get();
359     }
360     stateSet->addUniform(cmUniform,
361                          osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
362     
363     // Only add normal geometry if configured
364     if (getConfig()->getBoolValue("visible", true))
365         parent.addChild(normalGroup.get());
366     parent.addChild(highlightGroup);
367 }
368
369  osg::Group*
370  SGPickAnimation::createAnimationGroup(osg::Group& parent)
371  {
372      osg::Group* commonGroup = new osg::Group;
373      innerSetupPickGroup(commonGroup, parent);
374      SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
375
376      PickCallback* pickCb = NULL;
377      
378    // add actions that become macro and command invocations
379    std::vector<SGPropertyNode_ptr> actions;
380    actions = getConfig()->getChildren("action");
381    for (unsigned int i = 0; i < actions.size(); ++i) {
382      pickCb = new PickCallback(actions[i], getModelRoot());
383      ud->addPickCallback(pickCb);
384    }
385      
386    if (getConfig()->hasChild("hovered")) {
387      if (!pickCb) {
388        // make a trivial PickCallback to hang the hovered off of
389        SGPropertyNode_ptr dummyNode(new SGPropertyNode);
390        pickCb = new PickCallback(dummyNode.ptr(), getModelRoot());
391        ud->addPickCallback(pickCb);
392      }
393        
394      pickCb->addHoverBindings(getConfig()->getNode("hovered"), getModelRoot());
395    }
396      
397    // Look for the VNC sessions that want raw mouse input
398    actions = getConfig()->getChildren("vncaction");
399    for (unsigned int i = 0; i < actions.size(); ++i)
400      ud->addPickCallback(new VncCallback(actions[i], getModelRoot(),
401        &parent));
402
403    return commonGroup;
404 }
405
406 ///////////////////////////////////////////////////////////////////////////
407
408 // insert count copies of binding list A, into the output list.
409 // effectively makes the output list fire binding A multiple times
410 // in sequence
411 static void repeatBindings(const SGBindingList& a, SGBindingList& out, int count)
412 {
413     out.clear();
414     for (int i=0; i<count; ++i) {
415         out.insert(out.end(), a.begin(), a.end());
416     }
417 }
418
419 static bool static_knobMouseWheelAlternateDirection = false;
420 static bool static_knobDragAlternateAxis = false;
421 static double static_dragSensitivity = 1.0;
422
423 class KnobSliderPickCallback : public SGPickCallback {
424 public:
425     enum Direction
426     {
427         DIRECTION_NONE,
428         DIRECTION_INCREASE,
429         DIRECTION_DECREASE
430     };
431     
432     enum DragDirection
433     {
434         DRAG_DEFAULT = 0,
435         DRAG_VERTICAL,
436         DRAG_HORIZONTAL
437     };
438
439     
440     KnobSliderPickCallback(const SGPropertyNode* configNode,
441                  SGPropertyNode* modelRoot) :
442         SGPickCallback(PriorityPanel),
443         _direction(DIRECTION_NONE),
444         _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1)),
445         _dragDirection(DRAG_DEFAULT)
446     {
447         readOptionalBindingList(configNode, modelRoot, "action", _action);
448         readOptionalBindingList(configNode, modelRoot, "increase", _bindingsIncrease);
449         readOptionalBindingList(configNode, modelRoot, "decrease", _bindingsDecrease);
450         
451         readOptionalBindingList(configNode, modelRoot, "release", _releaseAction);
452         readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
453         
454         if (configNode->hasChild("shift-action") || configNode->hasChild("shift-increase") ||
455             configNode->hasChild("shift-decrease"))
456         {
457         // explicit shifted behaviour - just do exactly what was provided
458             readOptionalBindingList(configNode, modelRoot, "shift-action", _shiftedAction);
459             readOptionalBindingList(configNode, modelRoot, "shift-increase", _shiftedIncrease);
460             readOptionalBindingList(configNode, modelRoot, "shift-decrease", _shiftedDecrease);
461         } else {
462             // default shifted behaviour - repeat normal bindings N times.
463             int shiftRepeat = configNode->getIntValue("shift-repeat", 10);
464             repeatBindings(_action, _shiftedAction, shiftRepeat);
465             repeatBindings(_bindingsIncrease, _shiftedIncrease, shiftRepeat);
466             repeatBindings(_bindingsDecrease, _shiftedDecrease, shiftRepeat);
467         } // of default shifted behaviour
468         
469         _dragScale = configNode->getDoubleValue("drag-scale-px", 10.0);
470         std::string dragDir = configNode->getStringValue("drag-direction");
471         if (dragDir == "vertical") {
472             _dragDirection = DRAG_VERTICAL;
473         } else if (dragDir == "horizontal") {
474             _dragDirection = DRAG_HORIZONTAL;
475         }
476       
477         if (configNode->hasChild("cursor")) {
478             _cursorName = configNode->getStringValue("cursor");
479         } else {
480           DragDirection dir = effectiveDragDirection();
481           if (dir == DRAG_VERTICAL) {
482             _cursorName = "drag-vertical";
483           } else if (dir == DRAG_HORIZONTAL) {
484             _cursorName = "drag-horizontal";
485           }
486         }
487     }
488     
489     virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
490     {        
491         // the 'be nice to Mac / laptop' users option; alt-clicking spins the
492         // opposite direction. Should make this configurable
493         if ((button == 0) && (ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_ALT)) {
494             button = 1;
495         }
496         
497         int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4;
498         int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3;
499             
500         _direction = DIRECTION_NONE;
501         if ((button == 0) || (button == increaseMouseWheel)) {
502             _direction = DIRECTION_INCREASE;
503         } else if ((button == 1) || (button == decreaseMouseWheel)) {
504             _direction = DIRECTION_DECREASE;
505         } else {
506             return false;
507         }
508         
509         _lastFirePos = eventToWindowCoords(ea);
510     // delay start of repeat, makes dragging more usable
511         _repeatTime = -_repeatInterval;    
512         _hasDragged = false;
513         return true;
514     }
515     
516     virtual void buttonReleased(int keyModState)
517     {
518         // for *clicks*, we only fire on button release
519         if (!_hasDragged) {
520             fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
521         }
522         
523         fireBindingList(_releaseAction);
524     }
525   
526     DragDirection effectiveDragDirection() const
527     {
528       if (_dragDirection == DRAG_DEFAULT) {
529         // respect the current default settings - this allows runtime
530         // setting of the default drag direction.
531         return static_knobDragAlternateAxis ? DRAG_VERTICAL : DRAG_HORIZONTAL;
532       }
533       
534       return _dragDirection;
535   }
536   
537     virtual void mouseMoved(const osgGA::GUIEventAdapter* ea)
538     {
539         _mousePos = eventToWindowCoords(ea);
540         osg::Vec2d deltaMouse = _mousePos - _lastFirePos;
541         
542         if (!_hasDragged) {
543             
544             double manhattanDist = deltaMouse.x() * deltaMouse.x()  + deltaMouse.y() * deltaMouse.y();
545             if (manhattanDist < 5) {
546                 // don't do anything, just input noise
547                 return;
548             }
549             
550         // user is dragging, disable repeat behaviour
551             _hasDragged = true;
552         }
553       
554         double delta = (effectiveDragDirection() == DRAG_VERTICAL) ? deltaMouse.y() : deltaMouse.x();
555     // per-animation scale factor lets the aircraft author tune for expectations,
556     // eg heading setting vs 5-state switch.
557     // then we scale by a global sensitivity, which the user can set.
558         delta *= static_dragSensitivity / _dragScale;
559         
560         if (fabs(delta) >= 1.0) {
561             // determine direction from sign of delta
562             Direction dir = (delta > 0.0) ? DIRECTION_INCREASE : DIRECTION_DECREASE;
563             fire(ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_SHIFT, dir);
564             _lastFirePos = _mousePos;
565         }
566     }
567     
568     virtual void update(double dt, int keyModState)
569     {
570         if (_hasDragged) {
571             return;
572         }
573         
574         _repeatTime += dt;
575         while (_repeatInterval < _repeatTime) {
576             _repeatTime -= _repeatInterval;
577             fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
578         } // of repeat iteration
579     }
580
581     virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
582     {
583         if (_hover.empty()) {
584             return false;
585         }
586        
587         SGPropertyNode_ptr params(new SGPropertyNode);
588         params->setDoubleValue("x", windowPos.x());
589         params->setDoubleValue("y", windowPos.y());
590         fireBindingList(_hover, params.ptr());
591         return true;
592     }
593   
594     void setCursor(const std::string& aName)
595     {
596       _cursorName = aName;
597     }
598   
599     virtual std::string getCursor() const
600     { return _cursorName; }
601   
602 private:
603     void fire(bool isShifted, Direction dir)
604     {
605         const SGBindingList& act(isShifted ? _shiftedAction : _action);
606         const SGBindingList& incr(isShifted ? _shiftedIncrease : _bindingsIncrease);
607         const SGBindingList& decr(isShifted ? _shiftedDecrease : _bindingsDecrease);
608         
609         switch (dir) {
610             case DIRECTION_INCREASE:
611                 fireBindingListWithOffset(act,  1, 1);
612                 fireBindingList(incr);
613                 break;
614             case DIRECTION_DECREASE:
615                 fireBindingListWithOffset(act, -1, 1);
616                 fireBindingList(decr);
617                 break;
618             default: break;
619         }
620     }
621     
622     SGBindingList _action, _shiftedAction;
623     SGBindingList _releaseAction;
624     SGBindingList _bindingsIncrease, _shiftedIncrease,
625         _bindingsDecrease, _shiftedDecrease;
626     SGBindingList _hover;
627     
628         
629     Direction _direction;
630     double _repeatInterval;
631     double _repeatTime;
632     
633     DragDirection _dragDirection;
634     bool _hasDragged; ///< has the mouse been dragged since the press?
635     osg::Vec2d _mousePos, ///< current window coords location of the mouse
636         _lastFirePos; ///< mouse location where we last fired the bindings
637     double _dragScale;
638   
639     std::string _cursorName;
640 };
641
642 ///////////////////////////////////////////////////////////////////////////////
643
644 class SGKnobAnimation::UpdateCallback : public osg::NodeCallback {
645 public:
646     UpdateCallback(SGExpressiond const* animationValue) :
647         _animationValue(animationValue)
648     {
649         setName("SGKnobAnimation::UpdateCallback");
650     }
651     virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
652     {
653         SGRotateTransform* transform = static_cast<SGRotateTransform*>(node);
654         transform->setAngleDeg(_animationValue->getValue());
655         traverse(node, nv);
656     }
657     
658 private:
659     SGSharedPtr<SGExpressiond const> _animationValue;
660 };
661
662
663 SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
664                                  SGPropertyNode* modelRoot) :
665     SGPickAnimation(configNode, modelRoot)
666 {
667     SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-deg",
668                                                   -SGLimitsd::max(), SGLimitsd::max());
669     _animationValue = value->simplify();
670     
671     
672     readRotationCenterAndAxis(configNode, _center, _axis);
673 }
674
675
676 osg::Group*
677 SGKnobAnimation::createAnimationGroup(osg::Group& parent)
678 {
679     SGRotateTransform* transform = new SGRotateTransform();
680     innerSetupPickGroup(transform, parent);
681     
682     UpdateCallback* uc = new UpdateCallback(_animationValue);
683     transform->setUpdateCallback(uc);
684     transform->setCenter(_center);
685     transform->setAxis(_axis);
686         
687     SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
688     ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
689     
690     return transform;
691 }
692
693 void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle)
694 {
695     static_knobMouseWheelAlternateDirection = aToggle;
696 }
697
698 void SGKnobAnimation::setAlternateDragAxis(bool aToggle)
699 {
700     static_knobDragAlternateAxis = aToggle;
701 }
702
703 void SGKnobAnimation::setDragSensitivity(double aFactor)
704 {
705     static_dragSensitivity = aFactor;
706 }
707
708 ///////////////////////////////////////////////////////////////////////////////
709
710 class SGSliderAnimation::UpdateCallback : public osg::NodeCallback {
711 public:
712     UpdateCallback(SGExpressiond const* animationValue) :
713     _animationValue(animationValue)
714     {
715         setName("SGSliderAnimation::UpdateCallback");
716     }
717     virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
718     {
719         SGTranslateTransform* transform = static_cast<SGTranslateTransform*>(node);
720         transform->setValue(_animationValue->getValue());
721
722         traverse(node, nv);
723     }
724     
725 private:
726     SGSharedPtr<SGExpressiond const> _animationValue;
727 };
728
729
730 SGSliderAnimation::SGSliderAnimation(const SGPropertyNode* configNode,
731                                  SGPropertyNode* modelRoot) :
732     SGPickAnimation(configNode, modelRoot)
733 {
734     SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-m",
735                                                   -SGLimitsd::max(), SGLimitsd::max());
736     _animationValue = value->simplify();
737     
738     _axis = readTranslateAxis(configNode);
739 }
740
741 osg::Group*
742 SGSliderAnimation::createAnimationGroup(osg::Group& parent)
743 {
744     SGTranslateTransform* transform = new SGTranslateTransform();
745     innerSetupPickGroup(transform, parent);
746     
747     UpdateCallback* uc = new UpdateCallback(_animationValue);
748     transform->setUpdateCallback(uc);
749     transform->setAxis(_axis);
750     
751     SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
752     ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
753     
754     return transform;
755 }