3 * Copyright (C) 2013 James Turner
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.
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.
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,
23 #include <simgear/scene/model/SGPickAnimation.hxx>
28 #include <osg/PolygonOffset>
29 #include <osg/PolygonMode>
30 #include <osg/Material>
31 #include <osgGA/GUIEventAdapter>
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>
42 using namespace simgear;
44 using OpenThreads::Mutex;
45 using OpenThreads::ScopedLock;
47 static void readOptionalBindingList(const SGPropertyNode* aNode, SGPropertyNode* modelRoot,
48 const std::string& aName, SGBindingList& aBindings)
50 const SGPropertyNode* n = aNode->getChild(aName);
52 aBindings = readBindingList(n->getChildren("binding"), modelRoot);
57 osg::Vec2d eventToWindowCoords(const osgGA::GUIEventAdapter* ea)
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;
70 return osg::Vec2d(x, y);
73 class SGPickAnimation::PickCallback : public SGPickCallback {
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))
81 std::vector<SGPropertyNode_ptr> bindings;
83 bindings = configNode->getChildren("button");
84 for (unsigned int i = 0; i < bindings.size(); ++i) {
85 _buttons.insert( bindings[i]->getIntValue() );
88 _bindingsDown = readBindingList(configNode->getChildren("binding"), modelRoot);
89 readOptionalBindingList(configNode, modelRoot, "mod-up", _bindingsUp);
92 if (configNode->hasChild("cursor")) {
93 _cursorName = configNode->getStringValue("cursor");
97 void addHoverBindings(const SGPropertyNode* hoverNode,
98 SGPropertyNode* modelRoot)
100 _hover = readBindingList(hoverNode->getChildren("binding"), modelRoot);
103 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
105 if (_buttons.find(button) == _buttons.end()) {
109 fireBindingList(_bindingsDown);
110 _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
113 virtual void buttonReleased(int keyModState)
115 SG_UNUSED(keyModState);
116 fireBindingList(_bindingsUp);
119 virtual void update(double dt, int keyModState)
121 SG_UNUSED(keyModState);
126 while (_repeatInterval < _repeatTime) {
127 _repeatTime -= _repeatInterval;
128 fireBindingList(_bindingsDown);
132 virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
134 if (_hover.empty()) {
138 SGPropertyNode_ptr params(new SGPropertyNode);
139 params->setDoubleValue("x", windowPos.x());
140 params->setDoubleValue("y", windowPos.y());
141 fireBindingList(_hover, params.ptr());
145 std::string getCursor() const
146 { return _cursorName; }
148 SGBindingList _bindingsDown;
149 SGBindingList _bindingsUp;
150 SGBindingList _hover;
151 std::set<int> _buttons;
153 double _repeatInterval;
155 std::string _cursorName;
158 class VncVisitor : public osg::NodeVisitor {
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)
164 SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
165 << x << "," << y << " mask " << mask);
168 virtual void apply(osg::Node &node)
170 // Some nodes have state sets attached
171 touchStateSet(node.getStateSet());
175 // See whether we are a geode worth exploring
176 osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
178 // Go find all its drawables
179 int i = g->getNumDrawables();
181 osg::Drawable *d = g->getDrawable(i);
182 if (d) touchDrawable(*d);
184 // Out of optimism, do the same for EffectGeode
185 simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
187 for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
188 di != eg->drawablesEnd(); di++) {
191 // Now see whether the EffectGeode has an Effect
192 simgear::Effect *e = eg->getEffect();
194 touchStateSet(e->getDefaultStateSet());
198 inline void touchDrawable(osg::Drawable &d)
200 osg::StateSet *ss = d.getStateSet();
204 void touchStateSet(osg::StateSet *ss)
207 osg::StateAttribute *sa = ss->getTextureAttribute(0,
208 osg::StateAttribute::TEXTURE);
210 osg::Texture *t = sa->asTexture();
212 osg::Image *img = t->getImage(0);
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);
223 inline bool wasSuccessful()
234 ///////////////////////////////////////////////////////////////////////////////
236 class SGPickAnimation::VncCallback : public SGPickCallback {
238 VncCallback(const SGPropertyNode* configNode,
239 SGPropertyNode* modelRoot,
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"));
253 _toRight -= _topLeft;
255 _squaredRight = dot(_toRight, _toRight);
256 _squaredDown = dot(_toDown, _toDown);
259 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
261 SGVec3d loc(info.local);
262 SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
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);
270 return vv.wasSuccessful();
273 virtual void buttonReleased(int keyModState)
275 SG_UNUSED(keyModState);
276 SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
277 VncVisitor vv(_x, _y, 0);
283 osg::ref_ptr<osg::Group> _node;
284 SGVec3d _topLeft, _toRight, _toDown;
285 double _squaredRight, _squaredDown;
288 ///////////////////////////////////////////////////////////////////////////////
290 SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
291 SGPropertyNode* modelRoot) :
292 SGAnimation(configNode, modelRoot)
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());
301 void SGPickAnimation::apply(osg::Node* node)
303 SGAnimation::apply(node);
308 OpenThreads::Mutex highlightStateSetMutex;
309 osg::ref_ptr<osg::StateSet> static_highlightStateSet;
315 osg::StateSet* sharedHighlightStateSet()
317 ScopedLock<Mutex> lock(highlightStateSetMutex);
318 if (!static_highlightStateSet.valid()) {
319 static_highlightStateSet = new osg::StateSet;
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.
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);
358 return static_highlightStateSet.get();
362 SGPickAnimation::apply(osg::Group& group)
364 if (_objectNames.empty()) {
368 osg::ref_ptr<osg::Group> renderGroup, proxyGroup, pickGroup;
369 group.traverse(*this);
370 SGSceneUserData* ud = NULL;
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()) {
380 std::list<std::string>::iterator it = std::find(_objectNames.begin(), _objectNames.end(), child->getName());
381 if (it != _objectNames.end()) {
382 _objectNames.erase(it);
387 osg::Group* mainGroup = createMainGroup(&group);
388 mainGroup->setName(child->getName());
390 if (getConfig()->getBoolValue("visible", true)) {
391 renderGroup = new osg::Group;
392 renderGroup->setName("pick render group");
393 mainGroup->addChild(renderGroup);
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);
403 ud = SGSceneUserData::getOrCreateSceneUserData(pickGroup);
404 setupCallbacks(ud, &group);
406 pickGroup->setUserData(ud);
408 } // of pick group setup
412 if (renderGroup.valid()) {
413 renderGroup->addChild(child);
416 pickGroup->addChild(child);
417 group.removeChild(child);
421 string_list::iterator j = std::find(_proxyNames.begin(), _proxyNames.end(), child->getName());
422 if (j == _proxyNames.end()) {
426 _proxyNames.erase(j);
428 proxyGroup = new osg::Group;
429 group.addChild(proxyGroup);
430 proxyGroup->setStateSet(sharedHighlightStateSet());
431 proxyGroup->setNodeMask(simgear::PICK_BIT);
434 ud = SGSceneUserData::getOrCreateSceneUserData(proxyGroup);
435 setupCallbacks(ud, &group);
437 proxyGroup->setUserData(ud);
439 } // of proxy group setup
441 proxyGroup->addChild(child);
442 group.removeChild(child);
443 } // of group children iteration
447 SGPickAnimation::createMainGroup(osg::Group* pr)
449 osg::Group* g = new osg::Group;
455 SGPickAnimation::setupCallbacks(SGSceneUserData* ud, osg::Group* parent)
457 PickCallback* pickCb = NULL;
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);
467 if (getConfig()->hasChild("hovered")) {
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);
475 pickCb->addHoverBindings(getConfig()->getNode("hovered"), getModelRoot());
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));
485 ///////////////////////////////////////////////////////////////////////////
487 // insert count copies of binding list A, into the output list.
488 // effectively makes the output list fire binding A multiple times
490 static void repeatBindings(const SGBindingList& a, SGBindingList& out, int count)
493 for (int i=0; i<count; ++i) {
494 out.insert(out.end(), a.begin(), a.end());
498 static bool static_knobMouseWheelAlternateDirection = false;
499 static bool static_knobDragAlternateAxis = false;
500 static double static_dragSensitivity = 1.0;
502 class KnobSliderPickCallback : public SGPickCallback {
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)
526 readOptionalBindingList(configNode, modelRoot, "action", _action);
527 readOptionalBindingList(configNode, modelRoot, "increase", _bindingsIncrease);
528 readOptionalBindingList(configNode, modelRoot, "decrease", _bindingsDecrease);
530 readOptionalBindingList(configNode, modelRoot, "release", _releaseAction);
531 readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
533 if (configNode->hasChild("shift-action") || configNode->hasChild("shift-increase") ||
534 configNode->hasChild("shift-decrease"))
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);
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
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;
556 if (configNode->hasChild("cursor")) {
557 _cursorName = configNode->getStringValue("cursor");
559 DragDirection dir = effectiveDragDirection();
560 if (dir == DRAG_VERTICAL) {
561 _cursorName = "drag-vertical";
562 } else if (dir == DRAG_HORIZONTAL) {
563 _cursorName = "drag-horizontal";
568 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
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)) {
576 int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4;
577 int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3;
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;
588 _lastFirePos = eventToWindowCoords(ea);
589 // delay start of repeat, makes dragging more usable
590 _repeatTime = -_repeatInterval;
595 virtual void buttonReleased(int keyModState)
597 // for *clicks*, we only fire on button release
599 fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
602 fireBindingList(_releaseAction);
605 DragDirection effectiveDragDirection() const
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;
613 return _dragDirection;
616 virtual void mouseMoved(const osgGA::GUIEventAdapter* ea)
618 _mousePos = eventToWindowCoords(ea);
619 osg::Vec2d deltaMouse = _mousePos - _lastFirePos;
623 double manhattanDist = deltaMouse.x() * deltaMouse.x() + deltaMouse.y() * deltaMouse.y();
624 if (manhattanDist < 5) {
625 // don't do anything, just input noise
629 // user is dragging, disable repeat behaviour
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;
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;
647 virtual void update(double dt, int keyModState)
654 while (_repeatInterval < _repeatTime) {
655 _repeatTime -= _repeatInterval;
656 fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
657 } // of repeat iteration
660 virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
662 if (_hover.empty()) {
666 SGPropertyNode_ptr params(new SGPropertyNode);
667 params->setDoubleValue("x", windowPos.x());
668 params->setDoubleValue("y", windowPos.y());
669 fireBindingList(_hover, params.ptr());
673 void setCursor(const std::string& aName)
678 virtual std::string getCursor() const
679 { return _cursorName; }
682 void fire(bool isShifted, Direction dir)
684 const SGBindingList& act(isShifted ? _shiftedAction : _action);
685 const SGBindingList& incr(isShifted ? _shiftedIncrease : _bindingsIncrease);
686 const SGBindingList& decr(isShifted ? _shiftedDecrease : _bindingsDecrease);
689 case DIRECTION_INCREASE:
690 fireBindingListWithOffset(act, 1, 1);
691 fireBindingList(incr);
693 case DIRECTION_DECREASE:
694 fireBindingListWithOffset(act, -1, 1);
695 fireBindingList(decr);
701 SGBindingList _action, _shiftedAction;
702 SGBindingList _releaseAction;
703 SGBindingList _bindingsIncrease, _shiftedIncrease,
704 _bindingsDecrease, _shiftedDecrease;
705 SGBindingList _hover;
708 Direction _direction;
709 double _repeatInterval;
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
718 std::string _cursorName;
721 ///////////////////////////////////////////////////////////////////////////////
723 class SGKnobAnimation::UpdateCallback : public osg::NodeCallback {
725 UpdateCallback(SGExpressiond const* animationValue) :
726 _animationValue(animationValue)
728 setName("SGKnobAnimation::UpdateCallback");
730 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
732 SGRotateTransform* transform = static_cast<SGRotateTransform*>(node);
733 transform->setAngleDeg(_animationValue->getValue());
738 SGSharedPtr<SGExpressiond const> _animationValue;
742 SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
743 SGPropertyNode* modelRoot) :
744 SGPickAnimation(configNode, modelRoot)
746 SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-deg",
747 -SGLimitsd::max(), SGLimitsd::max());
748 _animationValue = value->simplify();
751 readRotationCenterAndAxis(configNode, _center, _axis);
755 SGKnobAnimation::createMainGroup(osg::Group* pr)
757 SGRotateTransform* transform = new SGRotateTransform();
759 UpdateCallback* uc = new UpdateCallback(_animationValue);
760 transform->setUpdateCallback(uc);
761 transform->setCenter(_center);
762 transform->setAxis(_axis);
764 pr->addChild(transform);
769 SGKnobAnimation::setupCallbacks(SGSceneUserData* ud, osg::Group*)
771 ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
774 void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle)
776 static_knobMouseWheelAlternateDirection = aToggle;
779 void SGKnobAnimation::setAlternateDragAxis(bool aToggle)
781 static_knobDragAlternateAxis = aToggle;
784 void SGKnobAnimation::setDragSensitivity(double aFactor)
786 static_dragSensitivity = aFactor;
789 ///////////////////////////////////////////////////////////////////////////////
791 class SGSliderAnimation::UpdateCallback : public osg::NodeCallback {
793 UpdateCallback(SGExpressiond const* animationValue) :
794 _animationValue(animationValue)
796 setName("SGSliderAnimation::UpdateCallback");
798 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
800 SGTranslateTransform* transform = static_cast<SGTranslateTransform*>(node);
801 transform->setValue(_animationValue->getValue());
807 SGSharedPtr<SGExpressiond const> _animationValue;
811 SGSliderAnimation::SGSliderAnimation(const SGPropertyNode* configNode,
812 SGPropertyNode* modelRoot) :
813 SGPickAnimation(configNode, modelRoot)
815 SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-m",
816 -SGLimitsd::max(), SGLimitsd::max());
817 _animationValue = value->simplify();
819 _axis = readTranslateAxis(configNode);
823 SGSliderAnimation::createMainGroup(osg::Group* pr)
825 SGTranslateTransform* transform = new SGTranslateTransform();
827 UpdateCallback* uc = new UpdateCallback(_animationValue);
828 transform->setUpdateCallback(uc);
829 transform->setAxis(_axis);
831 pr->addChild(transform);
836 SGSliderAnimation::setupCallbacks(SGSceneUserData* ud, osg::Group*)
838 ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));