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