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 09:13:58 +01:00
parent 45d5511fd7
commit f6709e357f
3 changed files with 270 additions and 165 deletions

View File

@@ -22,6 +22,8 @@
#include <simgear/scene/model/SGPickAnimation.hxx>
#include <algorithm>
#include <osg/Geode>
#include <osg/PolygonOffset>
#include <osg/PolygonMode>
@@ -231,108 +233,106 @@ osg::Vec2d eventToWindowCoords(const osgGA::GUIEventAdapter* ea)
///////////////////////////////////////////////////////////////////////////////
class SGPickAnimation::VncCallback : public SGPickCallback {
public:
VncCallback(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot,
osg::Group *node)
: _node(node)
{
SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback");
const char *cornernames[3] = {"top-left", "top-right", "bottom-left"};
SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown};
for (int c =0; c < 3; c++) {
const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]);
*cornercoords[c] = SGVec3d(
cornerNode->getDoubleValue("x"),
cornerNode->getDoubleValue("y"),
cornerNode->getDoubleValue("z"));
}
_toRight -= _topLeft;
_toDown -= _topLeft;
_squaredRight = dot(_toRight, _toRight);
_squaredDown = dot(_toDown, _toDown);
class SGPickAnimation::VncCallback : public SGPickCallback {
public:
VncCallback(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot,
osg::Group *node)
: _node(node)
{
SG_LOG(SG_INPUT, SG_DEBUG, "Configuring VNC callback");
const char *cornernames[3] = {"top-left", "top-right", "bottom-left"};
SGVec3d *cornercoords[3] = {&_topLeft, &_toRight, &_toDown};
for (int c =0; c < 3; c++) {
const SGPropertyNode* cornerNode = configNode->getChild(cornernames[c]);
*cornercoords[c] = SGVec3d(
cornerNode->getDoubleValue("x"),
cornerNode->getDoubleValue("y"),
cornerNode->getDoubleValue("z"));
}
_toRight -= _topLeft;
_toDown -= _topLeft;
_squaredRight = dot(_toRight, _toRight);
_squaredDown = dot(_toDown, _toDown);
}
virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
{
SGVec3d loc(info.local);
SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
loc -= _topLeft;
_x = dot(loc, _toRight) / _squaredRight;
_y = dot(loc, _toDown) / _squaredDown;
if (_x<0) _x = 0; else if (_x > 1) _x = 1;
if (_y<0) _y = 0; else if (_y > 1) _y = 1;
VncVisitor vv(_x, _y, 1 << button);
_node->accept(vv);
return vv.wasSuccessful();
virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
{
SGVec3d loc(info.local);
SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
loc -= _topLeft;
_x = dot(loc, _toRight) / _squaredRight;
_y = dot(loc, _toDown) / _squaredDown;
if (_x<0) _x = 0; else if (_x > 1) _x = 1;
if (_y<0) _y = 0; else if (_y > 1) _y = 1;
VncVisitor vv(_x, _y, 1 << button);
_node->accept(vv);
return vv.wasSuccessful();
}
virtual void buttonReleased(int keyModState)
{
SG_UNUSED(keyModState);
SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
VncVisitor vv(_x, _y, 0);
_node->accept(vv);
}
}
virtual void buttonReleased(int keyModState)
{
SG_UNUSED(keyModState);
SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
VncVisitor vv(_x, _y, 0);
_node->accept(vv);
}
private:
double _x, _y;
osg::ref_ptr<osg::Group> _node;
SGVec3d _topLeft, _toRight, _toDown;
double _squaredRight, _squaredDown;
};
private:
double _x, _y;
osg::ref_ptr<osg::Group> _node;
SGVec3d _topLeft, _toRight, _toDown;
double _squaredRight, _squaredDown;
};
///////////////////////////////////////////////////////////////////////////////
SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) :
SGAnimation(configNode, modelRoot)
{
}
namespace
{
OpenThreads::Mutex colorModeUniformMutex;
osg::ref_ptr<osg::Uniform> colorModeUniform;
}
void
SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent)
SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) :
SGAnimation(configNode, modelRoot)
{
// Contains the normal geometry that is interactive
osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
normalGroup->setName("pick normal group");
normalGroup->addChild(commonGroup);
std::vector<SGPropertyNode_ptr> names =
configNode->getChildren("proxy-name");
for (unsigned i = 0; i < names.size(); ++i) {
_proxyNames.push_back(names[i]->getStringValue());
}
}
void SGPickAnimation::apply(osg::Node* node)
{
SGAnimation::apply(node);
}
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;
// Used to render the geometry with just yellow edges
osg::Group* highlightGroup = new osg::Group;
highlightGroup->setName("pick highlight group");
highlightGroup->setNodeMask(simgear::PICK_BIT);
highlightGroup->addChild(commonGroup);
// 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();
stateSet->setTextureAttributeAndModes(0, white,
static_highlightStateSet->setTextureAttributeAndModes(0, white,
(osg::StateAttribute::ON
| osg::StateAttribute::OVERRIDE
| osg::StateAttribute::PROTECTED));
osg::PolygonOffset* polygonOffset = new osg::PolygonOffset;
polygonOffset->setFactor(-1);
polygonOffset->setUnits(-1);
stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
stateSet->setMode(GL_POLYGON_OFFSET_LINE,
static_highlightStateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
static_highlightStateSet->setMode(GL_POLYGON_OFFSET_LINE,
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
osg::PolygonMode* polygonMode = new osg::PolygonMode;
polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK,
osg::PolygonMode::LINE);
stateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
static_highlightStateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
osg::Material* material = new osg::Material;
material->setColorMode(osg::Material::OFF);
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->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));
stateSet->setAttribute(
static_highlightStateSet->setAttribute(
material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
// The default shader has a colorMode uniform that mimics the
// 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::Uniform* colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
colorModeUniform->set(0); // MODE_OFF
colorModeUniform->setDataVariance(osg::Object::STATIC);
static_highlightStateSet->addUniform(colorModeUniform,
osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
// Only add normal geometry if configured
if (getConfig()->getBoolValue("visible", true))
parent.addChild(normalGroup.get());
parent.addChild(highlightGroup);
}
return static_highlightStateSet.get();
}
osg::Group*
SGPickAnimation::createAnimationGroup(osg::Group& parent)
{
osg::Group* commonGroup = new osg::Group;
innerSetupPickGroup(commonGroup, parent);
SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
void
SGPickAnimation::apply(osg::Group& group)
{
if (_objectNames.empty()) {
return;
}
osg::ref_ptr<osg::Group> renderGroup, proxyGroup, pickGroup;
group.traverse(*this);
SGSceneUserData* ud = NULL;
// iterate over all group children
int i = group.getNumChildren() - 1;
for (; 0 <= i; --i) {
osg::Node* child = group.getChild(i);
if (child->getName().empty()) {
continue;
}
std::list<std::string>::iterator it = std::find(_objectNames.begin(), _objectNames.end(), child->getName());
if (it != _objectNames.end()) {
_objectNames.erase(it);
install(*child);
//////
if (!pickGroup) {
osg::Group* mainGroup = createMainGroup(&group);
mainGroup->setName(child->getName());
if (getConfig()->getBoolValue("visible", true)) {
renderGroup = new osg::Group;
renderGroup->setName("pick render group");
mainGroup->addChild(renderGroup);
}
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
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));
proxyGroup->addChild(child);
group.removeChild(child);
} // of group children iteration
}
return commonGroup;
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);
}
osg::Group*
SGKnobAnimation::createAnimationGroup(osg::Group& parent)
SGKnobAnimation::createMainGroup(osg::Group* pr)
{
SGRotateTransform* transform = new SGRotateTransform();
UpdateCallback* uc = new UpdateCallback(_animationValue);
transform->setUpdateCallback(uc);
transform->setCenter(_center);
transform->setAxis(_axis);
pr->addChild(transform);
return transform;
}
void
SGKnobAnimation::setupCallbacks(SGSceneUserData* ud, osg::Group*)
{
SGRotateTransform* transform = new SGRotateTransform();
innerSetupPickGroup(transform, parent);
UpdateCallback* uc = new UpdateCallback(_animationValue);
transform->setUpdateCallback(uc);
transform->setCenter(_center);
transform->setAxis(_axis);
SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
return transform;
ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
}
void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle)
@@ -739,17 +820,20 @@ SGSliderAnimation::SGSliderAnimation(const SGPropertyNode* configNode,
}
osg::Group*
SGSliderAnimation::createAnimationGroup(osg::Group& parent)
{
SGTranslateTransform* transform = new SGTranslateTransform();
innerSetupPickGroup(transform, parent);
UpdateCallback* uc = new UpdateCallback(_animationValue);
transform->setUpdateCallback(uc);
transform->setAxis(_axis);
SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
return transform;
SGSliderAnimation::createMainGroup(osg::Group* pr)
{
SGTranslateTransform* transform = new SGTranslateTransform();
UpdateCallback* uc = new UpdateCallback(_animationValue);
transform->setUpdateCallback(uc);
transform->setAxis(_axis);
pr->addChild(transform);
return transform;
}
void
SGSliderAnimation::setupCallbacks(SGSceneUserData* ud, osg::Group*)
{
ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
}

View File

@@ -23,9 +23,11 @@
#define SG_SCENE_PICK_ANIMATION_HXX
#include <simgear/scene/model/animation.hxx>
#include <simgear/misc/strutils.hxx>
// forward decls
class SGPickCallback;
class SGSceneUserData;
//////////////////////////////////////////////////////////////////////
// Pick animation
@@ -35,15 +37,22 @@ class SGPickAnimation : public SGAnimation {
public:
SGPickAnimation(const SGPropertyNode* configNode,
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:
void innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent);
virtual osg::Group* createMainGroup(osg::Group* pr);
virtual void setupCallbacks(SGSceneUserData* ud, osg::Group* parent);
private:
class PickCallback;
class VncCallback;
string_list _proxyNames;
};
@@ -52,7 +61,6 @@ class SGKnobAnimation : public SGPickAnimation
public:
SGKnobAnimation(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot);
virtual osg::Group* createAnimationGroup(osg::Group& parent);
/**
* by default mouse wheel up corresponds to increment (CW)
@@ -75,6 +83,13 @@ public:
* personal preference.
*/
static void setDragSensitivity(double aFactor);
protected:
virtual osg::Group* createMainGroup(osg::Group* pr);
virtual void setupCallbacks(SGSceneUserData* ud, osg::Group* parent);
private:
class UpdateCallback;
@@ -88,7 +103,12 @@ class SGSliderAnimation : public SGPickAnimation
public:
SGSliderAnimation(const SGPropertyNode* configNode,
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:
class UpdateCallback;

View File

@@ -79,6 +79,7 @@ protected:
const SGCondition* getCondition() const;
std::list<std::string> _objectNames;
private:
void installInGroup(const std::string& name, osg::Group& group,
osg::ref_ptr<osg::Group>& animationGroup);
@@ -94,7 +95,7 @@ private:
std::string _name;
SGSharedPtr<SGPropertyNode const> _configNode;
SGPropertyNode* _modelRoot;
std::list<std::string> _objectNames;
std::list<osg::ref_ptr<osg::Node> > _installedAnimations;
bool _enableHOT;
};