Support for proxy pick objects.

Pick, knob and slider animations support additional object-names,
which are always invisible and untransformed (in the case of knobs/
sliders). These objects act to extend the hover/pick area, making small
switches and knobs more usable. (Especially for users with smaller
screens / windows).
This commit is contained in:
James Turner
2013-04-05 17:12:58 +01:00
parent 45d5511fd7
commit f6709e357f
3 changed files with 269 additions and 164 deletions
+231 -147
View File
@@ -22,6 +22,8 @@
#include <simgear/scene/model/SGPickAnimation.hxx> #include <simgear/scene/model/SGPickAnimation.hxx>
#include <algorithm>
#include <osg/Geode> #include <osg/Geode>
#include <osg/PolygonOffset> #include <osg/PolygonOffset>
#include <osg/PolygonMode> #include <osg/PolygonMode>
@@ -231,108 +233,106 @@ osg::Vec2d eventToWindowCoords(const osgGA::GUIEventAdapter* ea)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
class SGPickAnimation::VncCallback : public SGPickCallback { class SGPickAnimation::VncCallback : public SGPickCallback {
public: public:
VncCallback(const SGPropertyNode* configNode, VncCallback(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot, SGPropertyNode* modelRoot,
osg::Group *node) osg::Group *node)
: _node(node) : _node(node)
{ {
SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback"); SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback");
const char *cornernames[3] = {"top-left", "top-right", "bottom-left"}; const char *cornernames[3] = {"top-left", "top-right", "bottom-left"};
SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown}; SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown};
for (int c =0; c < 3; c++) { for (int c =0; c < 3; c++) {
const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]); const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]);
*cornercoords[c] = SGVec3d( *cornercoords[c] = SGVec3d(
cornerNode->getDoubleValue("x"), cornerNode->getDoubleValue("x"),
cornerNode->getDoubleValue("y"), cornerNode->getDoubleValue("y"),
cornerNode->getDoubleValue("z")); cornerNode->getDoubleValue("z"));
}
_toRight -= _topLeft;
_toDown -= _topLeft;
_squaredRight = dot(_toRight, _toRight);
_squaredDown = dot(_toDown, _toDown);
} }
_toRight -= _topLeft;
_toDown -= _topLeft;
_squaredRight = dot(_toRight, _toRight);
_squaredDown = dot(_toDown, _toDown);
}
virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info) virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
{ {
SGVec3d loc(info.local); SGVec3d loc(info.local);
SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc); SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
loc -= _topLeft; loc -= _topLeft;
_x = dot(loc, _toRight) / _squaredRight; _x = dot(loc, _toRight) / _squaredRight;
_y = dot(loc, _toDown) / _squaredDown; _y = dot(loc, _toDown) / _squaredDown;
if (_x<0) _x = 0; else if (_x > 1) _x = 1; if (_x<0) _x = 0; else if (_x > 1) _x = 1;
if (_y<0) _y = 0; else if (_y > 1) _y = 1; if (_y<0) _y = 0; else if (_y > 1) _y = 1;
VncVisitor vv(_x, _y, 1 << button); VncVisitor vv(_x, _y, 1 << button);
_node->accept(vv); _node->accept(vv);
return vv.wasSuccessful(); return vv.wasSuccessful();
} }
virtual void buttonReleased(int keyModState) virtual void buttonReleased(int keyModState)
{ {
SG_UNUSED(keyModState); SG_UNUSED(keyModState);
SG_LOG(SG_INPUT, SG_DEBUG, "VNC release"); SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
VncVisitor vv(_x, _y, 0); VncVisitor vv(_x, _y, 0);
_node->accept(vv); _node->accept(vv);
} }
private: private:
double _x, _y; double _x, _y;
osg::ref_ptr<osg::Group> _node; osg::ref_ptr<osg::Group> _node;
SGVec3d _topLeft, _toRight, _toDown; SGVec3d _topLeft, _toRight, _toDown;
double _squaredRight, _squaredDown; double _squaredRight, _squaredDown;
}; };
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode, SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) : SGPropertyNode* modelRoot) :
SGAnimation(configNode, modelRoot) SGAnimation(configNode, modelRoot)
{
}
namespace
{
OpenThreads::Mutex colorModeUniformMutex;
osg::ref_ptr<osg::Uniform> colorModeUniform;
}
void
SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent)
{ {
// Contains the normal geometry that is interactive std::vector<SGPropertyNode_ptr> names =
osg::ref_ptr<osg::Group> normalGroup = new osg::Group; configNode->getChildren("proxy-name");
normalGroup->setName("pick normal group"); for (unsigned i = 0; i < names.size(); ++i) {
normalGroup->addChild(commonGroup); _proxyNames.push_back(names[i]->getStringValue());
}
}
// Used to render the geometry with just yellow edges void SGPickAnimation::apply(osg::Node* node)
osg::Group* highlightGroup = new osg::Group; {
highlightGroup->setName("pick highlight group"); SGAnimation::apply(node);
highlightGroup->setNodeMask(simgear::PICK_BIT); }
highlightGroup->addChild(commonGroup);
namespace
{
OpenThreads::Mutex highlightStateSetMutex;
osg::ref_ptr<osg::StateSet> static_highlightStateSet;
}
osg::StateSet* sharedHighlightStateSet()
{
ScopedLock<Mutex> lock(highlightStateSetMutex);
if (!static_highlightStateSet.valid()) {
static_highlightStateSet = new osg::StateSet;
// prepare a state set that paints the edges of this object yellow
// The material and texture attributes are set with
// OVERRIDE|PROTECTED in case there is a material animation on a
// higher node in the scene graph, which would have its material
// attribute set with OVERRIDE.
osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture(); osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
stateSet->setTextureAttributeAndModes(0, white, static_highlightStateSet->setTextureAttributeAndModes(0, white,
(osg::StateAttribute::ON (osg::StateAttribute::ON
| osg::StateAttribute::OVERRIDE | osg::StateAttribute::OVERRIDE
| osg::StateAttribute::PROTECTED)); | osg::StateAttribute::PROTECTED));
osg::PolygonOffset* polygonOffset = new osg::PolygonOffset; osg::PolygonOffset* polygonOffset = new osg::PolygonOffset;
polygonOffset->setFactor(-1); polygonOffset->setFactor(-1);
polygonOffset->setUnits(-1); polygonOffset->setUnits(-1);
stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE); static_highlightStateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
stateSet->setMode(GL_POLYGON_OFFSET_LINE, static_highlightStateSet->setMode(GL_POLYGON_OFFSET_LINE,
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
osg::PolygonMode* polygonMode = new osg::PolygonMode; osg::PolygonMode* polygonMode = new osg::PolygonMode;
polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK, polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK,
osg::PolygonMode::LINE); osg::PolygonMode::LINE);
stateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE); static_highlightStateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
osg::Material* material = new osg::Material; osg::Material* material = new osg::Material;
material->setColorMode(osg::Material::OFF); material->setColorMode(osg::Material::OFF);
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1)); material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
@@ -343,64 +343,143 @@ SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, .95)); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, .95));
material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1)); material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0)); material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0));
stateSet->setAttribute( static_highlightStateSet->setAttribute(
material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
// The default shader has a colorMode uniform that mimics the // The default shader has a colorMode uniform that mimics the
// behavior of Material color mode. // behavior of Material color mode.
osg::Uniform* cmUniform = 0;
{
ScopedLock<Mutex> lock(colorModeUniformMutex);
if (!colorModeUniform.valid()) {
colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
colorModeUniform->set(0); // MODE_OFF
colorModeUniform->setDataVariance(osg::Object::STATIC);
}
cmUniform = colorModeUniform.get();
}
stateSet->addUniform(cmUniform,
osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
// Only add normal geometry if configured osg::Uniform* colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
if (getConfig()->getBoolValue("visible", true)) colorModeUniform->set(0); // MODE_OFF
parent.addChild(normalGroup.get()); colorModeUniform->setDataVariance(osg::Object::STATIC);
parent.addChild(highlightGroup); static_highlightStateSet->addUniform(colorModeUniform,
osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
}
return static_highlightStateSet.get();
} }
osg::Group* void
SGPickAnimation::createAnimationGroup(osg::Group& parent) SGPickAnimation::apply(osg::Group& group)
{ {
osg::Group* commonGroup = new osg::Group; if (_objectNames.empty()) {
innerSetupPickGroup(commonGroup, parent); return;
SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup); }
PickCallback* pickCb = NULL; osg::ref_ptr<osg::Group> renderGroup, proxyGroup, pickGroup;
group.traverse(*this);
SGSceneUserData* ud = NULL;
// add actions that become macro and command invocations // iterate over all group children
std::vector<SGPropertyNode_ptr> actions; int i = group.getNumChildren() - 1;
actions = getConfig()->getChildren("action"); for (; 0 <= i; --i) {
for (unsigned int i = 0; i < actions.size(); ++i) { osg::Node* child = group.getChild(i);
pickCb = new PickCallback(actions[i], getModelRoot()); if (child->getName().empty()) {
ud->addPickCallback(pickCb); continue;
} }
if (getConfig()->hasChild("hovered")) { std::list<std::string>::iterator it = std::find(_objectNames.begin(), _objectNames.end(), child->getName());
if (!pickCb) { if (it != _objectNames.end()) {
// make a trivial PickCallback to hang the hovered off of _objectNames.erase(it);
SGPropertyNode_ptr dummyNode(new SGPropertyNode); install(*child);
pickCb = new PickCallback(dummyNode.ptr(), getModelRoot());
ud->addPickCallback(pickCb);
}
pickCb->addHoverBindings(getConfig()->getNode("hovered"), getModelRoot()); //////
} if (!pickGroup) {
osg::Group* mainGroup = createMainGroup(&group);
mainGroup->setName(child->getName());
// Look for the VNC sessions that want raw mouse input if (getConfig()->getBoolValue("visible", true)) {
actions = getConfig()->getChildren("vncaction"); renderGroup = new osg::Group;
for (unsigned int i = 0; i < actions.size(); ++i) renderGroup->setName("pick render group");
ud->addPickCallback(new VncCallback(actions[i], getModelRoot(), mainGroup->addChild(renderGroup);
&parent)); }
return commonGroup; pickGroup = new osg::Group;
pickGroup->setName("pick highlight group");
pickGroup->setNodeMask(simgear::PICK_BIT);
pickGroup->setStateSet(sharedHighlightStateSet());
mainGroup->addChild(pickGroup);
if (!ud) {
ud = SGSceneUserData::getOrCreateSceneUserData(pickGroup);
setupCallbacks(ud, &group);
} else {
pickGroup->setUserData(ud);
}
} // of pick group setup
////////
child->setName("");
if (renderGroup.valid()) {
renderGroup->addChild(child);
}
pickGroup->addChild(child);
group.removeChild(child);
continue;
}
string_list::iterator j = std::find(_proxyNames.begin(), _proxyNames.end(), child->getName());
if (j == _proxyNames.end()) {
continue;
}
_proxyNames.erase(j);
if (!proxyGroup) {
proxyGroup = new osg::Group;
group.addChild(proxyGroup);
proxyGroup->setStateSet(sharedHighlightStateSet());
proxyGroup->setNodeMask(simgear::PICK_BIT);
if (!ud) {
ud = SGSceneUserData::getOrCreateSceneUserData(proxyGroup);
setupCallbacks(ud, &group);
} else {
proxyGroup->setUserData(ud);
}
} // of proxy group setup
proxyGroup->addChild(child);
group.removeChild(child);
} // of group children iteration
}
osg::Group*
SGPickAnimation::createMainGroup(osg::Group* pr)
{
osg::Group* g = new osg::Group;
pr->addChild(g);
return g;
}
void
SGPickAnimation::setupCallbacks(SGSceneUserData* ud, osg::Group* parent)
{
PickCallback* pickCb = NULL;
// add actions that become macro and command invocations
std::vector<SGPropertyNode_ptr> actions;
actions = getConfig()->getChildren("action");
for (unsigned int i = 0; i < actions.size(); ++i) {
pickCb = new PickCallback(actions[i], getModelRoot());
ud->addPickCallback(pickCb);
}
if (getConfig()->hasChild("hovered")) {
if (!pickCb) {
// make a trivial PickCallback to hang the hovered off of
SGPropertyNode_ptr dummyNode(new SGPropertyNode);
pickCb = new PickCallback(dummyNode.ptr(), getModelRoot());
ud->addPickCallback(pickCb);
}
pickCb->addHoverBindings(getConfig()->getNode("hovered"), getModelRoot());
}
// Look for the VNC sessions that want raw mouse input
actions = getConfig()->getChildren("vncaction");
for (unsigned int i = 0; i < actions.size(); ++i) {
ud->addPickCallback(new VncCallback(actions[i], getModelRoot(), parent));
}
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@@ -672,22 +751,24 @@ SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
readRotationCenterAndAxis(configNode, _center, _axis); readRotationCenterAndAxis(configNode, _center, _axis);
} }
osg::Group* osg::Group*
SGKnobAnimation::createAnimationGroup(osg::Group& parent) SGKnobAnimation::createMainGroup(osg::Group* pr)
{ {
SGRotateTransform* transform = new SGRotateTransform(); SGRotateTransform* transform = new SGRotateTransform();
innerSetupPickGroup(transform, parent);
UpdateCallback* uc = new UpdateCallback(_animationValue); UpdateCallback* uc = new UpdateCallback(_animationValue);
transform->setUpdateCallback(uc); transform->setUpdateCallback(uc);
transform->setCenter(_center); transform->setCenter(_center);
transform->setAxis(_axis); transform->setAxis(_axis);
SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform); pr->addChild(transform);
ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot())); return transform;
}
return transform; void
SGKnobAnimation::setupCallbacks(SGSceneUserData* ud, osg::Group*)
{
ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
} }
void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle) void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle)
@@ -739,17 +820,20 @@ SGSliderAnimation::SGSliderAnimation(const SGPropertyNode* configNode,
} }
osg::Group* osg::Group*
SGSliderAnimation::createAnimationGroup(osg::Group& parent) SGSliderAnimation::createMainGroup(osg::Group* pr)
{ {
SGTranslateTransform* transform = new SGTranslateTransform(); SGTranslateTransform* transform = new SGTranslateTransform();
innerSetupPickGroup(transform, parent);
UpdateCallback* uc = new UpdateCallback(_animationValue); UpdateCallback* uc = new UpdateCallback(_animationValue);
transform->setUpdateCallback(uc); transform->setUpdateCallback(uc);
transform->setAxis(_axis); transform->setAxis(_axis);
SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform); pr->addChild(transform);
ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot())); return transform;
}
return transform; void
SGSliderAnimation::setupCallbacks(SGSceneUserData* ud, osg::Group*)
{
ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
} }
+24 -4
View File
@@ -23,9 +23,11 @@
#define SG_SCENE_PICK_ANIMATION_HXX #define SG_SCENE_PICK_ANIMATION_HXX
#include <simgear/scene/model/animation.hxx> #include <simgear/scene/model/animation.hxx>
#include <simgear/misc/strutils.hxx>
// forward decls // forward decls
class SGPickCallback; class SGPickCallback;
class SGSceneUserData;
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Pick animation // Pick animation
@@ -35,15 +37,22 @@ class SGPickAnimation : public SGAnimation {
public: public:
SGPickAnimation(const SGPropertyNode* configNode, SGPickAnimation(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot); SGPropertyNode* modelRoot);
virtual osg::Group* createAnimationGroup(osg::Group& parent);
// override so we can treat object-name specially
virtual void apply(osg::Group& group);
void apply(osg::Node* node);
protected: protected:
void innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent);
virtual osg::Group* createMainGroup(osg::Group* pr);
virtual void setupCallbacks(SGSceneUserData* ud, osg::Group* parent);
private: private:
class PickCallback; class PickCallback;
class VncCallback; class VncCallback;
string_list _proxyNames;
}; };
@@ -52,7 +61,6 @@ class SGKnobAnimation : public SGPickAnimation
public: public:
SGKnobAnimation(const SGPropertyNode* configNode, SGKnobAnimation(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot); SGPropertyNode* modelRoot);
virtual osg::Group* createAnimationGroup(osg::Group& parent);
/** /**
* by default mouse wheel up corresponds to increment (CW) * by default mouse wheel up corresponds to increment (CW)
@@ -75,6 +83,13 @@ public:
* personal preference. * personal preference.
*/ */
static void setDragSensitivity(double aFactor); static void setDragSensitivity(double aFactor);
protected:
virtual osg::Group* createMainGroup(osg::Group* pr);
virtual void setupCallbacks(SGSceneUserData* ud, osg::Group* parent);
private: private:
class UpdateCallback; class UpdateCallback;
@@ -88,7 +103,12 @@ class SGSliderAnimation : public SGPickAnimation
public: public:
SGSliderAnimation(const SGPropertyNode* configNode, SGSliderAnimation(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot); SGPropertyNode* modelRoot);
virtual osg::Group* createAnimationGroup(osg::Group& parent);
protected:
virtual osg::Group* createMainGroup(osg::Group* pr);
virtual void setupCallbacks(SGSceneUserData* ud, osg::Group* parent);
private: private:
class UpdateCallback; class UpdateCallback;
+2 -1
View File
@@ -79,6 +79,7 @@ protected:
const SGCondition* getCondition() const; const SGCondition* getCondition() const;
std::list<std::string> _objectNames;
private: private:
void installInGroup(const std::string& name, osg::Group& group, void installInGroup(const std::string& name, osg::Group& group,
osg::ref_ptr<osg::Group>& animationGroup); osg::ref_ptr<osg::Group>& animationGroup);
@@ -94,7 +95,7 @@ private:
std::string _name; std::string _name;
SGSharedPtr<SGPropertyNode const> _configNode; SGSharedPtr<SGPropertyNode const> _configNode;
SGPropertyNode* _modelRoot; SGPropertyNode* _modelRoot;
std::list<std::string> _objectNames;
std::list<osg::ref_ptr<osg::Node> > _installedAnimations; std::list<osg::ref_ptr<osg::Node> > _installedAnimations;
bool _enableHOT; bool _enableHOT;
}; };