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