]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/SGPickAnimation.cxx
Pick animation moved to its own file.
[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/material/EffectGeode.hxx>
31 #include <simgear/scene/util/SGPickCallback.hxx>
32 #include <simgear/scene/util/SGSceneUserData.hxx>
33 #include <simgear/structure/SGBinding.hxx>
34 #include <simgear/scene/util/StateAttributeFactory.hxx>
35
36 using namespace simgear;
37
38 using OpenThreads::Mutex;
39 using OpenThreads::ScopedLock;
40
41  class SGPickAnimation::PickCallback : public SGPickCallback {
42  public:
43    PickCallback(const SGPropertyNode* configNode,
44                 SGPropertyNode* modelRoot) :
45      _repeatable(configNode->getBoolValue("repeatable", false)),
46      _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
47    {
48      SG_LOG(SG_INPUT, SG_DEBUG, "Reading all bindings");
49      std::vector<SGPropertyNode_ptr> bindings;
50
51      bindings = configNode->getChildren("button");
52      for (unsigned int i = 0; i < bindings.size(); ++i) {
53        _buttons.push_back( bindings[i]->getIntValue() );
54      }
55      bindings = configNode->getChildren("binding");
56      for (unsigned int i = 0; i < bindings.size(); ++i) {
57        _bindingsDown.push_back(new SGBinding(bindings[i], modelRoot));
58      }
59
60      const SGPropertyNode* upNode = configNode->getChild("mod-up");
61      if (!upNode)
62        return;
63      bindings = upNode->getChildren("binding");
64      for (unsigned int i = 0; i < bindings.size(); ++i) {
65        _bindingsUp.push_back(new SGBinding(bindings[i], modelRoot));
66      }
67    }
68    virtual bool buttonPressed(int button, const Info&)
69    {
70      bool found = false;
71      for( std::vector<int>::iterator it = _buttons.begin(); it != _buttons.end(); ++it ) {
72        if( *it == button ) {
73          found = true;
74          break;
75        }
76      }
77      if (!found )
78        return false;
79      SGBindingList::const_iterator i;
80      for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
81        (*i)->fire();
82      _repeatTime = -_repeatInterval;    // anti-bobble: delay start of repeat
83      return true;
84    }
85    virtual void buttonReleased(void)
86    {
87      SGBindingList::const_iterator i;
88      for (i = _bindingsUp.begin(); i != _bindingsUp.end(); ++i)
89        (*i)->fire();
90    }
91    virtual void update(double dt)
92    {
93      if (!_repeatable)
94        return;
95
96      _repeatTime += dt;
97      while (_repeatInterval < _repeatTime) {
98        _repeatTime -= _repeatInterval;
99        SGBindingList::const_iterator i;
100        for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
101          (*i)->fire();
102      }
103    }
104  private:
105    SGBindingList _bindingsDown;
106    SGBindingList _bindingsUp;
107    std::vector<int> _buttons;
108    bool _repeatable;
109    double _repeatInterval;
110    double _repeatTime;
111  };
112
113  class VncVisitor : public osg::NodeVisitor {
114   public:
115    VncVisitor(double x, double y, int mask) :
116      osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
117      _texX(x), _texY(y), _mask(mask), _done(false)
118    {
119      SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
120        << x << "," << y << " mask " << mask);
121    }
122
123    virtual void apply(osg::Node &node)
124    {
125      // Some nodes have state sets attached
126      touchStateSet(node.getStateSet());
127      if (!_done)
128        traverse(node);
129      if (_done) return;
130      // See whether we are a geode worth exploring
131      osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
132      if (!g) return;
133      // Go find all its drawables
134      int i = g->getNumDrawables();
135      while (--i >= 0) {
136        osg::Drawable *d = g->getDrawable(i);
137        if (d) touchDrawable(*d);
138      }
139      // Out of optimism, do the same for EffectGeode
140      simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
141      if (!eg) return;
142      for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
143           di != eg->drawablesEnd(); di++) {
144        touchDrawable(**di);
145      }
146      // Now see whether the EffectGeode has an Effect
147      simgear::Effect *e = eg->getEffect();
148      if (e) {
149        touchStateSet(e->getDefaultStateSet());
150      }
151    }
152
153    inline void touchDrawable(osg::Drawable &d)
154    {
155      osg::StateSet *ss = d.getStateSet();
156      touchStateSet(ss);
157    }
158
159    void touchStateSet(osg::StateSet *ss)
160    {
161      if (!ss) return;
162      osg::StateAttribute *sa = ss->getTextureAttribute(0,
163        osg::StateAttribute::TEXTURE);
164      if (!sa) return;
165      osg::Texture *t = sa->asTexture();
166      if (!t) return;
167      osg::Image *img = t->getImage(0);
168      if (!img) return;
169      if (!_done) {
170        int pixX = _texX * img->s();
171        int pixY = _texY * img->t();
172        _done = img->sendPointerEvent(pixX, pixY, _mask);
173        SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor image said " << _done
174          << " to coord " << pixX << "," << pixY);
175      }
176    }
177
178    inline bool wasSuccessful()
179    {
180      return _done;
181    }
182
183   private:
184    double _texX, _texY;
185    int _mask;
186    bool _done;
187  };
188
189
190  class SGPickAnimation::VncCallback : public SGPickCallback {
191  public:
192    VncCallback(const SGPropertyNode* configNode,
193                 SGPropertyNode* modelRoot,
194                 osg::Group *node)
195        : _node(node)
196    {
197      SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback");
198      const char *cornernames[3] = {"top-left", "top-right", "bottom-left"};
199      SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown};
200      for (int c =0; c < 3; c++) {
201        const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]);
202        *cornercoords[c] = SGVec3d(
203          cornerNode->getDoubleValue("x"),
204          cornerNode->getDoubleValue("y"),
205          cornerNode->getDoubleValue("z"));
206      }
207      _toRight -= _topLeft;
208      _toDown -= _topLeft;
209      _squaredRight = dot(_toRight, _toRight);
210      _squaredDown = dot(_toDown, _toDown);
211    }
212
213    virtual bool buttonPressed(int button, const Info& info)
214    {
215      SGVec3d loc(info.local);
216      SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
217      loc -= _topLeft;
218      _x = dot(loc, _toRight) / _squaredRight;
219      _y = dot(loc, _toDown) / _squaredDown;
220      if (_x<0) _x = 0; else if (_x > 1) _x = 1;
221      if (_y<0) _y = 0; else if (_y > 1) _y = 1;
222      VncVisitor vv(_x, _y, 1 << button);
223      _node->accept(vv);
224      return vv.wasSuccessful();
225
226    }
227    virtual void buttonReleased(void)
228    {
229      SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
230      VncVisitor vv(_x, _y, 0);
231      _node->accept(vv);
232    }
233    virtual void update(double dt)
234    {
235    }
236  private:
237    double _x, _y;
238    osg::ref_ptr<osg::Group> _node;
239    SGVec3d _topLeft, _toRight, _toDown;
240    double _squaredRight, _squaredDown;
241  };
242
243  SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
244                                   SGPropertyNode* modelRoot) :
245    SGAnimation(configNode, modelRoot)
246  {
247  }
248
249  namespace
250  {
251  OpenThreads::Mutex colorModeUniformMutex;
252  osg::ref_ptr<osg::Uniform> colorModeUniform;
253  }
254
255  osg::Group*
256  SGPickAnimation::createAnimationGroup(osg::Group& parent)
257  {
258    osg::Group* commonGroup = new osg::Group;
259
260    // Contains the normal geometry that is interactive
261    osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
262    normalGroup->setName("pick normal group");
263    normalGroup->addChild(commonGroup);
264
265    // Used to render the geometry with just yellow edges
266    osg::Group* highlightGroup = new osg::Group;
267    highlightGroup->setName("pick highlight group");
268    highlightGroup->setNodeMask(simgear::PICK_BIT);
269    highlightGroup->addChild(commonGroup);
270    SGSceneUserData* ud;
271    ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
272
273    // add actions that become macro and command invocations
274    std::vector<SGPropertyNode_ptr> actions;
275    actions = getConfig()->getChildren("action");
276    for (unsigned int i = 0; i < actions.size(); ++i)
277      ud->addPickCallback(new PickCallback(actions[i], getModelRoot()));
278    // Look for the VNC sessions that want raw mouse input
279    actions = getConfig()->getChildren("vncaction");
280    for (unsigned int i = 0; i < actions.size(); ++i)
281      ud->addPickCallback(new VncCallback(actions[i], getModelRoot(),
282        &parent));
283
284    // prepare a state set that paints the edges of this object yellow
285    // The material and texture attributes are set with
286    // OVERRIDE|PROTECTED in case there is a material animation on a
287    // higher node in the scene graph, which would have its material
288    // attribute set with OVERRIDE.
289    osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
290    osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
291    stateSet->setTextureAttributeAndModes(0, white,
292                                          (osg::StateAttribute::ON
293                                           | osg::StateAttribute::OVERRIDE
294                                           | osg::StateAttribute::PROTECTED));
295    osg::PolygonOffset* polygonOffset = new osg::PolygonOffset;
296    polygonOffset->setFactor(-1);
297    polygonOffset->setUnits(-1);
298    stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
299    stateSet->setMode(GL_POLYGON_OFFSET_LINE,
300                      osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
301    osg::PolygonMode* polygonMode = new osg::PolygonMode;
302    polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK,
303                         osg::PolygonMode::LINE);
304    stateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
305    osg::Material* material = new osg::Material;
306    material->setColorMode(osg::Material::OFF);
307    material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
308    // XXX Alpha < 1.0 in the diffuse material value is a signal to the
309    // default shader to take the alpha value from the material value
310    // and not the glColor. In many cases the pick animation geometry is
311    // transparent, so the outline would not be visible without this hack.
312    material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, .95));
313    material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
314    material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0));
315    stateSet->setAttribute(
316        material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
317    // The default shader has a colorMode uniform that mimics the
318    // behavior of Material color mode.
319    osg::Uniform* cmUniform = 0;
320    {
321        ScopedLock<Mutex> lock(colorModeUniformMutex);
322        if (!colorModeUniform.valid()) {
323            colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
324            colorModeUniform->set(0); // MODE_OFF
325            colorModeUniform->setDataVariance(osg::Object::STATIC);
326        }
327        cmUniform = colorModeUniform.get();
328    }
329    stateSet->addUniform(cmUniform,
330                         osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
331    // Only add normal geometry if configured
332    if (getConfig()->getBoolValue("visible", true))
333      parent.addChild(normalGroup.get());
334    parent.addChild(highlightGroup);
335
336   
337    return commonGroup;
338 }
339