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