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/sg_inlines.h>
32 #include <simgear/scene/util/SGPickCallback.hxx>
33 #include <simgear/scene/material/EffectGeode.hxx>
34 #include <simgear/scene/util/SGSceneUserData.hxx>
35 #include <simgear/structure/SGBinding.hxx>
36 #include <simgear/scene/util/StateAttributeFactory.hxx>
37 #include <simgear/scene/model/SGRotateTransform.hxx>
38 #include <simgear/scene/model/SGTranslateTransform.hxx>
40 using namespace simgear;
42 using OpenThreads::Mutex;
43 using OpenThreads::ScopedLock;
45 static void readOptionalBindingList(const SGPropertyNode* aNode, SGPropertyNode* modelRoot,
46 const std::string& aName, SGBindingList& aBindings)
48 const SGPropertyNode* n = aNode->getChild(aName);
50 aBindings = readBindingList(n->getChildren("binding"), modelRoot);
55 osg::Vec2d eventToWindowCoords(const osgGA::GUIEventAdapter* ea)
58 const GraphicsContext* gc = ea->getGraphicsContext();
59 const GraphicsContext::Traits* traits = gc->getTraits() ;
60 // Scale x, y to the dimensions of the window
61 double x = (((ea->getX() - ea->getXmin()) / (ea->getXmax() - ea->getXmin()))
62 * (double)traits->width);
63 double y = (((ea->getY() - ea->getYmin()) / (ea->getYmax() - ea->getYmin()))
64 * (double)traits->height);
65 if (ea->getMouseYOrientation() == osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS)
66 y = (double)traits->height - y;
68 return osg::Vec2d(x, y);
71 class SGPickAnimation::PickCallback : public SGPickCallback {
73 PickCallback(const SGPropertyNode* configNode,
74 SGPropertyNode* modelRoot) :
75 SGPickCallback(PriorityPanel),
76 _repeatable(configNode->getBoolValue("repeatable", false)),
77 _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
79 std::vector<SGPropertyNode_ptr> bindings;
81 bindings = configNode->getChildren("button");
82 for (unsigned int i = 0; i < bindings.size(); ++i) {
83 _buttons.insert( bindings[i]->getIntValue() );
86 _bindingsDown = readBindingList(configNode->getChildren("binding"), modelRoot);
87 readOptionalBindingList(configNode, modelRoot, "mod-up", _bindingsUp);
88 readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
91 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
93 if (_buttons.find(button) == _buttons.end()) {
97 fireBindingList(_bindingsDown);
98 _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
101 virtual void buttonReleased(int keyModState)
103 SG_UNUSED(keyModState);
104 fireBindingList(_bindingsUp);
107 virtual void update(double dt, int keyModState)
109 SG_UNUSED(keyModState);
114 while (_repeatInterval < _repeatTime) {
115 _repeatTime -= _repeatInterval;
116 fireBindingList(_bindingsDown);
120 virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
122 if (_hover.empty()) {
126 SGPropertyNode_ptr params(new SGPropertyNode);
127 params->setDoubleValue("x", windowPos.x());
128 params->setDoubleValue("y", windowPos.y());
129 fireBindingList(_hover, params.ptr());
133 SGBindingList _bindingsDown;
134 SGBindingList _bindingsUp;
135 SGBindingList _hover;
136 std::set<int> _buttons;
138 double _repeatInterval;
142 class VncVisitor : public osg::NodeVisitor {
144 VncVisitor(double x, double y, int mask) :
145 osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
146 _texX(x), _texY(y), _mask(mask), _done(false)
148 SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
149 << x << "," << y << " mask " << mask);
152 virtual void apply(osg::Node &node)
154 // Some nodes have state sets attached
155 touchStateSet(node.getStateSet());
159 // See whether we are a geode worth exploring
160 osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
162 // Go find all its drawables
163 int i = g->getNumDrawables();
165 osg::Drawable *d = g->getDrawable(i);
166 if (d) touchDrawable(*d);
168 // Out of optimism, do the same for EffectGeode
169 simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
171 for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
172 di != eg->drawablesEnd(); di++) {
175 // Now see whether the EffectGeode has an Effect
176 simgear::Effect *e = eg->getEffect();
178 touchStateSet(e->getDefaultStateSet());
182 inline void touchDrawable(osg::Drawable &d)
184 osg::StateSet *ss = d.getStateSet();
188 void touchStateSet(osg::StateSet *ss)
191 osg::StateAttribute *sa = ss->getTextureAttribute(0,
192 osg::StateAttribute::TEXTURE);
194 osg::Texture *t = sa->asTexture();
196 osg::Image *img = t->getImage(0);
199 int pixX = _texX * img->s();
200 int pixY = _texY * img->t();
201 _done = img->sendPointerEvent(pixX, pixY, _mask);
202 SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor image said " << _done
203 << " to coord " << pixX << "," << pixY);
207 inline bool wasSuccessful()
218 ///////////////////////////////////////////////////////////////////////////////
220 class SGPickAnimation::VncCallback : public SGPickCallback {
222 VncCallback(const SGPropertyNode* configNode,
223 SGPropertyNode* modelRoot,
227 SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback");
228 const char *cornernames[3] = {"top-left", "top-right", "bottom-left"};
229 SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown};
230 for (int c =0; c < 3; c++) {
231 const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]);
232 *cornercoords[c] = SGVec3d(
233 cornerNode->getDoubleValue("x"),
234 cornerNode->getDoubleValue("y"),
235 cornerNode->getDoubleValue("z"));
237 _toRight -= _topLeft;
239 _squaredRight = dot(_toRight, _toRight);
240 _squaredDown = dot(_toDown, _toDown);
243 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
245 SGVec3d loc(info.local);
246 SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
248 _x = dot(loc, _toRight) / _squaredRight;
249 _y = dot(loc, _toDown) / _squaredDown;
250 if (_x<0) _x = 0; else if (_x > 1) _x = 1;
251 if (_y<0) _y = 0; else if (_y > 1) _y = 1;
252 VncVisitor vv(_x, _y, 1 << button);
254 return vv.wasSuccessful();
257 virtual void buttonReleased(int keyModState)
259 SG_UNUSED(keyModState);
260 SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
261 VncVisitor vv(_x, _y, 0);
267 osg::ref_ptr<osg::Group> _node;
268 SGVec3d _topLeft, _toRight, _toDown;
269 double _squaredRight, _squaredDown;
272 ///////////////////////////////////////////////////////////////////////////////
274 SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
275 SGPropertyNode* modelRoot) :
276 SGAnimation(configNode, modelRoot)
282 OpenThreads::Mutex colorModeUniformMutex;
283 osg::ref_ptr<osg::Uniform> colorModeUniform;
288 SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent)
290 // Contains the normal geometry that is interactive
291 osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
292 normalGroup->setName("pick normal group");
293 normalGroup->addChild(commonGroup);
295 // Used to render the geometry with just yellow edges
296 osg::Group* highlightGroup = new osg::Group;
297 highlightGroup->setName("pick highlight group");
298 highlightGroup->setNodeMask(simgear::PICK_BIT);
299 highlightGroup->addChild(commonGroup);
301 // prepare a state set that paints the edges of this object yellow
302 // The material and texture attributes are set with
303 // OVERRIDE|PROTECTED in case there is a material animation on a
304 // higher node in the scene graph, which would have its material
305 // attribute set with OVERRIDE.
306 osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
307 osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
308 stateSet->setTextureAttributeAndModes(0, white,
309 (osg::StateAttribute::ON
310 | osg::StateAttribute::OVERRIDE
311 | osg::StateAttribute::PROTECTED));
312 osg::PolygonOffset* polygonOffset = new osg::PolygonOffset;
313 polygonOffset->setFactor(-1);
314 polygonOffset->setUnits(-1);
315 stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
316 stateSet->setMode(GL_POLYGON_OFFSET_LINE,
317 osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
318 osg::PolygonMode* polygonMode = new osg::PolygonMode;
319 polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK,
320 osg::PolygonMode::LINE);
321 stateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
322 osg::Material* material = new osg::Material;
323 material->setColorMode(osg::Material::OFF);
324 material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
325 // XXX Alpha < 1.0 in the diffuse material value is a signal to the
326 // default shader to take the alpha value from the material value
327 // and not the glColor. In many cases the pick animation geometry is
328 // transparent, so the outline would not be visible without this hack.
329 material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, .95));
330 material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
331 material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0));
332 stateSet->setAttribute(
333 material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
334 // The default shader has a colorMode uniform that mimics the
335 // behavior of Material color mode.
336 osg::Uniform* cmUniform = 0;
338 ScopedLock<Mutex> lock(colorModeUniformMutex);
339 if (!colorModeUniform.valid()) {
340 colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
341 colorModeUniform->set(0); // MODE_OFF
342 colorModeUniform->setDataVariance(osg::Object::STATIC);
344 cmUniform = colorModeUniform.get();
346 stateSet->addUniform(cmUniform,
347 osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
349 // Only add normal geometry if configured
350 if (getConfig()->getBoolValue("visible", true))
351 parent.addChild(normalGroup.get());
352 parent.addChild(highlightGroup);
356 SGPickAnimation::createAnimationGroup(osg::Group& parent)
358 osg::Group* commonGroup = new osg::Group;
359 innerSetupPickGroup(commonGroup, parent);
360 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
362 // add actions that become macro and command invocations
363 std::vector<SGPropertyNode_ptr> actions;
364 actions = getConfig()->getChildren("action");
365 for (unsigned int i = 0; i < actions.size(); ++i)
366 ud->addPickCallback(new PickCallback(actions[i], getModelRoot()));
367 // Look for the VNC sessions that want raw mouse input
368 actions = getConfig()->getChildren("vncaction");
369 for (unsigned int i = 0; i < actions.size(); ++i)
370 ud->addPickCallback(new VncCallback(actions[i], getModelRoot(),
376 ///////////////////////////////////////////////////////////////////////////
378 // insert count copies of binding list A, into the output list.
379 // effectively makes the output list fire binding A multiple times
381 static void repeatBindings(const SGBindingList& a, SGBindingList& out, int count)
384 for (int i=0; i<count; ++i) {
385 out.insert(out.end(), a.begin(), a.end());
389 static bool static_knobMouseWheelAlternateDirection = false;
390 static bool static_knobDragAlternateAxis = false;
391 static double static_dragSensitivity = 1.0;
393 class KnobSliderPickCallback : public SGPickCallback {
410 KnobSliderPickCallback(const SGPropertyNode* configNode,
411 SGPropertyNode* modelRoot) :
412 SGPickCallback(PriorityPanel),
413 _direction(DIRECTION_NONE),
414 _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1)),
415 _dragDirection(DRAG_DEFAULT)
417 readOptionalBindingList(configNode, modelRoot, "action", _action);
418 readOptionalBindingList(configNode, modelRoot, "increase", _bindingsIncrease);
419 readOptionalBindingList(configNode, modelRoot, "decrease", _bindingsDecrease);
421 readOptionalBindingList(configNode, modelRoot, "release", _releaseAction);
422 readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
424 if (configNode->hasChild("shift-action") || configNode->hasChild("shift-increase") ||
425 configNode->hasChild("shift-decrease"))
427 // explicit shifted behaviour - just do exactly what was provided
428 readOptionalBindingList(configNode, modelRoot, "shift-action", _shiftedAction);
429 readOptionalBindingList(configNode, modelRoot, "shift-increase", _shiftedIncrease);
430 readOptionalBindingList(configNode, modelRoot, "shift-decrease", _shiftedDecrease);
432 // default shifted behaviour - repeat normal bindings N times.
433 int shiftRepeat = configNode->getIntValue("shift-repeat", 10);
434 repeatBindings(_action, _shiftedAction, shiftRepeat);
435 repeatBindings(_bindingsIncrease, _shiftedIncrease, shiftRepeat);
436 repeatBindings(_bindingsDecrease, _shiftedDecrease, shiftRepeat);
437 } // of default shifted behaviour
439 _dragScale = configNode->getDoubleValue("drag-scale-px", 10.0);
440 std::string dragDir = configNode->getStringValue("drag-direction");
441 if (dragDir == "vertical") {
442 _dragDirection = DRAG_VERTICAL;
443 } else if (dragDir == "horizontal") {
444 _dragDirection = DRAG_HORIZONTAL;
448 virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
450 // the 'be nice to Mac / laptop' users option; alt-clicking spins the
451 // opposite direction. Should make this configurable
452 if ((button == 0) && (ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_ALT)) {
456 int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4;
457 int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3;
459 _direction = DIRECTION_NONE;
460 if ((button == 0) || (button == increaseMouseWheel)) {
461 _direction = DIRECTION_INCREASE;
462 } else if ((button == 1) || (button == decreaseMouseWheel)) {
463 _direction = DIRECTION_DECREASE;
468 _lastFirePos = eventToWindowCoords(ea);
469 // delay start of repeat, makes dragging more usable
470 _repeatTime = -_repeatInterval;
475 virtual void buttonReleased(int keyModState)
477 // for *clicks*, we only fire on button release
479 fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
482 fireBindingList(_releaseAction);
485 virtual void mouseMoved(const osgGA::GUIEventAdapter* ea)
487 _mousePos = eventToWindowCoords(ea);
488 osg::Vec2d deltaMouse = _mousePos - _lastFirePos;
492 double manhattanDist = deltaMouse.x() * deltaMouse.x() + deltaMouse.y() * deltaMouse.y();
493 if (manhattanDist < 5) {
494 // don't do anything, just input noise
498 // user is dragging, disable repeat behaviour
502 DragDirection dragDir = _dragDirection;
503 if (dragDir == DRAG_DEFAULT) {
504 // respect the current default settings - this allows runtime
505 // setting of the default drag direction.
506 dragDir = static_knobDragAlternateAxis ? DRAG_VERTICAL : DRAG_HORIZONTAL;
509 double delta = (dragDir == DRAG_VERTICAL) ? deltaMouse.y() : deltaMouse.x();
510 // per-animation scale factor lets the aircraft author tune for expectations,
511 // eg heading setting vs 5-state switch.
512 // then we scale by a global sensitivity, which the user can set.
513 delta *= static_dragSensitivity / _dragScale;
515 if (fabs(delta) >= 1.0) {
516 // determine direction from sign of delta
517 Direction dir = (delta > 0.0) ? DIRECTION_INCREASE : DIRECTION_DECREASE;
518 fire(ea->getModKeyMask(), dir);
519 _lastFirePos = _mousePos;
523 virtual void update(double dt, int keyModState)
530 while (_repeatInterval < _repeatTime) {
531 _repeatTime -= _repeatInterval;
532 fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
533 } // of repeat iteration
536 virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
538 if (_hover.empty()) {
542 SGPropertyNode_ptr params(new SGPropertyNode);
543 params->setDoubleValue("x", windowPos.x());
544 params->setDoubleValue("y", windowPos.y());
545 fireBindingList(_hover, params.ptr());
549 void fire(bool isShifted, Direction dir)
551 const SGBindingList& act(isShifted ? _shiftedAction : _action);
552 const SGBindingList& incr(isShifted ? _shiftedIncrease : _bindingsIncrease);
553 const SGBindingList& decr(isShifted ? _shiftedDecrease : _bindingsDecrease);
556 case DIRECTION_INCREASE:
557 fireBindingListWithOffset(act, 1, 1);
558 fireBindingList(incr);
560 case DIRECTION_DECREASE:
561 fireBindingListWithOffset(act, -1, 1);
562 fireBindingList(decr);
568 SGBindingList _action, _shiftedAction;
569 SGBindingList _releaseAction;
570 SGBindingList _bindingsIncrease, _shiftedIncrease,
571 _bindingsDecrease, _shiftedDecrease;
572 SGBindingList _hover;
575 Direction _direction;
576 double _repeatInterval;
579 DragDirection _dragDirection;
580 bool _hasDragged; ///< has the mouse been dragged since the press?
581 osg::Vec2d _mousePos, ///< current window coords location of the mouse
582 _lastFirePos; ///< mouse location where we last fired the bindings
586 ///////////////////////////////////////////////////////////////////////////////
588 class SGKnobAnimation::UpdateCallback : public osg::NodeCallback {
590 UpdateCallback(SGExpressiond const* animationValue) :
591 _animationValue(animationValue)
593 setName("SGKnobAnimation::UpdateCallback");
595 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
597 SGRotateTransform* transform = static_cast<SGRotateTransform*>(node);
598 transform->setAngleDeg(_animationValue->getValue());
603 SGSharedPtr<SGExpressiond const> _animationValue;
607 SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
608 SGPropertyNode* modelRoot) :
609 SGPickAnimation(configNode, modelRoot)
611 SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-deg",
612 -SGLimitsd::max(), SGLimitsd::max());
613 _animationValue = value->simplify();
616 readRotationCenterAndAxis(configNode, _center, _axis);
621 SGKnobAnimation::createAnimationGroup(osg::Group& parent)
623 SGRotateTransform* transform = new SGRotateTransform();
624 innerSetupPickGroup(transform, parent);
626 UpdateCallback* uc = new UpdateCallback(_animationValue);
627 transform->setUpdateCallback(uc);
628 transform->setCenter(_center);
629 transform->setAxis(_axis);
631 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
632 ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
637 void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle)
639 static_knobMouseWheelAlternateDirection = aToggle;
642 void SGKnobAnimation::setAlternateDragAxis(bool aToggle)
644 static_knobDragAlternateAxis = aToggle;
647 void SGKnobAnimation::setDragSensitivity(double aFactor)
649 static_dragSensitivity = aFactor;
652 ///////////////////////////////////////////////////////////////////////////////
654 class SGSliderAnimation::UpdateCallback : public osg::NodeCallback {
656 UpdateCallback(SGExpressiond const* animationValue) :
657 _animationValue(animationValue)
659 setName("SGSliderAnimation::UpdateCallback");
661 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
663 SGTranslateTransform* transform = static_cast<SGTranslateTransform*>(node);
664 transform->setValue(_animationValue->getValue());
670 SGSharedPtr<SGExpressiond const> _animationValue;
674 SGSliderAnimation::SGSliderAnimation(const SGPropertyNode* configNode,
675 SGPropertyNode* modelRoot) :
676 SGPickAnimation(configNode, modelRoot)
678 SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-m",
679 -SGLimitsd::max(), SGLimitsd::max());
680 _animationValue = value->simplify();
682 _axis = readTranslateAxis(configNode);
686 SGSliderAnimation::createAnimationGroup(osg::Group& parent)
688 SGTranslateTransform* transform = new SGTranslateTransform();
689 innerSetupPickGroup(transform, parent);
691 UpdateCallback* uc = new UpdateCallback(_animationValue);
692 transform->setUpdateCallback(uc);
693 transform->setAxis(_axis);
695 SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
696 ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));