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/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>
37 using namespace simgear;
39 using OpenThreads::Mutex;
40 using OpenThreads::ScopedLock;
42 class SGPickAnimation::PickCallback : public SGPickCallback {
44 PickCallback(const SGPropertyNode* configNode,
45 SGPropertyNode* modelRoot) :
46 _repeatable(configNode->getBoolValue("repeatable", false)),
47 _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
49 SG_LOG(SG_INPUT, SG_DEBUG, "Reading all bindings");
50 std::vector<SGPropertyNode_ptr> bindings;
52 bindings = configNode->getChildren("button");
53 for (unsigned int i = 0; i < bindings.size(); ++i) {
54 _buttons.push_back( bindings[i]->getIntValue() );
56 bindings = configNode->getChildren("binding");
57 for (unsigned int i = 0; i < bindings.size(); ++i) {
58 _bindingsDown.push_back(new SGBinding(bindings[i], modelRoot));
61 const SGPropertyNode* upNode = configNode->getChild("mod-up");
64 bindings = upNode->getChildren("binding");
65 for (unsigned int i = 0; i < bindings.size(); ++i) {
66 _bindingsUp.push_back(new SGBinding(bindings[i], modelRoot));
69 virtual bool buttonPressed(int button, const Info&)
72 for( std::vector<int>::iterator it = _buttons.begin(); it != _buttons.end(); ++it ) {
81 fireBindingList(_bindingsDown);
82 _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
85 virtual void buttonReleased(void)
87 fireBindingList(_bindingsUp);
89 virtual void update(double dt)
95 while (_repeatInterval < _repeatTime) {
96 _repeatTime -= _repeatInterval;
97 fireBindingList(_bindingsDown);
101 SGBindingList _bindingsDown;
102 SGBindingList _bindingsUp;
103 std::vector<int> _buttons;
105 double _repeatInterval;
109 class VncVisitor : public osg::NodeVisitor {
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)
115 SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
116 << x << "," << y << " mask " << mask);
119 virtual void apply(osg::Node &node)
121 // Some nodes have state sets attached
122 touchStateSet(node.getStateSet());
126 // See whether we are a geode worth exploring
127 osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
129 // Go find all its drawables
130 int i = g->getNumDrawables();
132 osg::Drawable *d = g->getDrawable(i);
133 if (d) touchDrawable(*d);
135 // Out of optimism, do the same for EffectGeode
136 simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
138 for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
139 di != eg->drawablesEnd(); di++) {
142 // Now see whether the EffectGeode has an Effect
143 simgear::Effect *e = eg->getEffect();
145 touchStateSet(e->getDefaultStateSet());
149 inline void touchDrawable(osg::Drawable &d)
151 osg::StateSet *ss = d.getStateSet();
155 void touchStateSet(osg::StateSet *ss)
158 osg::StateAttribute *sa = ss->getTextureAttribute(0,
159 osg::StateAttribute::TEXTURE);
161 osg::Texture *t = sa->asTexture();
163 osg::Image *img = t->getImage(0);
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);
174 inline bool wasSuccessful()
186 class SGPickAnimation::VncCallback : public SGPickCallback {
188 VncCallback(const SGPropertyNode* configNode,
189 SGPropertyNode* modelRoot,
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"));
203 _toRight -= _topLeft;
205 _squaredRight = dot(_toRight, _toRight);
206 _squaredDown = dot(_toDown, _toDown);
209 virtual bool buttonPressed(int button, const Info& info)
211 SGVec3d loc(info.local);
212 SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
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);
220 return vv.wasSuccessful();
223 virtual void buttonReleased(void)
225 SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
226 VncVisitor vv(_x, _y, 0);
229 virtual void update(double dt)
234 osg::ref_ptr<osg::Group> _node;
235 SGVec3d _topLeft, _toRight, _toDown;
236 double _squaredRight, _squaredDown;
239 SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
240 SGPropertyNode* modelRoot) :
241 SGAnimation(configNode, modelRoot)
247 OpenThreads::Mutex colorModeUniformMutex;
248 osg::ref_ptr<osg::Uniform> colorModeUniform;
253 SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent)
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);
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);
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;
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);
309 cmUniform = colorModeUniform.get();
311 stateSet->addUniform(cmUniform,
312 osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
314 // Only add normal geometry if configured
315 if (getConfig()->getBoolValue("visible", true))
316 parent.addChild(normalGroup.get());
317 parent.addChild(highlightGroup);
321 SGPickAnimation::createAnimationGroup(osg::Group& parent)
323 osg::Group* commonGroup = new osg::Group;
324 innerSetupPickGroup(commonGroup, parent);
325 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
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(),
341 ///////////////////////////////////////////////////////////////////////////
343 class SGKnobAnimation::KnobPickCallback : public SGPickCallback {
345 KnobPickCallback(const SGPropertyNode* configNode,
346 SGPropertyNode* modelRoot) :
347 _direction(DIRECTION_NONE),
348 _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
350 const SGPropertyNode* act = configNode->getChild("action");
352 _action = readBindingList(act->getChildren("binding"), modelRoot);
354 const SGPropertyNode* cw = configNode->getChild("cw");
356 _bindingsCW = readBindingList(cw->getChildren("binding"), modelRoot);
358 const SGPropertyNode* ccw = configNode->getChild("ccw");
360 _bindingsCCW = readBindingList(ccw->getChildren("binding"), modelRoot);
363 virtual bool buttonPressed(int button, const Info&)
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;
374 _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
379 virtual void buttonReleased(void)
383 virtual void update(double dt)
386 while (_repeatInterval < _repeatTime) {
387 _repeatTime -= _repeatInterval;
389 } // of repeat iteration
394 switch (_direction) {
395 case DIRECTION_CLOCKWISE:
396 fireBindingListWithOffset(_action, 1, 1);
397 fireBindingList(_bindingsCW);
399 case DIRECTION_ANTICLOCKWISE:
400 fireBindingListWithOffset(_action, -1, 1);
401 fireBindingList(_bindingsCCW);
407 SGBindingList _action;
408 SGBindingList _bindingsCW,
415 DIRECTION_ANTICLOCKWISE
418 Direction _direction;
419 double _repeatInterval;
423 class SGKnobAnimation::UpdateCallback : public osg::NodeCallback {
425 UpdateCallback(SGExpressiond const* animationValue) :
426 _animationValue(animationValue)
428 setName("SGKnobAnimation::UpdateCallback");
430 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
432 SGRotateTransform* transform = static_cast<SGRotateTransform*>(node);
433 transform->setAngleDeg(_animationValue->getValue());
438 SGSharedPtr<SGExpressiond const> _animationValue;
442 SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
443 SGPropertyNode* modelRoot) :
444 SGPickAnimation(configNode, modelRoot)
446 SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-deg",
447 -SGLimitsd::max(), SGLimitsd::max());
448 _animationValue = value->simplify();
451 readRotationCenterAndAxis(configNode, _center, _axis);
456 SGKnobAnimation::createAnimationGroup(osg::Group& parent)
458 SGRotateTransform* transform = new SGRotateTransform();
459 innerSetupPickGroup(transform, parent);
461 UpdateCallback* uc = new UpdateCallback(_animationValue);
462 transform->setUpdateCallback(uc);
463 transform->setCenter(_center);
464 transform->setAxis(_axis);
466 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
467 ud->setPickCallback(new KnobPickCallback(getConfig(), getModelRoot()));