3 * Copyright (C) 2013 James Turner
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.
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.
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,
23 #include <simgear/scene/model/SGPickAnimation.hxx>
26 #include <osg/PolygonOffset>
27 #include <osg/PolygonMode>
28 #include <osg/Material>
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>
36 using namespace simgear;
38 using OpenThreads::Mutex;
39 using OpenThreads::ScopedLock;
41 class SGPickAnimation::PickCallback : public SGPickCallback {
43 PickCallback(const SGPropertyNode* configNode,
44 SGPropertyNode* modelRoot) :
45 _repeatable(configNode->getBoolValue("repeatable", false)),
46 _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
48 SG_LOG(SG_INPUT, SG_DEBUG, "Reading all bindings");
49 std::vector<SGPropertyNode_ptr> bindings;
51 bindings = configNode->getChildren("button");
52 for (unsigned int i = 0; i < bindings.size(); ++i) {
53 _buttons.push_back( bindings[i]->getIntValue() );
55 bindings = configNode->getChildren("binding");
56 for (unsigned int i = 0; i < bindings.size(); ++i) {
57 _bindingsDown.push_back(new SGBinding(bindings[i], modelRoot));
60 const SGPropertyNode* upNode = configNode->getChild("mod-up");
63 bindings = upNode->getChildren("binding");
64 for (unsigned int i = 0; i < bindings.size(); ++i) {
65 _bindingsUp.push_back(new SGBinding(bindings[i], modelRoot));
68 virtual bool buttonPressed(int button, const Info&)
71 for( std::vector<int>::iterator it = _buttons.begin(); it != _buttons.end(); ++it ) {
79 SGBindingList::const_iterator i;
80 for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
82 _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
85 virtual void buttonReleased(void)
87 SGBindingList::const_iterator i;
88 for (i = _bindingsUp.begin(); i != _bindingsUp.end(); ++i)
91 virtual void update(double dt)
97 while (_repeatInterval < _repeatTime) {
98 _repeatTime -= _repeatInterval;
99 SGBindingList::const_iterator i;
100 for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
105 SGBindingList _bindingsDown;
106 SGBindingList _bindingsUp;
107 std::vector<int> _buttons;
109 double _repeatInterval;
113 class VncVisitor : public osg::NodeVisitor {
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)
119 SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
120 << x << "," << y << " mask " << mask);
123 virtual void apply(osg::Node &node)
125 // Some nodes have state sets attached
126 touchStateSet(node.getStateSet());
130 // See whether we are a geode worth exploring
131 osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
133 // Go find all its drawables
134 int i = g->getNumDrawables();
136 osg::Drawable *d = g->getDrawable(i);
137 if (d) touchDrawable(*d);
139 // Out of optimism, do the same for EffectGeode
140 simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
142 for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
143 di != eg->drawablesEnd(); di++) {
146 // Now see whether the EffectGeode has an Effect
147 simgear::Effect *e = eg->getEffect();
149 touchStateSet(e->getDefaultStateSet());
153 inline void touchDrawable(osg::Drawable &d)
155 osg::StateSet *ss = d.getStateSet();
159 void touchStateSet(osg::StateSet *ss)
162 osg::StateAttribute *sa = ss->getTextureAttribute(0,
163 osg::StateAttribute::TEXTURE);
165 osg::Texture *t = sa->asTexture();
167 osg::Image *img = t->getImage(0);
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);
178 inline bool wasSuccessful()
190 class SGPickAnimation::VncCallback : public SGPickCallback {
192 VncCallback(const SGPropertyNode* configNode,
193 SGPropertyNode* modelRoot,
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"));
207 _toRight -= _topLeft;
209 _squaredRight = dot(_toRight, _toRight);
210 _squaredDown = dot(_toDown, _toDown);
213 virtual bool buttonPressed(int button, const Info& info)
215 SGVec3d loc(info.local);
216 SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
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);
224 return vv.wasSuccessful();
227 virtual void buttonReleased(void)
229 SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
230 VncVisitor vv(_x, _y, 0);
233 virtual void update(double dt)
238 osg::ref_ptr<osg::Group> _node;
239 SGVec3d _topLeft, _toRight, _toDown;
240 double _squaredRight, _squaredDown;
243 SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
244 SGPropertyNode* modelRoot) :
245 SGAnimation(configNode, modelRoot)
251 OpenThreads::Mutex colorModeUniformMutex;
252 osg::ref_ptr<osg::Uniform> colorModeUniform;
256 SGPickAnimation::createAnimationGroup(osg::Group& parent)
258 osg::Group* commonGroup = new osg::Group;
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);
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);
271 ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
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(),
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;
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);
327 cmUniform = colorModeUniform.get();
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);