]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/SGPickAnimation.cxx
Make the knob mouse-wheel direction configurable.
[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/scene/util/SGPickCallback.hxx>
32 #include <simgear/scene/material/EffectGeode.hxx>
33 #include <simgear/scene/util/SGSceneUserData.hxx>
34 #include <simgear/structure/SGBinding.hxx>
35 #include <simgear/scene/util/StateAttributeFactory.hxx>
36 #include <simgear/scene/model/SGRotateTransform.hxx>
37
38 using namespace simgear;
39
40 using OpenThreads::Mutex;
41 using OpenThreads::ScopedLock;
42
43  class SGPickAnimation::PickCallback : public SGPickCallback {
44  public:
45    PickCallback(const SGPropertyNode* configNode,
46                 SGPropertyNode* modelRoot) :
47      _repeatable(configNode->getBoolValue("repeatable", false)),
48      _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
49    {
50      SG_LOG(SG_INPUT, SG_DEBUG, "Reading all bindings");
51      std::vector<SGPropertyNode_ptr> bindings;
52
53      bindings = configNode->getChildren("button");
54      for (unsigned int i = 0; i < bindings.size(); ++i) {
55        _buttons.push_back( bindings[i]->getIntValue() );
56      }
57      bindings = configNode->getChildren("binding");
58      for (unsigned int i = 0; i < bindings.size(); ++i) {
59        _bindingsDown.push_back(new SGBinding(bindings[i], modelRoot));
60      }
61
62      const SGPropertyNode* upNode = configNode->getChild("mod-up");
63      if (!upNode)
64        return;
65      bindings = upNode->getChildren("binding");
66      for (unsigned int i = 0; i < bindings.size(); ++i) {
67        _bindingsUp.push_back(new SGBinding(bindings[i], modelRoot));
68      }
69    }
70    virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
71    {
72      bool found = false;
73      for( std::vector<int>::iterator it = _buttons.begin(); it != _buttons.end(); ++it ) {
74        if( *it == button ) {
75          found = true;
76          break;
77        }
78      }
79      if (!found )
80        return false;
81        
82      fireBindingList(_bindingsDown);
83      _repeatTime = -_repeatInterval;    // anti-bobble: delay start of repeat
84      return true;
85    }
86    virtual void buttonReleased(void)
87    {
88        fireBindingList(_bindingsUp);
89    }
90    virtual void update(double dt)
91    {
92      if (!_repeatable)
93        return;
94
95      _repeatTime += dt;
96      while (_repeatInterval < _repeatTime) {
97        _repeatTime -= _repeatInterval;
98          fireBindingList(_bindingsDown);
99      }
100    }
101  private:
102    SGBindingList _bindingsDown;
103    SGBindingList _bindingsUp;
104    std::vector<int> _buttons;
105    bool _repeatable;
106    double _repeatInterval;
107    double _repeatTime;
108  };
109
110  class VncVisitor : public osg::NodeVisitor {
111   public:
112    VncVisitor(double x, double y, int mask) :
113      osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
114      _texX(x), _texY(y), _mask(mask), _done(false)
115    {
116      SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
117        << x << "," << y << " mask " << mask);
118    }
119
120    virtual void apply(osg::Node &node)
121    {
122      // Some nodes have state sets attached
123      touchStateSet(node.getStateSet());
124      if (!_done)
125        traverse(node);
126      if (_done) return;
127      // See whether we are a geode worth exploring
128      osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
129      if (!g) return;
130      // Go find all its drawables
131      int i = g->getNumDrawables();
132      while (--i >= 0) {
133        osg::Drawable *d = g->getDrawable(i);
134        if (d) touchDrawable(*d);
135      }
136      // Out of optimism, do the same for EffectGeode
137      simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
138      if (!eg) return;
139      for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
140           di != eg->drawablesEnd(); di++) {
141        touchDrawable(**di);
142      }
143      // Now see whether the EffectGeode has an Effect
144      simgear::Effect *e = eg->getEffect();
145      if (e) {
146        touchStateSet(e->getDefaultStateSet());
147      }
148    }
149
150    inline void touchDrawable(osg::Drawable &d)
151    {
152      osg::StateSet *ss = d.getStateSet();
153      touchStateSet(ss);
154    }
155
156    void touchStateSet(osg::StateSet *ss)
157    {
158      if (!ss) return;
159      osg::StateAttribute *sa = ss->getTextureAttribute(0,
160        osg::StateAttribute::TEXTURE);
161      if (!sa) return;
162      osg::Texture *t = sa->asTexture();
163      if (!t) return;
164      osg::Image *img = t->getImage(0);
165      if (!img) return;
166      if (!_done) {
167        int pixX = _texX * img->s();
168        int pixY = _texY * img->t();
169        _done = img->sendPointerEvent(pixX, pixY, _mask);
170        SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor image said " << _done
171          << " to coord " << pixX << "," << pixY);
172      }
173    }
174
175    inline bool wasSuccessful()
176    {
177      return _done;
178    }
179
180   private:
181    double _texX, _texY;
182    int _mask;
183    bool _done;
184  };
185
186
187  class SGPickAnimation::VncCallback : public SGPickCallback {
188  public:
189    VncCallback(const SGPropertyNode* configNode,
190                 SGPropertyNode* modelRoot,
191                 osg::Group *node)
192        : _node(node)
193    {
194      SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback");
195      const char *cornernames[3] = {"top-left", "top-right", "bottom-left"};
196      SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown};
197      for (int c =0; c < 3; c++) {
198        const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]);
199        *cornercoords[c] = SGVec3d(
200          cornerNode->getDoubleValue("x"),
201          cornerNode->getDoubleValue("y"),
202          cornerNode->getDoubleValue("z"));
203      }
204      _toRight -= _topLeft;
205      _toDown -= _topLeft;
206      _squaredRight = dot(_toRight, _toRight);
207      _squaredDown = dot(_toDown, _toDown);
208    }
209
210    virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
211    {
212      SGVec3d loc(info.local);
213      SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
214      loc -= _topLeft;
215      _x = dot(loc, _toRight) / _squaredRight;
216      _y = dot(loc, _toDown) / _squaredDown;
217      if (_x<0) _x = 0; else if (_x > 1) _x = 1;
218      if (_y<0) _y = 0; else if (_y > 1) _y = 1;
219      VncVisitor vv(_x, _y, 1 << button);
220      _node->accept(vv);
221      return vv.wasSuccessful();
222
223    }
224    virtual void buttonReleased(void)
225    {
226      SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
227      VncVisitor vv(_x, _y, 0);
228      _node->accept(vv);
229    }
230    virtual void update(double dt)
231    {
232    }
233  private:
234    double _x, _y;
235    osg::ref_ptr<osg::Group> _node;
236    SGVec3d _topLeft, _toRight, _toDown;
237    double _squaredRight, _squaredDown;
238  };
239
240  SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
241                                   SGPropertyNode* modelRoot) :
242    SGAnimation(configNode, modelRoot)
243  {
244  }
245
246  namespace
247  {
248  OpenThreads::Mutex colorModeUniformMutex;
249  osg::ref_ptr<osg::Uniform> colorModeUniform;
250  }
251
252
253 void 
254 SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent)
255 {
256     // Contains the normal geometry that is interactive
257     osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
258     normalGroup->setName("pick normal group");
259     normalGroup->addChild(commonGroup);
260     
261     // Used to render the geometry with just yellow edges
262     osg::Group* highlightGroup = new osg::Group;
263     highlightGroup->setName("pick highlight group");
264     highlightGroup->setNodeMask(simgear::PICK_BIT);
265     highlightGroup->addChild(commonGroup);
266     
267     // prepare a state set that paints the edges of this object yellow
268     // The material and texture attributes are set with
269     // OVERRIDE|PROTECTED in case there is a material animation on a
270     // higher node in the scene graph, which would have its material
271     // attribute set with OVERRIDE.
272     osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
273     osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
274     stateSet->setTextureAttributeAndModes(0, white,
275                                           (osg::StateAttribute::ON
276                                            | osg::StateAttribute::OVERRIDE
277                                            | osg::StateAttribute::PROTECTED));
278     osg::PolygonOffset* polygonOffset = new osg::PolygonOffset;
279     polygonOffset->setFactor(-1);
280     polygonOffset->setUnits(-1);
281     stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
282     stateSet->setMode(GL_POLYGON_OFFSET_LINE,
283                       osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
284     osg::PolygonMode* polygonMode = new osg::PolygonMode;
285     polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK,
286                          osg::PolygonMode::LINE);
287     stateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
288     osg::Material* material = new osg::Material;
289     material->setColorMode(osg::Material::OFF);
290     material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
291     // XXX Alpha < 1.0 in the diffuse material value is a signal to the
292     // default shader to take the alpha value from the material value
293     // and not the glColor. In many cases the pick animation geometry is
294     // transparent, so the outline would not be visible without this hack.
295     material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, .95));
296     material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
297     material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0));
298     stateSet->setAttribute(
299                            material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
300     // The default shader has a colorMode uniform that mimics the
301     // behavior of Material color mode.
302     osg::Uniform* cmUniform = 0;
303     {
304         ScopedLock<Mutex> lock(colorModeUniformMutex);
305         if (!colorModeUniform.valid()) {
306             colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
307             colorModeUniform->set(0); // MODE_OFF
308             colorModeUniform->setDataVariance(osg::Object::STATIC);
309         }
310         cmUniform = colorModeUniform.get();
311     }
312     stateSet->addUniform(cmUniform,
313                          osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
314     
315     // Only add normal geometry if configured
316     if (getConfig()->getBoolValue("visible", true))
317         parent.addChild(normalGroup.get());
318     parent.addChild(highlightGroup);
319 }
320
321  osg::Group*
322  SGPickAnimation::createAnimationGroup(osg::Group& parent)
323  {
324      osg::Group* commonGroup = new osg::Group;
325      innerSetupPickGroup(commonGroup, parent);
326      SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
327
328    // add actions that become macro and command invocations
329    std::vector<SGPropertyNode_ptr> actions;
330    actions = getConfig()->getChildren("action");
331    for (unsigned int i = 0; i < actions.size(); ++i)
332      ud->addPickCallback(new PickCallback(actions[i], getModelRoot()));
333    // Look for the VNC sessions that want raw mouse input
334    actions = getConfig()->getChildren("vncaction");
335    for (unsigned int i = 0; i < actions.size(); ++i)
336      ud->addPickCallback(new VncCallback(actions[i], getModelRoot(),
337        &parent));
338
339    return commonGroup;
340 }
341
342 ///////////////////////////////////////////////////////////////////////////
343
344 // insert count copies of binding list A, into the output list.
345 // effectively makes the output list fire binding A multiple times
346 // in sequence
347 static void repeatBindings(const SGBindingList& a, SGBindingList& out, int count)
348 {
349     out.clear();
350     for (int i=0; i<count; ++i) {
351         out.insert(out.end(), a.begin(), a.end());
352     }
353 }
354
355 static void readOptionalBindingList(const SGPropertyNode* aNode, SGPropertyNode* modelRoot,
356     const std::string& aName, SGBindingList& aBindings)
357 {
358     const SGPropertyNode* n = aNode->getChild(aName);
359     if (n)
360         aBindings = readBindingList(n->getChildren("binding"), modelRoot);
361     
362 }
363
364 static bool static_knobMouseWheelAlternateDirection = false;
365
366 class SGKnobAnimation::KnobPickCallback : public SGPickCallback {
367 public:
368     KnobPickCallback(const SGPropertyNode* configNode,
369                  SGPropertyNode* modelRoot) :
370         _direction(DIRECTION_NONE),
371         _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1)),
372         _stickyShifted(false)
373     {
374         readOptionalBindingList(configNode, modelRoot, "action", _action);
375         readOptionalBindingList(configNode, modelRoot, "cw", _bindingsCW);
376         readOptionalBindingList(configNode, modelRoot, "ccw", _bindingsCCW);
377         
378         readOptionalBindingList(configNode, modelRoot, "release", _releaseAction);
379         
380         if (configNode->hasChild("shift-action") || configNode->hasChild("shift-cw") ||
381             configNode->hasChild("shift-ccw"))
382         {
383         // explicit shifted behaviour - just do exactly what was provided
384             readOptionalBindingList(configNode, modelRoot, "shift-action", _shiftedAction);
385             readOptionalBindingList(configNode, modelRoot, "shift-cw", _shiftedCW);
386             readOptionalBindingList(configNode, modelRoot, "shift-ccw", _shiftedCCW);
387         } else {
388             // default shifted behaviour - repeat normal bindings N times.
389             int shiftRepeat = configNode->getIntValue("shift-repeat", 10);
390             repeatBindings(_action, _shiftedAction, shiftRepeat);
391             repeatBindings(_bindingsCW, _shiftedCW, shiftRepeat);
392             repeatBindings(_bindingsCCW, _shiftedCCW, shiftRepeat);
393         } // of default shifted behaviour
394     }
395     
396     virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
397     {
398         _stickyShifted = ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_SHIFT;
399         
400         // the 'be nice to Mac / laptop' users option; alt-clicking spins the
401         // opposite direction. Should make this configurable
402         if ((button == 0) && (ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_ALT)) {
403             button = 1;
404         }
405         
406         int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4;
407         int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3;
408             
409         _direction = DIRECTION_NONE;
410         if ((button == 0) || (button == increaseMouseWheel)) {
411             _direction = DIRECTION_CLOCKWISE;
412         } else if ((button == 1) || (button == decreaseMouseWheel)) {
413             _direction = DIRECTION_ANTICLOCKWISE;
414         } else {
415             return false;
416         }
417         
418         _repeatTime = -_repeatInterval;    // anti-bobble: delay start of repeat
419         fire(_stickyShifted);
420         return true;
421     }
422     
423     virtual void buttonReleased(void)
424     {
425         fireBindingList(_releaseAction);
426     }
427     
428     virtual void update(double dt)
429     {
430         _repeatTime += dt;
431         while (_repeatInterval < _repeatTime) {
432             _repeatTime -= _repeatInterval;
433             fire(_stickyShifted);
434         } // of repeat iteration
435     }
436 private:
437     void fire(bool isShifted)
438     {
439         const SGBindingList& act(isShifted ? _shiftedAction : _action);
440         const SGBindingList& cw(isShifted ? _shiftedCW : _bindingsCW);
441         const SGBindingList& ccw(isShifted ? _shiftedCCW : _bindingsCCW);
442         
443         switch (_direction) {
444             case DIRECTION_CLOCKWISE:
445                 fireBindingListWithOffset(act,  1, 1);
446                 fireBindingList(cw);
447                 break;
448             case DIRECTION_ANTICLOCKWISE:
449                 fireBindingListWithOffset(act, -1, 1);
450                 fireBindingList(ccw);
451                 break;
452             default: break;
453         }
454     }
455     
456     SGBindingList _action, _shiftedAction;
457     SGBindingList _releaseAction;
458     SGBindingList _bindingsCW, _shiftedCW,
459         _bindingsCCW, _shiftedCCW;
460     
461     enum Direction
462     {
463         DIRECTION_NONE,
464         DIRECTION_CLOCKWISE,
465         DIRECTION_ANTICLOCKWISE
466     };
467     
468     Direction _direction;
469     double _repeatInterval;
470     double _repeatTime;
471     
472     // FIXME - would be better to pass the current modifier state
473     // into update(), but for now let's make it sticky
474     bool _stickyShifted;
475 };
476
477 class SGKnobAnimation::UpdateCallback : public osg::NodeCallback {
478 public:
479     UpdateCallback(SGExpressiond const* animationValue) :
480         _animationValue(animationValue)
481     {
482         setName("SGKnobAnimation::UpdateCallback");
483     }
484     virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
485     {
486         SGRotateTransform* transform = static_cast<SGRotateTransform*>(node);
487         transform->setAngleDeg(_animationValue->getValue());
488         traverse(node, nv);
489     }
490     
491 private:
492     SGSharedPtr<SGExpressiond const> _animationValue;
493 };
494
495
496 SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
497                                  SGPropertyNode* modelRoot) :
498     SGPickAnimation(configNode, modelRoot)
499 {
500     SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-deg",
501                                                   -SGLimitsd::max(), SGLimitsd::max());
502     _animationValue = value->simplify();
503     
504     
505     readRotationCenterAndAxis(configNode, _center, _axis);
506 }
507
508
509 osg::Group*
510 SGKnobAnimation::createAnimationGroup(osg::Group& parent)
511 {
512     SGRotateTransform* transform = new SGRotateTransform();
513     innerSetupPickGroup(transform, parent);
514     
515     UpdateCallback* uc = new UpdateCallback(_animationValue);
516     transform->setUpdateCallback(uc);
517     transform->setCenter(_center);
518     transform->setAxis(_axis);
519         
520     SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
521     ud->setPickCallback(new KnobPickCallback(getConfig(), getModelRoot()));
522     
523     return transform;
524 }
525
526 void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle)
527 {
528     static_knobMouseWheelAlternateDirection = aToggle;
529 }
530