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