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>
26 #include <osg/PolygonOffset>
27 #include <osg/PolygonMode>
28 #include <osg/Material>
29 #include <osgGA/GUIEventAdapter>
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>
40 using namespace simgear;
42 using OpenThreads::Mutex;
43 using OpenThreads::ScopedLock;
45 static void readOptionalBindingList(const SGPropertyNode* aNode, SGPropertyNode* modelRoot,
46 const std::string& aName, SGBindingList& aBindings)
48 const SGPropertyNode* n = aNode->getChild(aName);
50 aBindings = readBindingList(n->getChildren("binding"), modelRoot);
55 osg::Vec2d eventToWindowCoords(const osgGA::GUIEventAdapter* ea)
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;
68 return osg::Vec2d(x, y);
71 class SGPickAnimation::PickCallback : public SGPickCallback {
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))
79 std::vector<SGPropertyNode_ptr> bindings;
81 bindings = configNode->getChildren("button");
82 for (unsigned int i = 0; i < bindings.size(); ++i) {
83 _buttons.insert( bindings[i]->getIntValue() );
86 _bindingsDown = readBindingList(configNode->getChildren("binding"), modelRoot);
87 readOptionalBindingList(configNode, modelRoot, "mod-up", _bindingsUp);
90 if (configNode->hasChild("cursor")) {
91 _cursorName = configNode->getStringValue("cursor");
95 void addHoverBindings(const SGPropertyNode* hoverNode,
96 SGPropertyNode* modelRoot)
98 _hover = readBindingList(hoverNode->getChildren("binding"), modelRoot);
101 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
103 if (_buttons.find(button) == _buttons.end()) {
107 fireBindingList(_bindingsDown);
108 _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
111 virtual void buttonReleased(int keyModState)
113 SG_UNUSED(keyModState);
114 fireBindingList(_bindingsUp);
117 virtual void update(double dt, int keyModState)
119 SG_UNUSED(keyModState);
124 while (_repeatInterval < _repeatTime) {
125 _repeatTime -= _repeatInterval;
126 fireBindingList(_bindingsDown);
130 virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
132 if (_hover.empty()) {
136 SGPropertyNode_ptr params(new SGPropertyNode);
137 params->setDoubleValue("x", windowPos.x());
138 params->setDoubleValue("y", windowPos.y());
139 fireBindingList(_hover, params.ptr());
143 std::string getCursor() const
144 { return _cursorName; }
146 SGBindingList _bindingsDown;
147 SGBindingList _bindingsUp;
148 SGBindingList _hover;
149 std::set<int> _buttons;
151 double _repeatInterval;
153 std::string _cursorName;
156 class VncVisitor : public osg::NodeVisitor {
158 VncVisitor(double x, double y, int mask) :
159 osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
160 _texX(x), _texY(y), _mask(mask), _done(false)
162 SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
163 << x << "," << y << " mask " << mask);
166 virtual void apply(osg::Node &node)
168 // Some nodes have state sets attached
169 touchStateSet(node.getStateSet());
173 // See whether we are a geode worth exploring
174 osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
176 // Go find all its drawables
177 int i = g->getNumDrawables();
179 osg::Drawable *d = g->getDrawable(i);
180 if (d) touchDrawable(*d);
182 // Out of optimism, do the same for EffectGeode
183 simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
185 for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
186 di != eg->drawablesEnd(); di++) {
189 // Now see whether the EffectGeode has an Effect
190 simgear::Effect *e = eg->getEffect();
192 touchStateSet(e->getDefaultStateSet());
196 inline void touchDrawable(osg::Drawable &d)
198 osg::StateSet *ss = d.getStateSet();
202 void touchStateSet(osg::StateSet *ss)
205 osg::StateAttribute *sa = ss->getTextureAttribute(0,
206 osg::StateAttribute::TEXTURE);
208 osg::Texture *t = sa->asTexture();
210 osg::Image *img = t->getImage(0);
213 int pixX = _texX * img->s();
214 int pixY = _texY * img->t();
215 _done = img->sendPointerEvent(pixX, pixY, _mask);
216 SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor image said " << _done
217 << " to coord " << pixX << "," << pixY);
221 inline bool wasSuccessful()
232 ///////////////////////////////////////////////////////////////////////////////
234 class SGPickAnimation::VncCallback : public SGPickCallback {
236 VncCallback(const SGPropertyNode* configNode,
237 SGPropertyNode* modelRoot,
241 SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback");
242 const char *cornernames[3] = {"top-left", "top-right", "bottom-left"};
243 SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown};
244 for (int c =0; c < 3; c++) {
245 const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]);
246 *cornercoords[c] = SGVec3d(
247 cornerNode->getDoubleValue("x"),
248 cornerNode->getDoubleValue("y"),
249 cornerNode->getDoubleValue("z"));
251 _toRight -= _topLeft;
253 _squaredRight = dot(_toRight, _toRight);
254 _squaredDown = dot(_toDown, _toDown);
257 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
259 SGVec3d loc(info.local);
260 SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
262 _x = dot(loc, _toRight) / _squaredRight;
263 _y = dot(loc, _toDown) / _squaredDown;
264 if (_x<0) _x = 0; else if (_x > 1) _x = 1;
265 if (_y<0) _y = 0; else if (_y > 1) _y = 1;
266 VncVisitor vv(_x, _y, 1 << button);
268 return vv.wasSuccessful();
271 virtual void buttonReleased(int keyModState)
273 SG_UNUSED(keyModState);
274 SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
275 VncVisitor vv(_x, _y, 0);
281 osg::ref_ptr<osg::Group> _node;
282 SGVec3d _topLeft, _toRight, _toDown;
283 double _squaredRight, _squaredDown;
286 ///////////////////////////////////////////////////////////////////////////////
288 SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
289 SGPropertyNode* modelRoot) :
290 SGAnimation(configNode, modelRoot)
296 OpenThreads::Mutex colorModeUniformMutex;
297 osg::ref_ptr<osg::Uniform> colorModeUniform;
302 SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent)
304 // Contains the normal geometry that is interactive
305 osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
306 normalGroup->setName("pick normal group");
307 normalGroup->addChild(commonGroup);
309 // Used to render the geometry with just yellow edges
310 osg::Group* highlightGroup = new osg::Group;
311 highlightGroup->setName("pick highlight group");
312 highlightGroup->setNodeMask(simgear::PICK_BIT);
313 highlightGroup->addChild(commonGroup);
315 // prepare a state set that paints the edges of this object yellow
316 // The material and texture attributes are set with
317 // OVERRIDE|PROTECTED in case there is a material animation on a
318 // higher node in the scene graph, which would have its material
319 // attribute set with OVERRIDE.
320 osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
321 osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
322 stateSet->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 stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
330 stateSet->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 stateSet->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 stateSet->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 osg::Uniform* cmUniform = 0;
352 ScopedLock<Mutex> lock(colorModeUniformMutex);
353 if (!colorModeUniform.valid()) {
354 colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
355 colorModeUniform->set(0); // MODE_OFF
356 colorModeUniform->setDataVariance(osg::Object::STATIC);
358 cmUniform = colorModeUniform.get();
360 stateSet->addUniform(cmUniform,
361 osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
363 // Only add normal geometry if configured
364 if (getConfig()->getBoolValue("visible", true))
365 parent.addChild(normalGroup.get());
366 parent.addChild(highlightGroup);
370 SGPickAnimation::createAnimationGroup(osg::Group& parent)
372 osg::Group* commonGroup = new osg::Group;
373 innerSetupPickGroup(commonGroup, parent);
374 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
376 PickCallback* pickCb = NULL;
378 // add actions that become macro and command invocations
379 std::vector<SGPropertyNode_ptr> actions;
380 actions = getConfig()->getChildren("action");
381 for (unsigned int i = 0; i < actions.size(); ++i) {
382 pickCb = new PickCallback(actions[i], getModelRoot());
383 ud->addPickCallback(pickCb);
386 if (getConfig()->hasChild("hovered")) {
388 // make a trivial PickCallback to hang the hovered off of
389 SGPropertyNode_ptr dummyNode(new SGPropertyNode);
390 pickCb = new PickCallback(dummyNode.ptr(), getModelRoot());
391 ud->addPickCallback(pickCb);
394 pickCb->addHoverBindings(getConfig()->getNode("hovered"), getModelRoot());
397 // Look for the VNC sessions that want raw mouse input
398 actions = getConfig()->getChildren("vncaction");
399 for (unsigned int i = 0; i < actions.size(); ++i)
400 ud->addPickCallback(new VncCallback(actions[i], getModelRoot(),
406 ///////////////////////////////////////////////////////////////////////////
408 // insert count copies of binding list A, into the output list.
409 // effectively makes the output list fire binding A multiple times
411 static void repeatBindings(const SGBindingList& a, SGBindingList& out, int count)
414 for (int i=0; i<count; ++i) {
415 out.insert(out.end(), a.begin(), a.end());
419 static bool static_knobMouseWheelAlternateDirection = false;
420 static bool static_knobDragAlternateAxis = false;
421 static double static_dragSensitivity = 1.0;
423 class KnobSliderPickCallback : public SGPickCallback {
440 KnobSliderPickCallback(const SGPropertyNode* configNode,
441 SGPropertyNode* modelRoot) :
442 SGPickCallback(PriorityPanel),
443 _direction(DIRECTION_NONE),
444 _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1)),
445 _dragDirection(DRAG_DEFAULT)
447 readOptionalBindingList(configNode, modelRoot, "action", _action);
448 readOptionalBindingList(configNode, modelRoot, "increase", _bindingsIncrease);
449 readOptionalBindingList(configNode, modelRoot, "decrease", _bindingsDecrease);
451 readOptionalBindingList(configNode, modelRoot, "release", _releaseAction);
452 readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
454 if (configNode->hasChild("shift-action") || configNode->hasChild("shift-increase") ||
455 configNode->hasChild("shift-decrease"))
457 // explicit shifted behaviour - just do exactly what was provided
458 readOptionalBindingList(configNode, modelRoot, "shift-action", _shiftedAction);
459 readOptionalBindingList(configNode, modelRoot, "shift-increase", _shiftedIncrease);
460 readOptionalBindingList(configNode, modelRoot, "shift-decrease", _shiftedDecrease);
462 // default shifted behaviour - repeat normal bindings N times.
463 int shiftRepeat = configNode->getIntValue("shift-repeat", 10);
464 repeatBindings(_action, _shiftedAction, shiftRepeat);
465 repeatBindings(_bindingsIncrease, _shiftedIncrease, shiftRepeat);
466 repeatBindings(_bindingsDecrease, _shiftedDecrease, shiftRepeat);
467 } // of default shifted behaviour
469 _dragScale = configNode->getDoubleValue("drag-scale-px", 10.0);
470 std::string dragDir = configNode->getStringValue("drag-direction");
471 if (dragDir == "vertical") {
472 _dragDirection = DRAG_VERTICAL;
473 } else if (dragDir == "horizontal") {
474 _dragDirection = DRAG_HORIZONTAL;
477 if (configNode->hasChild("cursor")) {
478 _cursorName = configNode->getStringValue("cursor");
480 DragDirection dir = effectiveDragDirection();
481 if (dir == DRAG_VERTICAL) {
482 _cursorName = "drag-vertical";
483 } else if (dir == DRAG_HORIZONTAL) {
484 _cursorName = "drag-horizontal";
489 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
491 // the 'be nice to Mac / laptop' users option; alt-clicking spins the
492 // opposite direction. Should make this configurable
493 if ((button == 0) && (ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_ALT)) {
497 int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4;
498 int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3;
500 _direction = DIRECTION_NONE;
501 if ((button == 0) || (button == increaseMouseWheel)) {
502 _direction = DIRECTION_INCREASE;
503 } else if ((button == 1) || (button == decreaseMouseWheel)) {
504 _direction = DIRECTION_DECREASE;
509 _lastFirePos = eventToWindowCoords(ea);
510 // delay start of repeat, makes dragging more usable
511 _repeatTime = -_repeatInterval;
516 virtual void buttonReleased(int keyModState)
518 // for *clicks*, we only fire on button release
520 fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
523 fireBindingList(_releaseAction);
526 DragDirection effectiveDragDirection() const
528 if (_dragDirection == DRAG_DEFAULT) {
529 // respect the current default settings - this allows runtime
530 // setting of the default drag direction.
531 return static_knobDragAlternateAxis ? DRAG_VERTICAL : DRAG_HORIZONTAL;
534 return _dragDirection;
537 virtual void mouseMoved(const osgGA::GUIEventAdapter* ea)
539 _mousePos = eventToWindowCoords(ea);
540 osg::Vec2d deltaMouse = _mousePos - _lastFirePos;
544 double manhattanDist = deltaMouse.x() * deltaMouse.x() + deltaMouse.y() * deltaMouse.y();
545 if (manhattanDist < 5) {
546 // don't do anything, just input noise
550 // user is dragging, disable repeat behaviour
554 double delta = (effectiveDragDirection() == DRAG_VERTICAL) ? deltaMouse.y() : deltaMouse.x();
555 // per-animation scale factor lets the aircraft author tune for expectations,
556 // eg heading setting vs 5-state switch.
557 // then we scale by a global sensitivity, which the user can set.
558 delta *= static_dragSensitivity / _dragScale;
560 if (fabs(delta) >= 1.0) {
561 // determine direction from sign of delta
562 Direction dir = (delta > 0.0) ? DIRECTION_INCREASE : DIRECTION_DECREASE;
563 fire(ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_SHIFT, dir);
564 _lastFirePos = _mousePos;
568 virtual void update(double dt, int keyModState)
575 while (_repeatInterval < _repeatTime) {
576 _repeatTime -= _repeatInterval;
577 fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
578 } // of repeat iteration
581 virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
583 if (_hover.empty()) {
587 SGPropertyNode_ptr params(new SGPropertyNode);
588 params->setDoubleValue("x", windowPos.x());
589 params->setDoubleValue("y", windowPos.y());
590 fireBindingList(_hover, params.ptr());
594 void setCursor(const std::string& aName)
599 virtual std::string getCursor() const
600 { return _cursorName; }
603 void fire(bool isShifted, Direction dir)
605 const SGBindingList& act(isShifted ? _shiftedAction : _action);
606 const SGBindingList& incr(isShifted ? _shiftedIncrease : _bindingsIncrease);
607 const SGBindingList& decr(isShifted ? _shiftedDecrease : _bindingsDecrease);
610 case DIRECTION_INCREASE:
611 fireBindingListWithOffset(act, 1, 1);
612 fireBindingList(incr);
614 case DIRECTION_DECREASE:
615 fireBindingListWithOffset(act, -1, 1);
616 fireBindingList(decr);
622 SGBindingList _action, _shiftedAction;
623 SGBindingList _releaseAction;
624 SGBindingList _bindingsIncrease, _shiftedIncrease,
625 _bindingsDecrease, _shiftedDecrease;
626 SGBindingList _hover;
629 Direction _direction;
630 double _repeatInterval;
633 DragDirection _dragDirection;
634 bool _hasDragged; ///< has the mouse been dragged since the press?
635 osg::Vec2d _mousePos, ///< current window coords location of the mouse
636 _lastFirePos; ///< mouse location where we last fired the bindings
639 std::string _cursorName;
642 ///////////////////////////////////////////////////////////////////////////////
644 class SGKnobAnimation::UpdateCallback : public osg::NodeCallback {
646 UpdateCallback(SGExpressiond const* animationValue) :
647 _animationValue(animationValue)
649 setName("SGKnobAnimation::UpdateCallback");
651 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
653 SGRotateTransform* transform = static_cast<SGRotateTransform*>(node);
654 transform->setAngleDeg(_animationValue->getValue());
659 SGSharedPtr<SGExpressiond const> _animationValue;
663 SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
664 SGPropertyNode* modelRoot) :
665 SGPickAnimation(configNode, modelRoot)
667 SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-deg",
668 -SGLimitsd::max(), SGLimitsd::max());
669 _animationValue = value->simplify();
672 readRotationCenterAndAxis(configNode, _center, _axis);
677 SGKnobAnimation::createAnimationGroup(osg::Group& parent)
679 SGRotateTransform* transform = new SGRotateTransform();
680 innerSetupPickGroup(transform, parent);
682 UpdateCallback* uc = new UpdateCallback(_animationValue);
683 transform->setUpdateCallback(uc);
684 transform->setCenter(_center);
685 transform->setAxis(_axis);
687 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
688 ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
693 void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle)
695 static_knobMouseWheelAlternateDirection = aToggle;
698 void SGKnobAnimation::setAlternateDragAxis(bool aToggle)
700 static_knobDragAlternateAxis = aToggle;
703 void SGKnobAnimation::setDragSensitivity(double aFactor)
705 static_dragSensitivity = aFactor;
708 ///////////////////////////////////////////////////////////////////////////////
710 class SGSliderAnimation::UpdateCallback : public osg::NodeCallback {
712 UpdateCallback(SGExpressiond const* animationValue) :
713 _animationValue(animationValue)
715 setName("SGSliderAnimation::UpdateCallback");
717 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
719 SGTranslateTransform* transform = static_cast<SGTranslateTransform*>(node);
720 transform->setValue(_animationValue->getValue());
726 SGSharedPtr<SGExpressiond const> _animationValue;
730 SGSliderAnimation::SGSliderAnimation(const SGPropertyNode* configNode,
731 SGPropertyNode* modelRoot) :
732 SGPickAnimation(configNode, modelRoot)
734 SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-m",
735 -SGLimitsd::max(), SGLimitsd::max());
736 _animationValue = value->simplify();
738 _axis = readTranslateAxis(configNode);
742 SGSliderAnimation::createAnimationGroup(osg::Group& parent)
744 SGTranslateTransform* transform = new SGTranslateTransform();
745 innerSetupPickGroup(transform, parent);
747 UpdateCallback* uc = new UpdateCallback(_animationValue);
748 transform->setUpdateCallback(uc);
749 transform->setAxis(_axis);
751 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
752 ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));