-// animation.hxx - classes to manage model animation.
+// animation.cxx - classes to manage model animation.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain, and comes with no warranty.
+#ifdef HAVE_CONFIG_H
+# include <simgear_config.h>
+#endif
#include <string.h> // for strcmp()
+#include <math.h>
+#include <algorithm>
+#include <functional>
+
+#include <OpenThreads/Mutex>
+#include <OpenThreads/ReentrantMutex>
+#include <OpenThreads/ScopedLock>
+
+#include <osg/AlphaFunc>
+#include <osg/Drawable>
+#include <osg/Geode>
+#include <osg/Geometry>
+#include <osg/LOD>
+#include <osg/Math>
+#include <osg/PolygonMode>
+#include <osg/PolygonOffset>
+#include <osg/StateSet>
+#include <osg/Switch>
+#include <osg/TexMat>
+#include <osg/Texture2D>
+#include <osg/Transform>
+#include <osgDB/ReadFile>
+#include <osgDB/Registry>
+#include <osgDB/Input>
+#include <osgDB/ParameterOutput>
-#include <plib/sg.h>
-#include <plib/ssg.h>
-#include <plib/ul.h>
#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 "animation.hxx"
+#include "model.hxx"
+
+#include "SGTranslateTransform.hxx"
+#include "SGMaterialAnimation.hxx"
+#include "SGRotateTransform.hxx"
+#include "SGScaleTransform.hxx"
+#include "SGInteractionAnimation.hxx"
+
+#include "ConditionNode.hxx"
+using OpenThreads::Mutex;
+using OpenThreads::ReentrantMutex;
+using OpenThreads::ScopedLock;
\f
////////////////////////////////////////////////////////////////////////
* Set up the transform matrix for a spin or rotation.
*/
static void
-set_rotation (sgMat4 &matrix, double position_deg,
- sgVec3 ¢er, sgVec3 &axis)
-{
- float temp_angle = -position_deg * SG_DEGREES_TO_RADIANS ;
-
- float s = (float) sin ( temp_angle ) ;
- float c = (float) cos ( temp_angle ) ;
- float t = SG_ONE - c ;
-
- // axis was normalized at load time
- // hint to the compiler to put these into FP registers
- float x = axis[0];
- float y = axis[1];
- float z = axis[2];
-
- matrix[0][0] = t * x * x + c ;
- matrix[0][1] = t * y * x - s * z ;
- matrix[0][2] = t * z * x + s * y ;
- matrix[0][3] = SG_ZERO;
-
- matrix[1][0] = t * x * y + s * z ;
- matrix[1][1] = t * y * y + c ;
- matrix[1][2] = t * z * y - s * x ;
- matrix[1][3] = SG_ZERO;
-
- matrix[2][0] = t * x * z - s * y ;
- matrix[2][1] = t * y * z + s * x ;
- matrix[2][2] = t * z * z + c ;
- matrix[2][3] = SG_ZERO;
-
+set_rotation (osg::Matrix &matrix, double position_deg,
+ const SGVec3d ¢er, const SGVec3d &axis)
+{
+ double temp_angle = -SGMiscd::deg2rad(position_deg);
+
+ double s = sin(temp_angle);
+ double c = cos(temp_angle);
+ double t = 1 - c;
+
+ // axis was normalized at load time
+ // hint to the compiler to put these into FP registers
+ double x = axis[0];
+ double y = axis[1];
+ double z = axis[2];
+
+ matrix(0, 0) = t * x * x + c ;
+ matrix(0, 1) = t * y * x - s * z ;
+ matrix(0, 2) = t * z * x + s * y ;
+ matrix(0, 3) = 0;
+
+ matrix(1, 0) = t * x * y + s * z ;
+ matrix(1, 1) = t * y * y + c ;
+ matrix(1, 2) = t * z * y - s * x ;
+ matrix(1, 3) = 0;
+
+ matrix(2, 0) = t * x * z - s * y ;
+ matrix(2, 1) = t * y * z + s * x ;
+ matrix(2, 2) = t * z * z + c ;
+ matrix(2, 3) = 0;
+
// hint to the compiler to put these into FP registers
- x = center[0];
- y = center[1];
- z = center[2];
-
- matrix[3][0] = x - x*matrix[0][0] - y*matrix[1][0] - z*matrix[2][0];
- matrix[3][1] = y - x*matrix[0][1] - y*matrix[1][1] - z*matrix[2][1];
- matrix[3][2] = z - x*matrix[0][2] - y*matrix[1][2] - z*matrix[2][2];
- matrix[3][3] = SG_ONE;
+ x = center[0];
+ y = center[1];
+ z = center[2];
+
+ matrix(3, 0) = x - x*matrix(0, 0) - y*matrix(1, 0) - z*matrix(2, 0);
+ matrix(3, 1) = y - x*matrix(0, 1) - y*matrix(1, 1) - z*matrix(2, 1);
+ matrix(3, 2) = z - x*matrix(0, 2) - y*matrix(1, 2) - z*matrix(2, 2);
+ matrix(3, 3) = 1;
}
/**
* Set up the transform matrix for a translation.
*/
static void
-set_translation (sgMat4 &matrix, double position_m, sgVec3 &axis)
+set_translation (osg::Matrix &matrix, double position_m, const SGVec3d &axis)
{
- sgVec3 xyz;
- sgScaleVec3(xyz, axis, position_m);
- sgMakeTransMat4(matrix, xyz);
+ SGVec3d xyz = axis * position_m;
+ matrix.makeIdentity();
+ matrix(3, 0) = xyz[0];
+ matrix(3, 1) = xyz[1];
+ matrix(3, 2) = xyz[2];
}
-
/**
* Read an interpolation table from properties.
*/
static SGInterpTable *
-read_interpolation_table (SGPropertyNode_ptr props)
-{
- SGPropertyNode_ptr table_node = props->getNode("interpolation");
- if (table_node != 0) {
- SGInterpTable * table = new SGInterpTable();
- vector<SGPropertyNode_ptr> entries = table_node->getChildren("entry");
- for (unsigned int i = 0; i < entries.size(); i++)
- table->addEntry(entries[i]->getDoubleValue("ind", 0.0),
- entries[i]->getDoubleValue("dep", 0.0));
- return table;
- } else {
+read_interpolation_table(const SGPropertyNode* props)
+{
+ const SGPropertyNode* table_node = props->getNode("interpolation");
+ if (!table_node)
return 0;
+ return new SGInterpTable(table_node);
+}
+
+static std::string
+unit_string(const char* value, const char* unit)
+{
+ return std::string(value) + unit;
+}
+
+class SGPersonalityScaleOffsetExpression : public SGUnaryExpression<double> {
+public:
+ SGPersonalityScaleOffsetExpression(SGExpression<double>* expr,
+ SGPropertyNode const* config,
+ const std::string& scalename,
+ const std::string& offsetname,
+ double defScale = 1,
+ double defOffset = 0) :
+ SGUnaryExpression<double>(expr),
+ _scale(config, scalename.c_str(), defScale),
+ _offset(config, offsetname.c_str(), defOffset)
+ { }
+ void setScale(double scale)
+ { _scale = scale; }
+ void setOffset(double offset)
+ { _offset = offset; }
+
+ virtual void eval(double& value, const simgear::expression::Binding* b) const
+ {
+ _offset.shuffle();
+ _scale.shuffle();
+ value = _offset + _scale*getOperand()->getValue(b);
}
+
+ virtual bool isConst() const { return false; }
+
+private:
+ mutable SGPersonalityParameter<double> _scale;
+ mutable SGPersonalityParameter<double> _offset;
+};
+
+
+static SGExpressiond*
+read_factor_offset(const SGPropertyNode* configNode, SGExpressiond* expr,
+ const std::string& factor, const std::string& offset)
+{
+ double factorValue = configNode->getDoubleValue(factor, 1);
+ if (factorValue != 1)
+ expr = new SGScaleExpression<double>(expr, factorValue);
+ double offsetValue = configNode->getDoubleValue(offset, 0);
+ if (offsetValue != 0)
+ expr = new SGBiasExpression<double>(expr, offsetValue);
+ return expr;
+}
+
+static SGExpressiond*
+read_offset_factor(const SGPropertyNode* configNode, SGExpressiond* expr,
+ const std::string& factor, const std::string& offset)
+{
+ double offsetValue = configNode->getDoubleValue(offset, 0);
+ if (offsetValue != 0)
+ expr = new SGBiasExpression<double>(expr, offsetValue);
+ double factorValue = configNode->getDoubleValue(factor, 1);
+ if (factorValue != 1)
+ expr = new SGScaleExpression<double>(expr, factorValue);
+ return expr;
}
+SGExpressiond*
+read_value(const SGPropertyNode* configNode, SGPropertyNode* modelRoot,
+ const char* unit, double defMin, double defMax)
+{
+ SGExpression<double>* value = 0;
+
+ std::string inputPropertyName = configNode->getStringValue("property", "");
+ if (inputPropertyName.empty()) {
+ std::string spos = unit_string("starting-position", unit);
+ double initPos = configNode->getDoubleValue(spos, 0);
+ value = new SGConstExpression<double>(initPos);
+ } else {
+ SGPropertyNode* inputProperty;
+ inputProperty = modelRoot->getNode(inputPropertyName, true);
+ value = new SGPropertyExpression<double>(inputProperty);
+ }
+
+ SGInterpTable* interpTable = read_interpolation_table(configNode);
+ if (interpTable) {
+ return new SGInterpTableExpression<double>(value, interpTable);
+ } else {
+ std::string offset = unit_string("offset", unit);
+ std::string min = unit_string("min", unit);
+ std::string max = unit_string("max", unit);
+
+ if (configNode->getBoolValue("use-personality", false)) {
+ value = new SGPersonalityScaleOffsetExpression(value, configNode,
+ "factor", offset);
+ } else {
+ value = read_factor_offset(configNode, value, "factor", offset);
+ }
+
+ double minClip = configNode->getDoubleValue(min, defMin);
+ double maxClip = configNode->getDoubleValue(max, defMax);
+ if (minClip > SGMiscd::min(SGLimitsd::min(), -SGLimitsd::max()) ||
+ maxClip < SGLimitsd::max())
+ value = new SGClipExpression<double>(value, minClip, maxClip);
+
+ return value;
+ }
+ return 0;
+}
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of SGAnimation
+// Animation installer
////////////////////////////////////////////////////////////////////////
-// Initialize the static data member
-double SGAnimation::sim_time_sec = 0.0;
+class SGAnimation::RemoveModeVisitor : public SGStateAttributeVisitor {
+public:
+ RemoveModeVisitor(osg::StateAttribute::GLMode mode) :
+ _mode(mode)
+ { }
+ virtual void apply(osg::StateSet* stateSet)
+ {
+ if (!stateSet)
+ return;
+ stateSet->removeMode(_mode);
+ }
+private:
+ osg::StateAttribute::GLMode _mode;
+};
+
+class SGAnimation::RemoveAttributeVisitor : public SGStateAttributeVisitor {
+public:
+ RemoveAttributeVisitor(osg::StateAttribute::Type type) :
+ _type(type)
+ { }
+ virtual void apply(osg::StateSet* stateSet)
+ {
+ if (!stateSet)
+ return;
+ while (stateSet->getAttribute(_type)) {
+ stateSet->removeAttribute(_type);
+ }
+ }
+private:
+ osg::StateAttribute::Type _type;
+};
+
+class SGAnimation::RemoveTextureModeVisitor : public SGStateAttributeVisitor {
+public:
+ RemoveTextureModeVisitor(unsigned unit, osg::StateAttribute::GLMode mode) :
+ _unit(unit),
+ _mode(mode)
+ { }
+ virtual void apply(osg::StateSet* stateSet)
+ {
+ if (!stateSet)
+ return;
+ stateSet->removeTextureMode(_unit, _mode);
+ }
+private:
+ unsigned _unit;
+ osg::StateAttribute::GLMode _mode;
+};
+
+class SGAnimation::RemoveTextureAttributeVisitor :
+ public SGStateAttributeVisitor {
+public:
+ RemoveTextureAttributeVisitor(unsigned unit,
+ osg::StateAttribute::Type type) :
+ _unit(unit),
+ _type(type)
+ { }
+ virtual void apply(osg::StateSet* stateSet)
+ {
+ if (!stateSet)
+ return;
+ while (stateSet->getTextureAttribute(_unit, _type)) {
+ stateSet->removeTextureAttribute(_unit, _type);
+ }
+ }
+private:
+ unsigned _unit;
+ osg::StateAttribute::Type _type;
+};
+
+class SGAnimation::BinToInheritVisitor : public SGStateAttributeVisitor {
+public:
+ virtual void apply(osg::StateSet* stateSet)
+ {
+ if (!stateSet)
+ return;
+ stateSet->setRenderBinToInherit();
+ }
+};
+
+class SGAnimation::DrawableCloneVisitor : public osg::NodeVisitor {
+public:
+ DrawableCloneVisitor() :
+ osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
+ {}
+ void apply(osg::Geode& geode)
+ {
+ for (unsigned i = 0 ; i < geode.getNumDrawables(); ++i) {
+ osg::CopyOp copyOp(osg::CopyOp::DEEP_COPY_ALL &
+ ~osg::CopyOp::DEEP_COPY_TEXTURES);
+ geode.setDrawable(i, copyOp(geode.getDrawable(i)));
+ }
+ }
+};
+
+namespace
+{
+// Set all drawables to not use display lists. OSG will use
+// glDrawArrays instead.
+struct DoDrawArraysVisitor : public osg::NodeVisitor {
+ DoDrawArraysVisitor() :
+ osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
+ {}
+ void apply(osg::Geode& geode)
+ {
+ using namespace osg;
+ using namespace std;
+
+ for (int i = 0; i < (int)geode.getNumDrawables(); ++i)
+ geode.getDrawable(i)->setUseDisplayList(false);
+ }
+};
+}
+
+SGAnimation::SGAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+ _found(false),
+ _configNode(configNode),
+ _modelRoot(modelRoot)
+{
+ _name = configNode->getStringValue("name", "");
+ _enableHOT = configNode->getBoolValue("enable-hot", true);
+ _disableShadow = configNode->getBoolValue("disable-shadow", false);
+ std::vector<SGPropertyNode_ptr> objectNames =
+ configNode->getChildren("object-name");
+ for (unsigned i = 0; i < objectNames.size(); ++i)
+ _objectNames.push_back(objectNames[i]->getStringValue());
+}
+
+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");
+}
+
+bool
+SGAnimation::animate(osg::Node* node, const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot,
+ const osgDB::ReaderWriter::Options* options)
+{
+ std::string type = configNode->getStringValue("type", "none");
+ if (type == "alpha-test") {
+ SGAlphaTestAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "billboard") {
+ SGBillboardAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "blend") {
+ SGBlendAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "dist-scale") {
+ SGDistScaleAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "flash") {
+ SGFlashAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "interaction") {
+ SGInteractionAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "material") {
+ SGMaterialAnimation animInst(configNode, modelRoot, options);
+ animInst.apply(node);
+ } else if (type == "noshadow") {
+ SGShadowAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "pick") {
+ SGPickAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "range") {
+ SGRangeAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "rotate" || type == "spin") {
+ SGRotateAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "scale") {
+ SGScaleAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "select") {
+ SGSelectAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "shader") {
+ SGShaderAnimation animInst(configNode, modelRoot, options);
+ animInst.apply(node);
+ } else if (type == "textranslate" || type == "texrotate" ||
+ type == "texmultiple") {
+ SGTexTransformAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "timed") {
+ SGTimedAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "translate") {
+ SGTranslateAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else if (type == "null" || type == "none" || type.empty()) {
+ SGGroupAnimation animInst(configNode, modelRoot);
+ animInst.apply(node);
+ } else
+ return false;
+
+ return true;
+}
+
+
+void
+SGAnimation::apply(osg::Node* node)
+{
+ // duh what a special case ...
+ if (_objectNames.empty()) {
+ osg::Group* group = node->asGroup();
+ if (group) {
+ osg::ref_ptr<osg::Group> animationGroup;
+ installInGroup(std::string(), *group, animationGroup);
+ }
+ } else
+ node->accept(*this);
+}
+
+void
+SGAnimation::install(osg::Node& node)
+{
+ _found = true;
+ if (_enableHOT)
+ node.setNodeMask( SG_NODEMASK_TERRAIN_BIT | node.getNodeMask());
+ else
+ node.setNodeMask(~SG_NODEMASK_TERRAIN_BIT & node.getNodeMask());
+ if (!_disableShadow)
+ node.setNodeMask( SG_NODEMASK_CASTSHADOW_BIT | node.getNodeMask());
+ else
+ node.setNodeMask(~SG_NODEMASK_CASTSHADOW_BIT & node.getNodeMask());
+}
+
+osg::Group*
+SGAnimation::createAnimationGroup(osg::Group& parent)
+{
+ // default implementation, we do not need a new group
+ // for every animation type. Usually animations that just change
+ // the StateSet of some parts of the model
+ return 0;
+}
+
+void
+SGAnimation::apply(osg::Group& group)
+{
+ // the trick is to first traverse the children and then
+ // possibly splice in a new group node if required.
+ // Else we end up in a recursive loop where we infinitly insert new
+ // groups in between
+ traverse(group);
+
+ // Note that this algorithm preserves the order of the child objects
+ // like they appear in the object-name tags.
+ // The timed animations require this
+ osg::ref_ptr<osg::Group> animationGroup;
+ std::list<std::string>::const_iterator nameIt;
+ for (nameIt = _objectNames.begin(); nameIt != _objectNames.end(); ++nameIt)
+ installInGroup(*nameIt, group, animationGroup);
+}
+
+void
+SGAnimation::installInGroup(const std::string& name, osg::Group& group,
+ osg::ref_ptr<osg::Group>& animationGroup)
+{
+ int i = group.getNumChildren() - 1;
+ for (; 0 <= i; --i) {
+ osg::Node* child = group.getChild(i);
+
+ // Check if this one is already processed
+ if (std::find(_installedAnimations.begin(),
+ _installedAnimations.end(), child)
+ != _installedAnimations.end())
+ continue;
+
+ if (name.empty() || child->getName() == name) {
+ // fire the installation of the animation
+ install(*child);
+
+ // create a group node on demand
+ if (!animationGroup.valid()) {
+ animationGroup = createAnimationGroup(group);
+ // Animation type that does not require a new group,
+ // in this case we can stop and look for the next object
+ if (animationGroup.valid() && !_name.empty())
+ animationGroup->setName(_name);
+ }
+ if (animationGroup.valid()) {
+ animationGroup->addChild(child);
+ group.removeChild(i);
+ }
+
+ // store that we already have processed this child node
+ // We can hit this one twice if an animation references some
+ // part of a subtree twice
+ _installedAnimations.push_back(child);
+ }
+ }
+}
+
+void
+SGAnimation::removeMode(osg::Node& node, osg::StateAttribute::GLMode mode)
+{
+ RemoveModeVisitor visitor(mode);
+ node.accept(visitor);
+}
+
+void
+SGAnimation::removeAttribute(osg::Node& node, osg::StateAttribute::Type type)
+{
+ RemoveAttributeVisitor visitor(type);
+ node.accept(visitor);
+}
-SGAnimation::SGAnimation (SGPropertyNode_ptr props, ssgBranch * branch)
- : _branch(branch)
+void
+SGAnimation::removeTextureMode(osg::Node& node, unsigned unit,
+ osg::StateAttribute::GLMode mode)
{
- _branch->setName(props->getStringValue("name", 0));
+ RemoveTextureModeVisitor visitor(unit, mode);
+ node.accept(visitor);
}
-SGAnimation::~SGAnimation ()
+void
+SGAnimation::removeTextureAttribute(osg::Node& node, unsigned unit,
+ osg::StateAttribute::Type type)
{
+ RemoveTextureAttributeVisitor visitor(unit, type);
+ node.accept(visitor);
}
void
-SGAnimation::init ()
+SGAnimation::setRenderBinToInherit(osg::Node& node)
{
+ BinToInheritVisitor visitor;
+ node.accept(visitor);
}
void
-SGAnimation::update()
+SGAnimation::cloneDrawables(osg::Node& node)
{
+ DrawableCloneVisitor visitor;
+ node.accept(visitor);
+}
+
+const SGCondition*
+SGAnimation::getCondition() const
+{
+ const SGPropertyNode* conditionNode = _configNode->getChild("condition");
+ if (!conditionNode)
+ return 0;
+ return sgReadCondition(_modelRoot, conditionNode);
}
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of SGNullAnimation
+// Implementation of null animation
////////////////////////////////////////////////////////////////////////
-SGNullAnimation::SGNullAnimation (SGPropertyNode_ptr props)
- : SGAnimation(props, new ssgBranch)
+// Ok, that is to build a subgraph from different other
+// graph nodes. I guess that this stems from the time where modellers
+// could not build hierarchical trees ...
+SGGroupAnimation::SGGroupAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot):
+ SGAnimation(configNode, modelRoot)
{
}
-SGNullAnimation::~SGNullAnimation ()
+osg::Group*
+SGGroupAnimation::createAnimationGroup(osg::Group& parent)
{
+ osg::Group* group = new osg::Group;
+ parent.addChild(group);
+ return group;
}
-
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of SGRangeAnimation
+// Implementation of translate animation
////////////////////////////////////////////////////////////////////////
-SGRangeAnimation::SGRangeAnimation (SGPropertyNode_ptr props)
- : SGAnimation(props, new ssgRangeSelector)
+class SGTranslateAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+ UpdateCallback(SGCondition const* condition,
+ SGExpressiond const* animationValue) :
+ _condition(condition),
+ _animationValue(animationValue)
+ { }
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ if (!_condition || _condition->test()) {
+ SGTranslateTransform* transform;
+ transform = static_cast<SGTranslateTransform*>(node);
+ transform->setValue(_animationValue->getValue());
+ }
+ traverse(node, nv);
+ }
+public:
+ SGSharedPtr<SGCondition const> _condition;
+ SGSharedPtr<SGExpressiond const> _animationValue;
+};
+
+SGTranslateAnimation::SGTranslateAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
{
- float ranges[] = { props->getFloatValue("min-m", 0),
- props->getFloatValue("max-m", 5000) };
- ((ssgRangeSelector *)_branch)->setRanges(ranges, 2);
-
+ _condition = getCondition();
+ SGSharedPtr<SGExpressiond> value;
+ value = read_value(configNode, modelRoot, "-m",
+ -SGLimitsd::max(), SGLimitsd::max());
+ _animationValue = value->simplify();
+ if (_animationValue)
+ _initialValue = _animationValue->getValue();
+ else
+ _initialValue = 0;
+
+ if (configNode->hasValue("axis/x1-m")) {
+ SGVec3d v1, v2;
+ v1[0] = configNode->getDoubleValue("axis/x1-m", 0);
+ v1[1] = configNode->getDoubleValue("axis/y1-m", 0);
+ v1[2] = configNode->getDoubleValue("axis/z1-m", 0);
+ v2[0] = configNode->getDoubleValue("axis/x2-m", 0);
+ v2[1] = configNode->getDoubleValue("axis/y2-m", 0);
+ v2[2] = configNode->getDoubleValue("axis/z2-m", 0);
+ _axis = v2 - v1;
+ } else {
+ _axis[0] = configNode->getDoubleValue("axis/x", 0);
+ _axis[1] = configNode->getDoubleValue("axis/y", 0);
+ _axis[2] = configNode->getDoubleValue("axis/z", 0);
+ }
+ if (8*SGLimitsd::min() < norm(_axis))
+ _axis = normalize(_axis);
}
-SGRangeAnimation::~SGRangeAnimation ()
+osg::Group*
+SGTranslateAnimation::createAnimationGroup(osg::Group& parent)
{
+ SGTranslateTransform* transform = new SGTranslateTransform;
+ transform->setName("translate animation");
+ if (_animationValue && !_animationValue->isConst()) {
+ UpdateCallback* uc = new UpdateCallback(_condition, _animationValue);
+ transform->setUpdateCallback(uc);
+ }
+ transform->setAxis(_axis);
+ transform->setValue(_initialValue);
+ parent.addChild(transform);
+ return transform;
}
-
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of SGBillboardAnimation
+// Implementation of rotate/spin animation
////////////////////////////////////////////////////////////////////////
-SGBillboardAnimation::SGBillboardAnimation (SGPropertyNode_ptr props)
- : SGAnimation(props, new ssgCutout(props->getBoolValue("spherical", true)))
+class SGRotateAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+ UpdateCallback(SGCondition const* condition,
+ SGExpressiond const* animationValue) :
+ _condition(condition),
+ _animationValue(animationValue)
+ { }
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ if (!_condition || _condition->test()) {
+ SGRotateTransform* transform;
+ transform = static_cast<SGRotateTransform*>(node);
+ transform->setAngleDeg(_animationValue->getValue());
+ }
+ traverse(node, nv);
+ }
+public:
+ SGSharedPtr<SGCondition const> _condition;
+ SGSharedPtr<SGExpressiond const> _animationValue;
+};
+
+class SGRotateAnimation::SpinUpdateCallback : public osg::NodeCallback {
+public:
+ SpinUpdateCallback(SGCondition const* condition,
+ SGExpressiond const* animationValue) :
+ _condition(condition),
+ _animationValue(animationValue),
+ _lastTime(-1)
+ { }
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ if (!_condition || _condition->test()) {
+ SGRotateTransform* transform;
+ transform = static_cast<SGRotateTransform*>(node);
+
+ double t = nv->getFrameStamp()->getReferenceTime();
+ double dt = 0;
+ if (0 <= _lastTime)
+ dt = t - _lastTime;
+ _lastTime = t;
+ double velocity_rpms = _animationValue->getValue()/60;
+ double angle = transform->getAngleDeg();
+ angle += dt*velocity_rpms*360;
+ angle -= 360*floor(angle/360);
+ transform->setAngleDeg(angle);
+ }
+ traverse(node, nv);
+ }
+public:
+ SGSharedPtr<SGCondition const> _condition;
+ SGSharedPtr<SGExpressiond const> _animationValue;
+ double _lastTime;
+};
+
+SGRotateAnimation::SGRotateAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
{
+ std::string type = configNode->getStringValue("type", "");
+ _isSpin = (type == "spin");
+
+ _condition = getCondition();
+ SGSharedPtr<SGExpressiond> value;
+ value = read_value(configNode, modelRoot, "-deg",
+ -SGLimitsd::max(), SGLimitsd::max());
+ _animationValue = value->simplify();
+ if (_animationValue)
+ _initialValue = _animationValue->getValue();
+ else
+ _initialValue = 0;
+
+ _center = SGVec3d::zeros();
+ if (configNode->hasValue("axis/x1-m")) {
+ SGVec3d v1, v2;
+ v1[0] = configNode->getDoubleValue("axis/x1-m", 0);
+ v1[1] = configNode->getDoubleValue("axis/y1-m", 0);
+ v1[2] = configNode->getDoubleValue("axis/z1-m", 0);
+ v2[0] = configNode->getDoubleValue("axis/x2-m", 0);
+ v2[1] = configNode->getDoubleValue("axis/y2-m", 0);
+ v2[2] = configNode->getDoubleValue("axis/z2-m", 0);
+ _center = 0.5*(v1+v2);
+ _axis = v2 - v1;
+ } else {
+ _axis[0] = configNode->getDoubleValue("axis/x", 0);
+ _axis[1] = configNode->getDoubleValue("axis/y", 0);
+ _axis[2] = configNode->getDoubleValue("axis/z", 0);
+ }
+ if (8*SGLimitsd::min() < norm(_axis))
+ _axis = normalize(_axis);
+
+ _center[0] = configNode->getDoubleValue("center/x-m", _center[0]);
+ _center[1] = configNode->getDoubleValue("center/y-m", _center[1]);
+ _center[2] = configNode->getDoubleValue("center/z-m", _center[2]);
}
-SGBillboardAnimation::~SGBillboardAnimation ()
+osg::Group*
+SGRotateAnimation::createAnimationGroup(osg::Group& parent)
{
+ SGRotateTransform* transform = new SGRotateTransform;
+ transform->setName("rotate animation");
+ if (_isSpin) {
+ SpinUpdateCallback* uc;
+ uc = new SpinUpdateCallback(_condition, _animationValue);
+ transform->setUpdateCallback(uc);
+ } else if (_animationValue || !_animationValue->isConst()) {
+ UpdateCallback* uc = new UpdateCallback(_condition, _animationValue);
+ transform->setUpdateCallback(uc);
+ }
+ transform->setCenter(_center);
+ transform->setAxis(_axis);
+ transform->setAngleDeg(_initialValue);
+ parent.addChild(transform);
+ return transform;
}
-
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of SGSelectAnimation
+// Implementation of scale animation
////////////////////////////////////////////////////////////////////////
-SGSelectAnimation::SGSelectAnimation( SGPropertyNode *prop_root,
- SGPropertyNode_ptr props )
- : SGAnimation(props, new ssgSelector),
- _condition(0)
+class SGScaleAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+ UpdateCallback(const SGCondition* condition,
+ SGSharedPtr<const SGExpressiond> animationValue[3]) :
+ _condition(condition)
+ {
+ _animationValue[0] = animationValue[0];
+ _animationValue[1] = animationValue[1];
+ _animationValue[2] = animationValue[2];
+ }
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ if (!_condition || _condition->test()) {
+ SGScaleTransform* transform;
+ transform = static_cast<SGScaleTransform*>(node);
+ SGVec3d scale(_animationValue[0]->getValue(),
+ _animationValue[1]->getValue(),
+ _animationValue[2]->getValue());
+ transform->setScaleFactor(scale);
+ }
+ traverse(node, nv);
+ }
+public:
+ SGSharedPtr<SGCondition const> _condition;
+ SGSharedPtr<SGExpressiond const> _animationValue[3];
+};
+
+SGScaleAnimation::SGScaleAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
{
- SGPropertyNode_ptr node = props->getChild("condition");
- if (node != 0)
- _condition = sgReadCondition(prop_root, node);
+ _condition = getCondition();
+
+ // default offset/factor for all directions
+ double offset = configNode->getDoubleValue("offset", 0);
+ double factor = configNode->getDoubleValue("factor", 1);
+
+ SGSharedPtr<SGExpressiond> inPropExpr;
+
+ std::string inputPropertyName;
+ inputPropertyName = configNode->getStringValue("property", "");
+ if (inputPropertyName.empty()) {
+ inPropExpr = new SGConstExpression<double>(0);
+ } else {
+ SGPropertyNode* inputProperty;
+ inputProperty = modelRoot->getNode(inputPropertyName, true);
+ inPropExpr = new SGPropertyExpression<double>(inputProperty);
+ }
+
+ SGInterpTable* interpTable = read_interpolation_table(configNode);
+ if (interpTable) {
+ SGSharedPtr<SGExpressiond> value;
+ value = new SGInterpTableExpression<double>(inPropExpr, interpTable);
+ _animationValue[0] = value->simplify();
+ _animationValue[1] = value->simplify();
+ _animationValue[2] = value->simplify();
+ } else if (configNode->getBoolValue("use-personality", false)) {
+ SGSharedPtr<SGExpressiond> value;
+ value = new SGPersonalityScaleOffsetExpression(inPropExpr, configNode,
+ "x-factor", "x-offset",
+ factor, offset);
+ double minClip = configNode->getDoubleValue("x-min", 0);
+ double maxClip = configNode->getDoubleValue("x-max", SGLimitsd::max());
+ value = new SGClipExpression<double>(value, minClip, maxClip);
+ _animationValue[0] = value->simplify();
+
+ value = new SGPersonalityScaleOffsetExpression(inPropExpr, configNode,
+ "y-factor", "y-offset",
+ factor, offset);
+ minClip = configNode->getDoubleValue("y-min", 0);
+ maxClip = configNode->getDoubleValue("y-max", SGLimitsd::max());
+ value = new SGClipExpression<double>(value, minClip, maxClip);
+ _animationValue[1] = value->simplify();
+
+ value = new SGPersonalityScaleOffsetExpression(inPropExpr, configNode,
+ "z-factor", "z-offset",
+ factor, offset);
+ minClip = configNode->getDoubleValue("z-min", 0);
+ maxClip = configNode->getDoubleValue("z-max", SGLimitsd::max());
+ value = new SGClipExpression<double>(value, minClip, maxClip);
+ _animationValue[2] = value->simplify();
+ } else {
+ SGSharedPtr<SGExpressiond> value;
+ value = read_factor_offset(configNode, inPropExpr, "x-factor", "x-offset");
+ double minClip = configNode->getDoubleValue("x-min", 0);
+ double maxClip = configNode->getDoubleValue("x-max", SGLimitsd::max());
+ value = new SGClipExpression<double>(value, minClip, maxClip);
+ _animationValue[0] = value->simplify();
+
+ value = read_factor_offset(configNode, inPropExpr, "y-factor", "y-offset");
+ minClip = configNode->getDoubleValue("y-min", 0);
+ maxClip = configNode->getDoubleValue("y-max", SGLimitsd::max());
+ value = new SGClipExpression<double>(value, minClip, maxClip);
+ _animationValue[1] = value->simplify();
+
+ value = read_factor_offset(configNode, inPropExpr, "z-factor", "z-offset");
+ minClip = configNode->getDoubleValue("z-min", 0);
+ maxClip = configNode->getDoubleValue("z-max", SGLimitsd::max());
+ value = new SGClipExpression<double>(value, minClip, maxClip);
+ _animationValue[2] = value->simplify();
+ }
+ _initialValue[0] = configNode->getDoubleValue("x-starting-scale", 1);
+ _initialValue[0] *= configNode->getDoubleValue("x-factor", factor);
+ _initialValue[0] += configNode->getDoubleValue("x-offset", offset);
+ _initialValue[1] = configNode->getDoubleValue("y-starting-scale", 1);
+ _initialValue[1] *= configNode->getDoubleValue("y-factor", factor);
+ _initialValue[1] += configNode->getDoubleValue("y-offset", offset);
+ _initialValue[2] = configNode->getDoubleValue("z-starting-scale", 1);
+ _initialValue[2] *= configNode->getDoubleValue("z-factor", factor);
+ _initialValue[2] += configNode->getDoubleValue("z-offset", offset);
+ _center[0] = configNode->getDoubleValue("center/x-m", 0);
+ _center[1] = configNode->getDoubleValue("center/y-m", 0);
+ _center[2] = configNode->getDoubleValue("center/z-m", 0);
}
-SGSelectAnimation::~SGSelectAnimation ()
+osg::Group*
+SGScaleAnimation::createAnimationGroup(osg::Group& parent)
{
- delete _condition;
+ SGScaleTransform* transform = new SGScaleTransform;
+ transform->setName("scale animation");
+ transform->setCenter(_center);
+ transform->setScaleFactor(_initialValue);
+ UpdateCallback* uc = new UpdateCallback(_condition, _animationValue);
+ transform->setUpdateCallback(uc);
+ parent.addChild(transform);
+ return transform;
}
-void
-SGSelectAnimation::update()
+\f
+// Don't create a new state state everytime we need GL_NORMALIZE!
+
+namespace
+{
+Mutex normalizeMutex;
+
+osg::StateSet* getNormalizeStateSet()
+{
+ static osg::ref_ptr<osg::StateSet> normalizeStateSet;
+ ScopedLock<Mutex> lock(normalizeMutex);
+ if (!normalizeStateSet.valid()) {
+ normalizeStateSet = new osg::StateSet;
+ normalizeStateSet->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
+ normalizeStateSet->setDataVariance(osg::Object::STATIC);
+ }
+ return normalizeStateSet.get();
+}
+}
+
+////////////////////////////////////////////////////////////////////////
+// Implementation of dist scale animation
+////////////////////////////////////////////////////////////////////////
+
+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"));
+ setReferenceFrame(RELATIVE_RF);
+ setStateSet(getNormalizeStateSet());
+ _factor = configNode->getFloatValue("factor", 1);
+ _offset = configNode->getFloatValue("offset", 0);
+ _min_v = configNode->getFloatValue("min", SGLimitsf::epsilon());
+ _max_v = configNode->getFloatValue("max", SGLimitsf::max());
+ _table = read_interpolation_table(configNode);
+ _center[0] = configNode->getFloatValue("center/x-m", 0);
+ _center[1] = configNode->getFloatValue("center/y-m", 0);
+ _center[2] = configNode->getFloatValue("center/z-m", 0);
+ }
+ virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,
+ osg::NodeVisitor* nv) const
+ {
+ osg::Matrix transform;
+ double scale_factor = computeScaleFactor(nv);
+ transform(0,0) = scale_factor;
+ transform(1,1) = scale_factor;
+ transform(2,2) = scale_factor;
+ transform(3,0) = _center[0]*(1 - scale_factor);
+ transform(3,1) = _center[1]*(1 - scale_factor);
+ transform(3,2) = _center[2]*(1 - scale_factor);
+ matrix.preMult(transform);
+ return true;
+ }
+
+ virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,
+ osg::NodeVisitor* nv) const
+ {
+ double scale_factor = computeScaleFactor(nv);
+ if (fabs(scale_factor) <= SGLimits<double>::min())
+ return false;
+ osg::Matrix transform;
+ double rScaleFactor = 1/scale_factor;
+ transform(0,0) = rScaleFactor;
+ transform(1,1) = rScaleFactor;
+ transform(2,2) = rScaleFactor;
+ transform(3,0) = _center[0]*(1 - rScaleFactor);
+ transform(3,1) = _center[1]*(1 - rScaleFactor);
+ transform(3,2) = _center[2]*(1 - rScaleFactor);
+ matrix.postMult(transform);
+ 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 = (toOsg(_center) - nv->getEyePoint()).length();
+ if (_table == 0) {
+ scale_factor = _factor * scale_factor + _offset;
+ } else {
+ scale_factor = _table->interpolate( scale_factor );
+ }
+ if (scale_factor < _min_v)
+ scale_factor = _min_v;
+ if (scale_factor > _max_v)
+ scale_factor = _max_v;
+
+ return scale_factor;
+ }
+
+ SGSharedPtr<SGInterpTable> _table;
+ SGVec3d _center;
+ double _min_v;
+ double _max_v;
+ double _factor;
+ double _offset;
+};
+
+
+SGDistScaleAnimation::SGDistScaleAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
{
- if (_condition != 0 && _condition->test())
- ((ssgSelector *)_branch)->select(0xffff);
- else
- ((ssgSelector *)_branch)->select(0x0000);
}
+osg::Group*
+SGDistScaleAnimation::createAnimationGroup(osg::Group& parent)
+{
+ Transform* transform = new Transform(getConfig());
+ parent.addChild(transform);
+ return transform;
+}
+namespace
+{
+ osgDB::RegisterDotOsgWrapperProxy distScaleAnimationTransformProxy
+ (
+ new SGDistScaleAnimation::Transform,
+ "SGDistScaleAnimation::Transform",
+ "Object Node Transform SGDistScaleAnimation::Transform Group",
+ 0,
+ &SGDistScaleAnimation::Transform::writeLocalData
+ );
+}
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of SGSpinAnimation
+// Implementation of flash animation
////////////////////////////////////////////////////////////////////////
-SGSpinAnimation::SGSpinAnimation( SGPropertyNode *prop_root,
- SGPropertyNode_ptr props,
- double sim_time_sec )
- : SGAnimation(props, new ssgTransform),
- _prop((SGPropertyNode *)prop_root->getNode(props->getStringValue("property", "/null"), true)),
- _factor(props->getDoubleValue("factor", 1.0)),
- _position_deg(props->getDoubleValue("starting-position-deg", 0)),
- _last_time_sec( sim_time_sec )
+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);
+ setName(configNode->getStringValue("name", "flash animation"));
+ setStateSet(getNormalizeStateSet());
+
+ _axis[0] = configNode->getFloatValue("axis/x", 0);
+ _axis[1] = configNode->getFloatValue("axis/y", 0);
+ _axis[2] = configNode->getFloatValue("axis/z", 1);
+ _axis.normalize();
+
+ _center[0] = configNode->getFloatValue("center/x-m", 0);
+ _center[1] = configNode->getFloatValue("center/y-m", 0);
+ _center[2] = configNode->getFloatValue("center/z-m", 0);
+
+ _offset = configNode->getFloatValue("offset", 0);
+ _factor = configNode->getFloatValue("factor", 1);
+ _power = configNode->getFloatValue("power", 1);
+ _two_sides = configNode->getBoolValue("two-sides", false);
+
+ _min_v = configNode->getFloatValue("min", SGLimitsf::epsilon());
+ _max_v = configNode->getFloatValue("max", 1);
+ }
+ virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,
+ osg::NodeVisitor* nv) const
+ {
+ osg::Matrix transform;
+ double scale_factor = computeScaleFactor(nv);
+ transform(0,0) = scale_factor;
+ transform(1,1) = scale_factor;
+ transform(2,2) = scale_factor;
+ transform(3,0) = _center[0]*(1 - scale_factor);
+ transform(3,1) = _center[1]*(1 - scale_factor);
+ transform(3,2) = _center[2]*(1 - scale_factor);
+ matrix.preMult(transform);
+ return true;
+ }
+
+ virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,
+ osg::NodeVisitor* nv) const
+ {
+ double scale_factor = computeScaleFactor(nv);
+ if (fabs(scale_factor) <= SGLimits<double>::min())
+ return false;
+ osg::Matrix transform;
+ double rScaleFactor = 1/scale_factor;
+ transform(0,0) = rScaleFactor;
+ transform(1,1) = rScaleFactor;
+ transform(2,2) = rScaleFactor;
+ transform(3,0) = _center[0]*(1 - rScaleFactor);
+ transform(3,1) = _center[1]*(1 - rScaleFactor);
+ transform(3,2) = _center[2]*(1 - rScaleFactor);
+ matrix.postMult(transform);
+ 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
+ {
+ if (!nv)
+ return 1;
+
+ osg::Vec3 localEyeToCenter = nv->getEyePoint() - _center;
+ localEyeToCenter.normalize();
+
+ double cos_angle = localEyeToCenter*_axis;
+ double scale_factor = 0;
+ if ( _two_sides && cos_angle < 0 )
+ scale_factor = _factor * pow( -cos_angle, _power ) + _offset;
+ else if ( cos_angle > 0 )
+ scale_factor = _factor * pow( cos_angle, _power ) + _offset;
+
+ if ( scale_factor < _min_v )
+ scale_factor = _min_v;
+ if ( scale_factor > _max_v )
+ scale_factor = _max_v;
+
+ return scale_factor;
+ }
+
+ virtual osg::BoundingSphere computeBound() const
+ {
+ // avoid being culled away by small feature culling
+ osg::BoundingSphere bs = osg::Group::computeBound();
+ bs.radius() *= _max_v;
+ return bs;
+ }
+
+private:
+ osg::Vec3 _center;
+ osg::Vec3 _axis;
+ double _power, _factor, _offset, _min_v, _max_v;
+ bool _two_sides;
+};
+
+
+SGFlashAnimation::SGFlashAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
{
- _center[0] = props->getFloatValue("center/x-m", 0);
- _center[1] = props->getFloatValue("center/y-m", 0);
- _center[2] = props->getFloatValue("center/z-m", 0);
- _axis[0] = props->getFloatValue("axis/x", 0);
- _axis[1] = props->getFloatValue("axis/y", 0);
- _axis[2] = props->getFloatValue("axis/z", 0);
- sgNormalizeVec3(_axis);
}
-SGSpinAnimation::~SGSpinAnimation ()
+osg::Group*
+SGFlashAnimation::createAnimationGroup(osg::Group& parent)
{
+ Transform* transform = new Transform(getConfig());
+ parent.addChild(transform);
+ return transform;
}
-void
-SGSpinAnimation::update()
+namespace
{
- double dt = sim_time_sec - _last_time_sec;
- _last_time_sec = sim_time_sec;
+ osgDB::RegisterDotOsgWrapperProxy flashAnimationTransformProxy
+ (
+ new SGFlashAnimation::Transform,
+ "SGFlashAnimation::Transform",
+ "Object Node Transform SGFlashAnimation::Transform Group",
+ 0,
+ &SGFlashAnimation::Transform::writeLocalData
+ );
+}
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of billboard animation
+////////////////////////////////////////////////////////////////////////
- float velocity_rpms = (_prop->getDoubleValue() * _factor / 60.0);
- _position_deg += (dt * velocity_rpms * 360);
- while (_position_deg < 0)
- _position_deg += 360.0;
- while (_position_deg >= 360.0)
- _position_deg -= 360.0;
- set_rotation(_matrix, _position_deg, _center, _axis);
- ((ssgTransform *)_branch)->setTransform(_matrix);
+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))
+ {
+ setReferenceFrame(RELATIVE_RF);
+ setName(configNode->getStringValue("name", "billboard animation"));
+ }
+ virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,
+ osg::NodeVisitor* nv) const
+ {
+ // More or less taken from plibs ssgCutout
+ if (_spherical) {
+ matrix(0,0) = 1; matrix(0,1) = 0; matrix(0,2) = 0;
+ matrix(1,0) = 0; matrix(1,1) = 0; matrix(1,2) = -1;
+ matrix(2,0) = 0; matrix(2,1) = 1; matrix(2,2) = 0;
+ } else {
+ osg::Vec3 zAxis(matrix(2, 0), matrix(2, 1), matrix(2, 2));
+ osg::Vec3 xAxis = osg::Vec3(0, 0, -1)^zAxis;
+ osg::Vec3 yAxis = zAxis^xAxis;
+
+ xAxis.normalize();
+ yAxis.normalize();
+ zAxis.normalize();
+
+ matrix(0,0) = xAxis[0]; matrix(0,1) = xAxis[1]; matrix(0,2) = xAxis[2];
+ matrix(1,0) = yAxis[0]; matrix(1,1) = yAxis[1]; matrix(1,2) = yAxis[2];
+ matrix(2,0) = zAxis[0]; matrix(2,1) = zAxis[1]; matrix(2,2) = zAxis[2];
+ }
+ return true;
+ }
+
+ virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,
+ osg::NodeVisitor* nv) const
+ {
+ // 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;
+};
+
+
+SGBillboardAnimation::SGBillboardAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
+{
}
+osg::Group*
+SGBillboardAnimation::createAnimationGroup(osg::Group& parent)
+{
+ Transform* transform = new Transform(getConfig());
+ parent.addChild(transform);
+ return transform;
+}
+namespace
+{
+ osgDB::RegisterDotOsgWrapperProxy billboardAnimationTransformProxy
+ (
+ new SGBillboardAnimation::Transform,
+ "SGBillboardAnimation::Transform",
+ "Object Node Transform SGBillboardAnimation::Transform Group",
+ 0,
+ &SGBillboardAnimation::Transform::writeLocalData
+ );
+}
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of SGTimedAnimation
+// Implementation of a range animation
////////////////////////////////////////////////////////////////////////
-SGTimedAnimation::SGTimedAnimation (SGPropertyNode_ptr props)
- : SGAnimation(props, new ssgSelector),
- _duration_sec(props->getDoubleValue("duration-sec", 1.0)),
- _last_time_sec(0),
- _step(-1)
+class SGRangeAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+ UpdateCallback(const SGCondition* condition,
+ const SGExpressiond* minAnimationValue,
+ const SGExpressiond* maxAnimationValue,
+ double minValue, double maxValue) :
+ _condition(condition),
+ _minAnimationValue(minAnimationValue),
+ _maxAnimationValue(maxAnimationValue),
+ _minStaticValue(minValue),
+ _maxStaticValue(maxValue)
+ {}
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ osg::LOD* lod = static_cast<osg::LOD*>(node);
+ if (!_condition || _condition->test()) {
+ double minRange;
+ if (_minAnimationValue)
+ minRange = _minAnimationValue->getValue();
+ else
+ minRange = _minStaticValue;
+ double maxRange;
+ if (_maxAnimationValue)
+ maxRange = _maxAnimationValue->getValue();
+ else
+ maxRange = _maxStaticValue;
+ lod->setRange(0, minRange, maxRange);
+ } else {
+ lod->setRange(0, 0, SGLimitsf::max());
+ }
+ traverse(node, nv);
+ }
+
+private:
+ SGSharedPtr<const SGCondition> _condition;
+ SGSharedPtr<const SGExpressiond> _minAnimationValue;
+ SGSharedPtr<const SGExpressiond> _maxAnimationValue;
+ double _minStaticValue;
+ double _maxStaticValue;
+};
+
+SGRangeAnimation::SGRangeAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
{
+ _condition = getCondition();
+
+ std::string inputPropertyName;
+ inputPropertyName = configNode->getStringValue("min-property", "");
+ if (!inputPropertyName.empty()) {
+ SGPropertyNode* inputProperty;
+ inputProperty = modelRoot->getNode(inputPropertyName, true);
+ SGSharedPtr<SGExpressiond> value;
+ value = new SGPropertyExpression<double>(inputProperty);
+
+ value = read_factor_offset(configNode, value, "min-factor", "min-offset");
+ _minAnimationValue = value->simplify();
+ }
+ inputPropertyName = configNode->getStringValue("max-property", "");
+ if (!inputPropertyName.empty()) {
+ SGPropertyNode* inputProperty;
+ inputProperty = modelRoot->getNode(inputPropertyName.c_str(), true);
+
+ SGSharedPtr<SGExpressiond> value;
+ value = new SGPropertyExpression<double>(inputProperty);
+
+ value = read_factor_offset(configNode, value, "max-factor", "max-offset");
+ _maxAnimationValue = value->simplify();
+ }
+
+ _initialValue[0] = configNode->getDoubleValue("min-m", 0);
+ _initialValue[0] *= configNode->getDoubleValue("min-factor", 1);
+ _initialValue[1] = configNode->getDoubleValue("max-m", SGLimitsf::max());
+ _initialValue[1] *= configNode->getDoubleValue("max-factor", 1);
}
-SGTimedAnimation::~SGTimedAnimation ()
+osg::Group*
+SGRangeAnimation::createAnimationGroup(osg::Group& parent)
{
+ osg::Group* group = new osg::Group;
+ group->setName("range animation group");
+
+ osg::LOD* lod = new osg::LOD;
+ lod->setName("range animation node");
+ parent.addChild(lod);
+
+ lod->addChild(group, _initialValue[0], _initialValue[1]);
+ lod->setCenterMode(osg::LOD::USE_BOUNDING_SPHERE_CENTER);
+ lod->setRangeMode(osg::LOD::DISTANCE_FROM_EYE_POINT);
+ if (_minAnimationValue || _maxAnimationValue || _condition) {
+ UpdateCallback* uc;
+ uc = new UpdateCallback(_condition, _minAnimationValue, _maxAnimationValue,
+ _initialValue[0], _initialValue[1]);
+ lod->setUpdateCallback(uc);
+ }
+ return group;
}
-void
-SGTimedAnimation::update()
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of a select animation
+////////////////////////////////////////////////////////////////////////
+
+SGSelectAnimation::SGSelectAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
{
- if ((sim_time_sec - _last_time_sec) >= _duration_sec) {
- _last_time_sec = sim_time_sec;
- _step++;
- if (_step >= getBranch()->getNumKids())
- _step = 0;
- ((ssgSelector *)getBranch())->selectStep(_step);
- }
+}
+
+osg::Group*
+SGSelectAnimation::createAnimationGroup(osg::Group& parent)
+{
+ // if no condition given, this is a noop.
+ SGSharedPtr<SGCondition const> condition = getCondition();
+ // trick, gets deleted with all its 'animated' children
+ // when the animation installer returns
+ if (!condition)
+ return new osg::Group;
+ 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;
}
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of SGRotateAnimation
+// Implementation of alpha test animation
////////////////////////////////////////////////////////////////////////
-SGRotateAnimation::SGRotateAnimation( SGPropertyNode *prop_root,
- SGPropertyNode_ptr props )
- : SGAnimation(props, new ssgTransform),
- _prop((SGPropertyNode *)prop_root->getNode(props->getStringValue("property", "/null"), true)),
- _offset_deg(props->getDoubleValue("offset-deg", 0.0)),
- _factor(props->getDoubleValue("factor", 1.0)),
- _table(read_interpolation_table(props)),
- _has_min(props->hasValue("min-deg")),
- _min_deg(props->getDoubleValue("min-deg")),
- _has_max(props->hasValue("max-deg")),
- _max_deg(props->getDoubleValue("max-deg")),
- _position_deg(props->getDoubleValue("starting-position-deg", 0))
+SGAlphaTestAnimation::SGAlphaTestAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
{
- _center[0] = props->getFloatValue("center/x-m", 0);
- _center[1] = props->getFloatValue("center/y-m", 0);
- _center[2] = props->getFloatValue("center/z-m", 0);
- _axis[0] = props->getFloatValue("axis/x", 0);
- _axis[1] = props->getFloatValue("axis/y", 0);
- _axis[2] = props->getFloatValue("axis/z", 0);
- sgNormalizeVec3(_axis);
}
-SGRotateAnimation::~SGRotateAnimation ()
+namespace
+{
+// Keep one copy of the most common alpha test its state set.
+ReentrantMutex alphaTestMutex;
+osg::ref_ptr<osg::AlphaFunc> standardAlphaFunc;
+osg::ref_ptr<osg::StateSet> alphaFuncStateSet;
+
+osg::AlphaFunc* makeAlphaFunc(float clamp)
{
- delete _table;
+ ScopedLock<ReentrantMutex> lock(alphaTestMutex);
+ if (osg::equivalent(clamp, 0.01f)) {
+ if (standardAlphaFunc.valid())
+ return standardAlphaFunc.get();
+ clamp = .01;
+ }
+ osg::AlphaFunc* alphaFunc = new osg::AlphaFunc;
+ alphaFunc->setFunction(osg::AlphaFunc::GREATER);
+ alphaFunc->setReferenceValue(clamp);
+ alphaFunc->setDataVariance(osg::Object::STATIC);
+ if (osg::equivalent(clamp, 0.01f))
+ standardAlphaFunc = alphaFunc;
+ return alphaFunc;
}
+osg::StateSet* makeAlphaTestStateSet(float clamp)
+{
+ using namespace OpenThreads;
+ ScopedLock<ReentrantMutex> lock(alphaTestMutex);
+ if (osg::equivalent(clamp, 0.01f)) {
+ if (alphaFuncStateSet.valid())
+ return alphaFuncStateSet.get();
+ }
+ osg::AlphaFunc* alphaFunc = makeAlphaFunc(clamp);
+ osg::StateSet* stateSet = new osg::StateSet;
+ stateSet->setAttributeAndModes(alphaFunc,
+ (osg::StateAttribute::ON
+ | osg::StateAttribute::OVERRIDE));
+ stateSet->setDataVariance(osg::Object::STATIC);
+ if (osg::equivalent(clamp, 0.01f))
+ alphaFuncStateSet = stateSet;
+ return stateSet;
+}
+}
void
-SGRotateAnimation::update()
-{
- if (_table == 0) {
- _position_deg = _prop->getDoubleValue() * _factor + _offset_deg;
- if (_has_min && _position_deg < _min_deg)
- _position_deg = _min_deg;
- if (_has_max && _position_deg > _max_deg)
- _position_deg = _max_deg;
+SGAlphaTestAnimation::install(osg::Node& node)
+{
+ SGAnimation::install(node);
+
+ float alphaClamp = getConfig()->getFloatValue("alpha-factor", 0);
+ osg::StateSet* stateSet = node.getStateSet();
+ if (!stateSet) {
+ node.setStateSet(makeAlphaTestStateSet(alphaClamp));
} else {
- _position_deg = _table->interpolate(_prop->getDoubleValue());
+ stateSet->setAttributeAndModes(makeAlphaFunc(alphaClamp),
+ (osg::StateAttribute::ON
+ | osg::StateAttribute::OVERRIDE));
+ }
+}
+
+\f
+//////////////////////////////////////////////////////////////////////
+// Blend animation installer
+//////////////////////////////////////////////////////////////////////
+
+// XXX This needs to be replaced by something using TexEnvCombine to
+// change the blend factor. Changing the alpha values in the geometry
+// is bogus.
+class SGBlendAnimation::BlendVisitor : public osg::NodeVisitor {
+public:
+ BlendVisitor(float blend) :
+ osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+ _blend(blend)
+ { setVisitorType(osg::NodeVisitor::NODE_VISITOR); }
+ virtual void apply(osg::Node& node)
+ {
+ updateStateSet(node.getStateSet());
+ traverse(node);
+ }
+ virtual void apply(osg::Geode& node)
+ {
+ apply((osg::Node&)node);
+ unsigned nDrawables = node.getNumDrawables();
+ for (unsigned i = 0; i < nDrawables; ++i) {
+ osg::Drawable* drawable = node.getDrawable(i);
+ osg::Geometry* geometry = drawable->asGeometry();
+ if (!geometry)
+ continue;
+ osg::Array* array = geometry->getColorArray();
+ if (!array)
+ continue;
+ osg::Vec4Array* vec4Array = dynamic_cast<osg::Vec4Array*>(array);
+ if (!vec4Array)
+ continue;
+ for (unsigned k = 0; k < vec4Array->size(); ++k) {
+ (*vec4Array)[k][3] = _blend;
+ }
+ vec4Array->dirty();
+ updateStateSet(drawable->getStateSet());
+ }
+ }
+ void updateStateSet(osg::StateSet* stateSet)
+ {
+ if (!stateSet)
+ return;
+ osg::StateAttribute* stateAttribute;
+ stateAttribute = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
+ if (!stateAttribute)
+ return;
+ osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
+ if (!material)
+ return;
+ material->setAlpha(osg::Material::FRONT_AND_BACK, _blend);
+ if (_blend < 1) {
+ stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
+ stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
+ } else {
+ stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
+ }
+ }
+private:
+ float _blend;
+};
+
+class SGBlendAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+ UpdateCallback(const SGPropertyNode* configNode, const SGExpressiond* v) :
+ _prev_value(-1),
+ _animationValue(v)
+ { }
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ double blend = _animationValue->getValue();
+ if (blend != _prev_value) {
+ _prev_value = blend;
+ BlendVisitor visitor(1-blend);
+ node->accept(visitor);
+ }
+ traverse(node, nv);
+ }
+public:
+ double _prev_value;
+ SGSharedPtr<SGExpressiond const> _animationValue;
+};
+
+
+SGBlendAnimation::SGBlendAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot)
+ : SGAnimation(configNode, modelRoot),
+ _animationValue(read_value(configNode, modelRoot, "", 0, 1))
+{
+}
+
+osg::Group*
+SGBlendAnimation::createAnimationGroup(osg::Group& parent)
+{
+ if (!_animationValue)
+ return 0;
+
+ osg::Group* group = new osg::Switch;
+ group->setName("blend animation node");
+ group->setUpdateCallback(new UpdateCallback(getConfig(), _animationValue));
+ parent.addChild(group);
+ return group;
+}
+
+void
+SGBlendAnimation::install(osg::Node& node)
+{
+ SGAnimation::install(node);
+ // make sure we do not change common geometries,
+ // that also creates new display lists for these subgeometries.
+ cloneDrawables(node);
+ DoDrawArraysVisitor visitor;
+ node.accept(visitor);
+}
+
+\f
+//////////////////////////////////////////////////////////////////////
+// Timed animation installer
+//////////////////////////////////////////////////////////////////////
+
+
+
+class SGTimedAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+ UpdateCallback(const SGPropertyNode* configNode) :
+ _current_index(0),
+ _reminder(0),
+ _duration_sec(configNode->getDoubleValue("duration-sec", 1)),
+ _last_time_sec(SGLimitsd::max()),
+ _use_personality(configNode->getBoolValue("use-personality", false))
+ {
+ std::vector<SGSharedPtr<SGPropertyNode> > nodes;
+ nodes = configNode->getChildren("branch-duration-sec");
+ for (size_t i = 0; i < nodes.size(); ++i) {
+ unsigned ind = nodes[ i ]->getIndex();
+ while ( ind >= _durations.size() ) {
+ _durations.push_back(DurationSpec(_duration_sec));
+ }
+ SGPropertyNode_ptr rNode = nodes[i]->getChild("random");
+ if ( rNode == 0 ) {
+ _durations[ind] = DurationSpec(nodes[ i ]->getDoubleValue());
+ } else {
+ _durations[ind] = DurationSpec(rNode->getDoubleValue( "min", 0),
+ rNode->getDoubleValue( "max", 1));
+ }
+ }
+ }
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ assert(dynamic_cast<osg::Switch*>(node));
+ osg::Switch* sw = static_cast<osg::Switch*>(node);
+
+ unsigned nChildren = sw->getNumChildren();
+
+ // blow up the durations vector to the required size
+ while (_durations.size() < nChildren) {
+ _durations.push_back(_duration_sec);
+ }
+ // make sure the current index is an duration that really exists
+ _current_index = _current_index % nChildren;
+
+ // update the time and compute the current systems time value
+ double t = nv->getFrameStamp()->getReferenceTime();
+ if (_last_time_sec == SGLimitsd::max()) {
+ _last_time_sec = t;
+ } else {
+ double dt = t - _last_time_sec;
+ if (_use_personality)
+ dt *= 1 + 0.2*(0.5 - sg_random());
+ _reminder += dt;
+ _last_time_sec = t;
+ }
+
+ double currentDuration = _durations[_current_index].get();
+ while (currentDuration < _reminder) {
+ _reminder -= currentDuration;
+ _current_index = (_current_index + 1) % nChildren;
+ currentDuration = _durations[_current_index].get();
+ }
+
+ sw->setSingleChildOn(_current_index);
+
+ traverse(node, nv);
+ }
+
+private:
+ struct DurationSpec {
+ DurationSpec(double t) :
+ minTime(SGMiscd::max(0.01, t)),
+ maxTime(SGMiscd::max(0.01, t))
+ {}
+ DurationSpec(double t0, double t1) :
+ minTime(SGMiscd::max(0.01, t0)),
+ maxTime(SGMiscd::max(0.01, t1))
+ {}
+ double get() const
+ { return minTime + sg_random()*(maxTime - minTime); }
+ double minTime;
+ double maxTime;
+ };
+ std::vector<DurationSpec> _durations;
+ unsigned _current_index;
+ double _reminder;
+ double _duration_sec;
+ double _last_time_sec;
+ bool _use_personality;
+};
+
+
+SGTimedAnimation::SGTimedAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot)
+ : SGAnimation(configNode, modelRoot)
+{
+}
+
+osg::Group*
+SGTimedAnimation::createAnimationGroup(osg::Group& parent)
+{
+ osg::Switch* sw = new osg::Switch;
+ sw->setName("timed animation node");
+ sw->setUpdateCallback(new UpdateCallback(getConfig()));
+ parent.addChild(sw);
+ return sw;
+}
+
+\f
+////////////////////////////////////////////////////////////////////////
+// dynamically switch on/off shadows
+////////////////////////////////////////////////////////////////////////
+
+class SGShadowAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+ UpdateCallback(const SGCondition* condition) :
+ _condition(condition)
+ {}
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ if (_condition->test())
+ node->setNodeMask( SG_NODEMASK_CASTSHADOW_BIT | node->getNodeMask());
+ else
+ node->setNodeMask(~SG_NODEMASK_CASTSHADOW_BIT & node->getNodeMask());
+ traverse(node, nv);
}
- set_rotation(_matrix, _position_deg, _center, _axis);
- ((ssgTransform *)_branch)->setTransform(_matrix);
+
+private:
+ SGSharedPtr<const SGCondition> _condition;
+};
+
+SGShadowAnimation::SGShadowAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
+{
}
+osg::Group*
+SGShadowAnimation::createAnimationGroup(osg::Group& parent)
+{
+ SGSharedPtr<SGCondition const> condition = getCondition();
+ if (!condition)
+ return 0;
+
+ osg::Group* group = new osg::Group;
+ group->setName("shadow animation");
+ group->setUpdateCallback(new UpdateCallback(condition));
+ parent.addChild(group);
+ return group;
+}
\f
////////////////////////////////////////////////////////////////////////
-// Implementation of SGTranslateAnimation
+// Implementation of SGTexTransformAnimation
////////////////////////////////////////////////////////////////////////
-SGTranslateAnimation::SGTranslateAnimation( SGPropertyNode *prop_root,
- SGPropertyNode_ptr props )
- : SGAnimation(props, new ssgTransform),
- _prop((SGPropertyNode *)prop_root->getNode(props->getStringValue("property", "/null"), true)),
- _offset_m(props->getDoubleValue("offset-m", 0.0)),
- _factor(props->getDoubleValue("factor", 1.0)),
- _table(read_interpolation_table(props)),
- _has_min(props->hasValue("min-m")),
- _min_m(props->getDoubleValue("min-m")),
- _has_max(props->hasValue("max-m")),
- _max_m(props->getDoubleValue("max-m")),
- _position_m(props->getDoubleValue("starting-position-m", 0))
+class SGTexTransformAnimation::Transform : public SGReferenced {
+public:
+ Transform() :
+ _value(0)
+ {}
+ virtual ~Transform()
+ { }
+ void setValue(double value)
+ { _value = value; }
+ virtual void transform(osg::Matrix&) = 0;
+protected:
+ double _value;
+};
+
+class SGTexTransformAnimation::Translation :
+ public SGTexTransformAnimation::Transform {
+public:
+ Translation(const SGVec3d& axis) :
+ _axis(axis)
+ { }
+ virtual void transform(osg::Matrix& matrix)
+ {
+ osg::Matrix tmp;
+ set_translation(tmp, _value, _axis);
+ matrix.preMult(tmp);
+ }
+private:
+ SGVec3d _axis;
+};
+
+class SGTexTransformAnimation::Rotation :
+ public SGTexTransformAnimation::Transform {
+public:
+ Rotation(const SGVec3d& axis, const SGVec3d& center) :
+ _axis(axis),
+ _center(center)
+ { }
+ virtual void transform(osg::Matrix& matrix)
+ {
+ osg::Matrix tmp;
+ set_rotation(tmp, _value, _center, _axis);
+ matrix.preMult(tmp);
+ }
+private:
+ SGVec3d _axis;
+ SGVec3d _center;
+};
+
+class SGTexTransformAnimation::UpdateCallback :
+ public osg::StateAttribute::Callback {
+public:
+ UpdateCallback(const SGCondition* condition) :
+ _condition(condition)
+ { }
+ virtual void operator () (osg::StateAttribute* sa, osg::NodeVisitor*)
+ {
+ if (!_condition || _condition->test()) {
+ TransformList::const_iterator i;
+ for (i = _transforms.begin(); i != _transforms.end(); ++i)
+ i->transform->setValue(i->value->getValue());
+ }
+ assert(dynamic_cast<osg::TexMat*>(sa));
+ osg::TexMat* texMat = static_cast<osg::TexMat*>(sa);
+ texMat->getMatrix().makeIdentity();
+ TransformList::const_iterator i;
+ for (i = _transforms.begin(); i != _transforms.end(); ++i)
+ i->transform->transform(texMat->getMatrix());
+ }
+ void appendTransform(Transform* transform, SGExpressiond* value)
+ {
+ Entry entry = { transform, value };
+ transform->transform(_matrix);
+ _transforms.push_back(entry);
+ }
+
+private:
+ struct Entry {
+ SGSharedPtr<Transform> transform;
+ SGSharedPtr<const SGExpressiond> value;
+ };
+ typedef std::vector<Entry> TransformList;
+ TransformList _transforms;
+ SGSharedPtr<const SGCondition> _condition;
+ osg::Matrix _matrix;
+};
+
+SGTexTransformAnimation::SGTexTransformAnimation(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ SGAnimation(configNode, modelRoot)
{
- _axis[0] = props->getFloatValue("axis/x", 0);
- _axis[1] = props->getFloatValue("axis/y", 0);
- _axis[2] = props->getFloatValue("axis/z", 0);
- sgNormalizeVec3(_axis);
}
-SGTranslateAnimation::~SGTranslateAnimation ()
+osg::Group*
+SGTexTransformAnimation::createAnimationGroup(osg::Group& parent)
{
- delete _table;
+ osg::Group* group = new osg::Group;
+ group->setName("texture transform group");
+ osg::StateSet* stateSet = group->getOrCreateStateSet();
+ stateSet->setDataVariance(osg::Object::DYNAMIC);
+ osg::TexMat* texMat = new osg::TexMat;
+ UpdateCallback* updateCallback = new UpdateCallback(getCondition());
+ // interpret the configs ...
+ std::string type = getType();
+
+ if (type == "textranslate") {
+ appendTexTranslate(getConfig(), updateCallback);
+ } else if (type == "texrotate") {
+ appendTexRotate(getConfig(), updateCallback);
+ } else if (type == "texmultiple") {
+ std::vector<SGSharedPtr<SGPropertyNode> > transformConfigs;
+ transformConfigs = getConfig()->getChildren("transform");
+ for (unsigned i = 0; i < transformConfigs.size(); ++i) {
+ std::string subtype = transformConfigs[i]->getStringValue("subtype", "");
+ if (subtype == "textranslate")
+ appendTexTranslate(transformConfigs[i], updateCallback);
+ else if (subtype == "texrotate")
+ appendTexRotate(transformConfigs[i], updateCallback);
+ else
+ SG_LOG(SG_INPUT, SG_ALERT,
+ "Ignoring unknown texture transform subtype");
+ }
+ } else {
+ SG_LOG(SG_INPUT, SG_ALERT, "Ignoring unknown texture transform type");
+ }
+
+ texMat->setUpdateCallback(updateCallback);
+ stateSet->setTextureAttribute(0, texMat);
+ parent.addChild(group);
+ return group;
+}
+
+void
+SGTexTransformAnimation::appendTexTranslate(const SGPropertyNode* config,
+ UpdateCallback* updateCallback)
+{
+ std::string propertyName = config->getStringValue("property", "");
+ SGSharedPtr<SGExpressiond> value;
+ if (propertyName.empty())
+ value = new SGConstExpression<double>(0);
+ else {
+ SGPropertyNode* inputProperty = getModelRoot()->getNode(propertyName, true);
+ value = new SGPropertyExpression<double>(inputProperty);
+ }
+
+ SGInterpTable* table = read_interpolation_table(config);
+ if (table) {
+ value = new SGInterpTableExpression<double>(value, table);
+ double biasValue = config->getDoubleValue("bias", 0);
+ if (biasValue != 0)
+ value = new SGBiasExpression<double>(value, biasValue);
+ value = new SGStepExpression<double>(value,
+ config->getDoubleValue("step", 0),
+ config->getDoubleValue("scroll", 0));
+ value = value->simplify();
+ } else {
+ double biasValue = config->getDoubleValue("bias", 0);
+ if (biasValue != 0)
+ value = new SGBiasExpression<double>(value, biasValue);
+ value = new SGStepExpression<double>(value,
+ config->getDoubleValue("step", 0),
+ config->getDoubleValue("scroll", 0));
+ value = read_offset_factor(config, value, "factor", "offset");
+
+ if (config->hasChild("min") || config->hasChild("max")) {
+ double minClip = config->getDoubleValue("min", -SGLimitsd::max());
+ double maxClip = config->getDoubleValue("max", SGLimitsd::max());
+ value = new SGClipExpression<double>(value, minClip, maxClip);
+ }
+ value = value->simplify();
+ }
+ SGVec3d axis(config->getDoubleValue("axis/x", 0),
+ config->getDoubleValue("axis/y", 0),
+ config->getDoubleValue("axis/z", 0));
+ Translation* translation;
+ translation = new Translation(normalize(axis));
+ translation->setValue(config->getDoubleValue("starting-position", 0));
+ updateCallback->appendTransform(translation, value);
}
void
-SGTranslateAnimation::update()
-{
- if (_table == 0) {
- _position_m = (_prop->getDoubleValue() + _offset_m) * _factor;
- if (_has_min && _position_m < _min_m)
- _position_m = _min_m;
- if (_has_max && _position_m > _max_m)
- _position_m = _max_m;
+SGTexTransformAnimation::appendTexRotate(const SGPropertyNode* config,
+ UpdateCallback* updateCallback)
+{
+ std::string propertyName = config->getStringValue("property", "");
+ SGSharedPtr<SGExpressiond> value;
+ if (propertyName.empty())
+ value = new SGConstExpression<double>(0);
+ else {
+ SGPropertyNode* inputProperty = getModelRoot()->getNode(propertyName, true);
+ value = new SGPropertyExpression<double>(inputProperty);
+ }
+
+ SGInterpTable* table = read_interpolation_table(config);
+ if (table) {
+ value = new SGInterpTableExpression<double>(value, table);
+ double biasValue = config->getDoubleValue("bias", 0);
+ if (biasValue != 0)
+ value = new SGBiasExpression<double>(value, biasValue);
+ value = new SGStepExpression<double>(value,
+ config->getDoubleValue("step", 0),
+ config->getDoubleValue("scroll", 0));
+ value = value->simplify();
} else {
- _position_m = _table->interpolate(_prop->getDoubleValue());
+ double biasValue = config->getDoubleValue("bias", 0);
+ if (biasValue != 0)
+ value = new SGBiasExpression<double>(value, biasValue);
+ value = new SGStepExpression<double>(value,
+ config->getDoubleValue("step", 0),
+ config->getDoubleValue("scroll", 0));
+ value = read_offset_factor(config, value, "factor", "offset-deg");
+
+ if (config->hasChild("min-deg") || config->hasChild("max-deg")) {
+ double minClip = config->getDoubleValue("min-deg", -SGLimitsd::max());
+ double maxClip = config->getDoubleValue("max-deg", SGLimitsd::max());
+ value = new SGClipExpression<double>(value, minClip, maxClip);
+ }
+ value = value->simplify();
}
- set_translation(_matrix, _position_m, _axis);
- ((ssgTransform *)_branch)->setTransform(_matrix);
+ SGVec3d axis(config->getDoubleValue("axis/x", 0),
+ config->getDoubleValue("axis/y", 0),
+ config->getDoubleValue("axis/z", 0));
+ SGVec3d center(config->getDoubleValue("center/x", 0),
+ config->getDoubleValue("center/y", 0),
+ config->getDoubleValue("center/z", 0));
+ Rotation* rotation;
+ rotation = new Rotation(normalize(axis), center);
+ rotation->setValue(config->getDoubleValue("starting-position-deg", 0));
+ updateCallback->appendTransform(rotation, value);
}
-// end of animation.cxx
+////////////////////////////////////////////////////////////////////////
+// Implementation of SGPickAnimation
+////////////////////////////////////////////////////////////////////////
+
+class SGPickAnimation::PickCallback : public SGPickCallback {
+public:
+ PickCallback(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ _repeatable(configNode->getBoolValue("repeatable", false)),
+ _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
+ {
+ SG_LOG(SG_INPUT, SG_DEBUG, "Reading all bindings");
+ std::vector<SGPropertyNode_ptr> bindings;
+
+ bindings = configNode->getChildren("button");
+ for (unsigned int i = 0; i < bindings.size(); ++i) {
+ _buttons.push_back( bindings[i]->getIntValue() );
+ }
+ bindings = configNode->getChildren("binding");
+ for (unsigned int i = 0; i < bindings.size(); ++i) {
+ _bindingsDown.push_back(new SGBinding(bindings[i], modelRoot));
+ }
+
+ const SGPropertyNode* upNode = configNode->getChild("mod-up");
+ if (!upNode)
+ return;
+ bindings = upNode->getChildren("binding");
+ for (unsigned int i = 0; i < bindings.size(); ++i) {
+ _bindingsUp.push_back(new SGBinding(bindings[i], modelRoot));
+ }
+ }
+ virtual bool buttonPressed(int button, const Info&)
+ {
+ bool found = false;
+ for( std::vector<int>::iterator it = _buttons.begin(); it != _buttons.end(); it++ ) {
+ if( *it == button ) {
+ found = true;
+ break;
+ }
+ }
+ if (!found )
+ return false;
+ SGBindingList::const_iterator i;
+ for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
+ (*i)->fire();
+ _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
+ return true;
+ }
+ virtual void buttonReleased(void)
+ {
+ SGBindingList::const_iterator i;
+ for (i = _bindingsUp.begin(); i != _bindingsUp.end(); ++i)
+ (*i)->fire();
+ }
+ virtual void update(double dt)
+ {
+ if (!_repeatable)
+ return;
+
+ _repeatTime += dt;
+ while (_repeatInterval < _repeatTime) {
+ _repeatTime -= _repeatInterval;
+ SGBindingList::const_iterator i;
+ for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
+ (*i)->fire();
+ }
+ }
+private:
+ SGBindingList _bindingsDown;
+ SGBindingList _bindingsUp;
+ std::vector<int> _buttons;
+ bool _repeatable;
+ double _repeatInterval;
+ 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)
+{
+}
+
+osg::Group*
+SGPickAnimation::createAnimationGroup(osg::Group& parent)
+{
+ osg::Group* commonGroup = new osg::Group;
+
+ // Contains the normal geometry that is interactive
+ osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
+ normalGroup->setName("pick normal group");
+ normalGroup->addChild(commonGroup);
+
+ // Used to render the geometry with just yellow edges
+ osg::Group* highlightGroup = new osg::Group;
+ highlightGroup->setName("pick highlight group");
+ highlightGroup->setNodeMask(SG_NODEMASK_PICK_BIT);
+ 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
+ osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
+ stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
+
+ 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);
+
+ 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->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);
+
+ // Only add normal geometry if configured
+ if (getConfig()->getBoolValue("visible", true))
+ parent.addChild(normalGroup.get());
+ parent.addChild(highlightGroup);
+
+ return commonGroup;
+}