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>
29 #include <osgGA/GUIEventAdapter>
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>
38 using namespace simgear;
40 using OpenThreads::Mutex;
41 using OpenThreads::ScopedLock;
43 static void readOptionalBindingList(const SGPropertyNode* aNode, SGPropertyNode* modelRoot,
44 const std::string& aName, SGBindingList& aBindings)
46 const SGPropertyNode* n = aNode->getChild(aName);
48 aBindings = readBindingList(n->getChildren("binding"), modelRoot);
52 class SGPickAnimation::PickCallback : public SGPickCallback {
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))
60 std::vector<SGPropertyNode_ptr> bindings;
62 bindings = configNode->getChildren("button");
63 for (unsigned int i = 0; i < bindings.size(); ++i) {
64 _buttons.insert( bindings[i]->getIntValue() );
67 _bindingsDown = readBindingList(configNode->getChildren("binding"), modelRoot);
68 readOptionalBindingList(configNode, modelRoot, "mod-up", _bindingsUp);
69 readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
72 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
74 if (_buttons.find(button) == _buttons.end()) {
78 fireBindingList(_bindingsDown);
79 _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
82 virtual void buttonReleased(void)
84 fireBindingList(_bindingsUp);
86 virtual void update(double dt)
92 while (_repeatInterval < _repeatTime) {
93 _repeatTime -= _repeatInterval;
94 fireBindingList(_bindingsDown);
98 virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
100 if (_hover.empty()) {
104 SGPropertyNode_ptr params(new SGPropertyNode);
105 params->setDoubleValue("x", windowPos.x());
106 params->setDoubleValue("y", windowPos.y());
107 fireBindingList(_hover, params.ptr());
111 SGBindingList _bindingsDown;
112 SGBindingList _bindingsUp;
113 SGBindingList _hover;
114 std::set<int> _buttons;
116 double _repeatInterval;
120 class VncVisitor : public osg::NodeVisitor {
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)
126 SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
127 << x << "," << y << " mask " << mask);
130 virtual void apply(osg::Node &node)
132 // Some nodes have state sets attached
133 touchStateSet(node.getStateSet());
137 // See whether we are a geode worth exploring
138 osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
140 // Go find all its drawables
141 int i = g->getNumDrawables();
143 osg::Drawable *d = g->getDrawable(i);
144 if (d) touchDrawable(*d);
146 // Out of optimism, do the same for EffectGeode
147 simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
149 for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
150 di != eg->drawablesEnd(); di++) {
153 // Now see whether the EffectGeode has an Effect
154 simgear::Effect *e = eg->getEffect();
156 touchStateSet(e->getDefaultStateSet());
160 inline void touchDrawable(osg::Drawable &d)
162 osg::StateSet *ss = d.getStateSet();
166 void touchStateSet(osg::StateSet *ss)
169 osg::StateAttribute *sa = ss->getTextureAttribute(0,
170 osg::StateAttribute::TEXTURE);
172 osg::Texture *t = sa->asTexture();
174 osg::Image *img = t->getImage(0);
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);
185 inline bool wasSuccessful()
197 class SGPickAnimation::VncCallback : public SGPickCallback {
199 VncCallback(const SGPropertyNode* configNode,
200 SGPropertyNode* modelRoot,
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"));
214 _toRight -= _topLeft;
216 _squaredRight = dot(_toRight, _toRight);
217 _squaredDown = dot(_toDown, _toDown);
220 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
222 SGVec3d loc(info.local);
223 SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
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);
231 return vv.wasSuccessful();
234 virtual void buttonReleased(void)
236 SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
237 VncVisitor vv(_x, _y, 0);
240 virtual void update(double dt)
245 osg::ref_ptr<osg::Group> _node;
246 SGVec3d _topLeft, _toRight, _toDown;
247 double _squaredRight, _squaredDown;
250 SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
251 SGPropertyNode* modelRoot) :
252 SGAnimation(configNode, modelRoot)
258 OpenThreads::Mutex colorModeUniformMutex;
259 osg::ref_ptr<osg::Uniform> colorModeUniform;
264 SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent)
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);
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);
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;
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);
320 cmUniform = colorModeUniform.get();
322 stateSet->addUniform(cmUniform,
323 osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
325 // Only add normal geometry if configured
326 if (getConfig()->getBoolValue("visible", true))
327 parent.addChild(normalGroup.get());
328 parent.addChild(highlightGroup);
332 SGPickAnimation::createAnimationGroup(osg::Group& parent)
334 osg::Group* commonGroup = new osg::Group;
335 innerSetupPickGroup(commonGroup, parent);
336 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
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(),
352 ///////////////////////////////////////////////////////////////////////////
354 // insert count copies of binding list A, into the output list.
355 // effectively makes the output list fire binding A multiple times
357 static void repeatBindings(const SGBindingList& a, SGBindingList& out, int count)
360 for (int i=0; i<count; ++i) {
361 out.insert(out.end(), a.begin(), a.end());
365 static bool static_knobMouseWheelAlternateDirection = false;
367 class SGKnobAnimation::KnobPickCallback : public SGPickCallback {
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)
376 readOptionalBindingList(configNode, modelRoot, "action", _action);
377 readOptionalBindingList(configNode, modelRoot, "cw", _bindingsCW);
378 readOptionalBindingList(configNode, modelRoot, "ccw", _bindingsCCW);
380 readOptionalBindingList(configNode, modelRoot, "release", _releaseAction);
381 readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
383 if (configNode->hasChild("shift-action") || configNode->hasChild("shift-cw") ||
384 configNode->hasChild("shift-ccw"))
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);
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
399 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
401 _stickyShifted = ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_SHIFT;
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)) {
409 int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4;
410 int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3;
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;
421 _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
422 fire(_stickyShifted);
426 virtual void buttonReleased(void)
428 fireBindingList(_releaseAction);
431 virtual void update(double dt)
434 while (_repeatInterval < _repeatTime) {
435 _repeatTime -= _repeatInterval;
436 fire(_stickyShifted);
437 } // of repeat iteration
440 virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
442 if (_hover.empty()) {
446 SGPropertyNode_ptr params(new SGPropertyNode);
447 params->setDoubleValue("x", windowPos.x());
448 params->setDoubleValue("y", windowPos.y());
449 fireBindingList(_hover, params.ptr());
453 void fire(bool isShifted)
455 const SGBindingList& act(isShifted ? _shiftedAction : _action);
456 const SGBindingList& cw(isShifted ? _shiftedCW : _bindingsCW);
457 const SGBindingList& ccw(isShifted ? _shiftedCCW : _bindingsCCW);
459 switch (_direction) {
460 case DIRECTION_CLOCKWISE:
461 fireBindingListWithOffset(act, 1, 1);
464 case DIRECTION_ANTICLOCKWISE:
465 fireBindingListWithOffset(act, -1, 1);
466 fireBindingList(ccw);
472 SGBindingList _action, _shiftedAction;
473 SGBindingList _releaseAction;
474 SGBindingList _bindingsCW, _shiftedCW,
475 _bindingsCCW, _shiftedCCW;
476 SGBindingList _hover;
482 DIRECTION_ANTICLOCKWISE
485 Direction _direction;
486 double _repeatInterval;
489 // FIXME - would be better to pass the current modifier state
490 // into update(), but for now let's make it sticky
494 class SGKnobAnimation::UpdateCallback : public osg::NodeCallback {
496 UpdateCallback(SGExpressiond const* animationValue) :
497 _animationValue(animationValue)
499 setName("SGKnobAnimation::UpdateCallback");
501 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
503 SGRotateTransform* transform = static_cast<SGRotateTransform*>(node);
504 transform->setAngleDeg(_animationValue->getValue());
509 SGSharedPtr<SGExpressiond const> _animationValue;
513 SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
514 SGPropertyNode* modelRoot) :
515 SGPickAnimation(configNode, modelRoot)
517 SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-deg",
518 -SGLimitsd::max(), SGLimitsd::max());
519 _animationValue = value->simplify();
522 readRotationCenterAndAxis(configNode, _center, _axis);
527 SGKnobAnimation::createAnimationGroup(osg::Group& parent)
529 SGRotateTransform* transform = new SGRotateTransform();
530 innerSetupPickGroup(transform, parent);
532 UpdateCallback* uc = new UpdateCallback(_animationValue);
533 transform->setUpdateCallback(uc);
534 transform->setCenter(_center);
535 transform->setAxis(_axis);
537 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
538 ud->setPickCallback(new KnobPickCallback(getConfig(), getModelRoot()));
543 void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle)
545 static_knobMouseWheelAlternateDirection = aToggle;