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