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