#include <osg/Geometry>
#include <osg/LOD>
#include <osg/Math>
+#include <osg/Object>
#include <osg/PolygonMode>
#include <osg/PolygonOffset>
#include <osg/StateSet>
#include <osg/TexMat>
#include <osg/Texture2D>
#include <osg/Transform>
+#include <osg/Uniform>
#include <osgDB/ReadFile>
+#include <osgDB/Registry>
+#include <osgDB/Input>
+#include <osgDB/ParameterOutput>
+
#include <simgear/math/interpolater.hxx>
#include <simgear/props/condition.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/SGBinding.hxx>
+#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/util/SGNodeMasks.hxx>
#include <simgear/scene/util/SGSceneUserData.hxx>
#include <simgear/scene/util/SGStateAttributeVisitor.hxx>
+#include <simgear/scene/util/StateAttributeFactory.hxx>
#include "animation.hxx"
#include "model.hxx"
#include "SGScaleTransform.hxx"
#include "SGInteractionAnimation.hxx"
+#include "ConditionNode.hxx"
+
using OpenThreads::Mutex;
using OpenThreads::ReentrantMutex;
using OpenThreads::ScopedLock;
+using namespace simgear;
\f
////////////////////////////////////////////////////////////////////////
// Static utility functions.
read_value(const SGPropertyNode* configNode, SGPropertyNode* modelRoot,
const char* unit, double defMin, double defMax)
{
+ const SGPropertyNode * expression = configNode->getNode( "expression" );
+ if( expression != NULL )
+ return SGReadDoubleExpression( modelRoot, expression->getChild(0) );
+
SGExpression<double>* value = 0;
std::string inputPropertyName = configNode->getStringValue("property", "");
SGAnimation::~SGAnimation()
{
- if (_found)
- return;
-
- SG_LOG(SG_IO, SG_ALERT, "Could not find at least one of the following"
- " objects for animation:\n");
- std::list<std::string>::const_iterator i;
- for (i = _objectNames.begin(); i != _objectNames.end(); ++i)
- SG_LOG(SG_IO, SG_ALERT, *i << "\n");
+ if (!_found)
+ {
+ std::list<std::string>::const_iterator i;
+ string info;
+ for (i = _objectNames.begin(); i != _objectNames.end(); ++i)
+ {
+ if (!info.empty())
+ info.append(", ");
+ info.append("'");
+ info.append(*i);
+ info.append("'");
+ }
+ if (!info.empty())
+ {
+ SG_LOG(SG_IO, SG_ALERT, "Could not find at least one of the following"
+ " objects for animation: " << info);
+ }
+ }
}
bool
class SGDistScaleAnimation::Transform : public osg::Transform {
public:
+ Transform() : _min_v(0.0), _max_v(0.0), _factor(0.0), _offset(0.0) {}
+ Transform(const Transform& rhs,
+ const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
+ : osg::Transform(rhs, copyOp), _table(rhs._table), _center(rhs._center),
+ _min_v(rhs._min_v), _max_v(rhs._max_v), _factor(rhs._factor),
+ _offset(rhs._offset)
+ {
+ }
+ META_Node(simgear, SGDistScaleAnimation::Transform);
Transform(const SGPropertyNode* configNode)
{
setName(configNode->getStringValue("name", "dist scale animation"));
return true;
}
+ static bool writeLocalData(const osg::Object& obj, osgDB::Output& fw)
+ {
+ const Transform& trans = static_cast<const Transform&>(obj);
+ fw.indent() << "center " << trans._center << "\n";
+ fw.indent() << "min_v " << trans._min_v << "\n";
+ fw.indent() << "max_v " << trans._max_v << "\n";
+ fw.indent() << "factor " << trans._factor << "\n";
+ fw.indent() << "offset " << trans._offset << "\n";
+ return true;
+ }
private:
double computeScaleFactor(osg::NodeVisitor* nv) const
{
if (!nv)
return 1;
- double scale_factor = (_center.osg() - nv->getEyePoint()).length();
+ double scale_factor = (toOsg(_center) - nv->getEyePoint()).length();
if (_table == 0) {
scale_factor = _factor * scale_factor + _offset;
} else {
return transform;
}
+namespace
+{
+ osgDB::RegisterDotOsgWrapperProxy distScaleAnimationTransformProxy
+ (
+ new SGDistScaleAnimation::Transform,
+ "SGDistScaleAnimation::Transform",
+ "Object Node Transform SGDistScaleAnimation::Transform Group",
+ 0,
+ &SGDistScaleAnimation::Transform::writeLocalData
+ );
+}
\f
////////////////////////////////////////////////////////////////////////
// Implementation of flash animation
class SGFlashAnimation::Transform : public osg::Transform {
public:
+ Transform() : _power(0.0), _factor(0.0), _offset(0.0), _min_v(0.0),
+ _max_v(0.0), _two_sides(false)
+ {}
+
+ Transform(const Transform& rhs,
+ const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
+ : osg::Transform(rhs, copyOp), _center(rhs._center), _axis(rhs._axis),
+ _power(rhs._power), _factor(rhs._factor), _offset(rhs._offset),
+ _min_v(rhs._min_v), _max_v(rhs._max_v), _two_sides(rhs._two_sides)
+ {
+ }
+ META_Node(simgear, SGFlashAnimation::Transform);
+
Transform(const SGPropertyNode* configNode)
{
setReferenceFrame(RELATIVE_RF);
return true;
}
+ static bool writeLocalData(const osg::Object& obj, osgDB::Output& fw)
+ {
+ const Transform& trans = static_cast<const Transform&>(obj);
+ fw.indent() << "center " << trans._center[0] << " "
+ << trans._center[1] << " " << trans._center[2] << " " << "\n";
+ fw.indent() << "axis " << trans._axis[0] << " "
+ << trans._axis[1] << " " << trans._axis[2] << " " << "\n";
+ fw.indent() << "power " << trans._power << " \n";
+ fw.indent() << "min_v " << trans._min_v << "\n";
+ fw.indent() << "max_v " << trans._max_v << "\n";
+ fw.indent() << "factor " << trans._factor << "\n";
+ fw.indent() << "offset " << trans._offset << "\n";
+ fw.indent() << "twosides " << (trans._two_sides ? "true" : "false") << "\n";
+ return true;
+ }
private:
double computeScaleFactor(osg::NodeVisitor* nv) const
{
return transform;
}
+namespace
+{
+ osgDB::RegisterDotOsgWrapperProxy flashAnimationTransformProxy
+ (
+ new SGFlashAnimation::Transform,
+ "SGFlashAnimation::Transform",
+ "Object Node Transform SGFlashAnimation::Transform Group",
+ 0,
+ &SGFlashAnimation::Transform::writeLocalData
+ );
+}
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of flash animation
+// Implementation of billboard animation
////////////////////////////////////////////////////////////////////////
class SGBillboardAnimation::Transform : public osg::Transform {
public:
+ Transform() : _spherical(true) {}
+ Transform(const Transform& rhs,
+ const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
+ : osg::Transform(rhs, copyOp), _spherical(rhs._spherical) {}
+ META_Node(simgear, SGBillboardAnimation::Transform);
Transform(const SGPropertyNode* configNode) :
_spherical(configNode->getBoolValue("spherical", true))
{
// Hmm, don't yet know how to get that back ...
return false;
}
+ static bool writeLocalData(const osg::Object& obj, osgDB::Output& fw)
+ {
+ const Transform& trans = static_cast<const Transform&>(obj);
+ fw.indent() << (trans._spherical ? "true" : "false") << "\n";
+ return true;
+ }
private:
bool _spherical;
};
return transform;
}
+namespace
+{
+ osgDB::RegisterDotOsgWrapperProxy billboardAnimationTransformProxy
+ (
+ new SGBillboardAnimation::Transform,
+ "SGBillboardAnimation::Transform",
+ "Object Node Transform SGBillboardAnimation::Transform Group",
+ 0,
+ &SGBillboardAnimation::Transform::writeLocalData
+ );
+}
\f
////////////////////////////////////////////////////////////////////////
// Implementation of a range animation
// Implementation of a select animation
////////////////////////////////////////////////////////////////////////
-class SGSelectAnimation::UpdateCallback : public osg::NodeCallback {
-public:
- UpdateCallback(const SGCondition* condition) :
- _condition(condition)
- {}
- virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
- {
- osg::Switch* sw = static_cast<osg::Switch*>(node);
- if (_condition->test())
- sw->setAllChildrenOn();
- else
- sw->setAllChildrenOff();
- traverse(node, nv);
- }
-
-private:
- SGSharedPtr<SGCondition const> _condition;
-};
-
SGSelectAnimation::SGSelectAnimation(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) :
SGAnimation(configNode, modelRoot)
// when the animation installer returns
if (!condition)
return new osg::Group;
-
- osg::Switch* sw = new osg::Switch;
- sw->setName("select animation node");
- sw->setUpdateCallback(new UpdateCallback(condition));
- parent.addChild(sw);
- return sw;
+ simgear::ConditionNode* cn = new simgear::ConditionNode;
+ cn->setName("select animation node");
+ cn->setCondition(condition.ptr());
+ osg::Group* grp = new osg::Group;
+ cn->addChild(grp);
+ parent.addChild(cn);
+ return grp;
}
virtual bool buttonPressed(int button, const Info&)
{
bool found = false;
- for( std::vector<int>::iterator it = _buttons.begin(); it != _buttons.end(); it++ ) {
+ for( std::vector<int>::iterator it = _buttons.begin(); it != _buttons.end(); ++it ) {
if( *it == button ) {
found = true;
break;
SGBindingList::const_iterator i;
for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
(*i)->fire();
- _repeatTime = 0;
+ _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
return true;
}
virtual void buttonReleased(void)
double _repeatTime;
};
+class VncVisitor : public osg::NodeVisitor {
+ public:
+ VncVisitor(double x, double y, int mask) :
+ osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+ _texX(x), _texY(y), _mask(mask), _done(false)
+ {
+ SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor constructor "
+ << x << "," << y << " mask " << mask);
+ }
+
+ virtual void apply(osg::Node &node)
+ {
+ // Some nodes have state sets attached
+ touchStateSet(node.getStateSet());
+ if (!_done)
+ traverse(node);
+ if (_done) return;
+ // See whether we are a geode worth exploring
+ osg::Geode *g = dynamic_cast<osg::Geode*>(&node);
+ if (!g) return;
+ // Go find all its drawables
+ int i = g->getNumDrawables();
+ while (--i >= 0) {
+ osg::Drawable *d = g->getDrawable(i);
+ if (d) touchDrawable(*d);
+ }
+ // Out of optimism, do the same for EffectGeode
+ simgear::EffectGeode *eg = dynamic_cast<simgear::EffectGeode*>(&node);
+ if (!eg) return;
+ for (simgear::EffectGeode::DrawablesIterator di = eg->drawablesBegin();
+ di != eg->drawablesEnd(); di++) {
+ touchDrawable(**di);
+ }
+ // Now see whether the EffectGeode has an Effect
+ simgear::Effect *e = eg->getEffect();
+ if (e) {
+ touchStateSet(e->getDefaultStateSet());
+ }
+ }
+
+ inline void touchDrawable(osg::Drawable &d)
+ {
+ osg::StateSet *ss = d.getStateSet();
+ touchStateSet(ss);
+ }
+
+ void touchStateSet(osg::StateSet *ss)
+ {
+ if (!ss) return;
+ osg::StateAttribute *sa = ss->getTextureAttribute(0,
+ osg::StateAttribute::TEXTURE);
+ if (!sa) return;
+ osg::Texture *t = sa->asTexture();
+ if (!t) return;
+ osg::Image *img = t->getImage(0);
+ if (!img) return;
+ if (!_done) {
+ int pixX = _texX * img->s();
+ int pixY = _texY * img->t();
+ _done = img->sendPointerEvent(pixX, pixY, _mask);
+ SG_LOG(SG_INPUT, SG_DEBUG, "VncVisitor image said " << _done
+ << " to coord " << pixX << "," << pixY);
+ }
+ }
+
+ inline bool wasSuccessful()
+ {
+ return _done;
+ }
+
+ private:
+ double _texX, _texY;
+ int _mask;
+ bool _done;
+};
+
+
+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 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(void)
+ {
+ SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
+ VncVisitor vv(_x, _y, 0);
+ _node->accept(vv);
+ }
+ virtual void update(double dt)
+ {
+ }
+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
+{
+Mutex colorModeUniformMutex;
+osg::ref_ptr<osg::Uniform> colorModeUniform;
+}
+
osg::Group*
SGPickAnimation::createAnimationGroup(osg::Group& parent)
{
highlightGroup->addChild(commonGroup);
SGSceneUserData* ud;
ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
+
+ // 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)
ud->addPickCallback(new PickCallback(actions[i], 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));
// 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();
- stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
-
+ osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
+ stateSet->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, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
-
+ stateSet->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);
-
osg::Material* material = new osg::Material;
material->setColorMode(osg::Material::OFF);
- material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
- material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
+ material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
+ // XXX Alpha < 1.0 in the diffuse material value is a signal to the
+ // default shader to take the alpha value from the material value
+ // and not the glColor. In many cases the pick animation geometry is
+ // transparent, so the outline would not be visible without this hack.
+ 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(material, osg::StateAttribute::OVERRIDE);
-
+ stateSet->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::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
// Only add normal geometry if configured
if (getConfig()->getBoolValue("visible", true))
parent.addChild(normalGroup.get());