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