]> git.mxchange.org Git - simgear.git/commitdiff
Add trackTo (locked-track) animation
authorThomas Geymayer <tomgey@gmail.com>
Thu, 25 Apr 2013 21:22:04 +0000 (23:22 +0200)
committerThomas Geymayer <tomgey@gmail.com>
Thu, 25 Apr 2013 21:23:16 +0000 (23:23 +0200)
simgear/scene/model/CMakeLists.txt
simgear/scene/model/SGPickAnimation.cxx
simgear/scene/model/SGTrackToAnimation.cxx [new file with mode: 0644]
simgear/scene/model/SGTrackToAnimation.hxx [new file with mode: 0644]
simgear/scene/model/animation.cxx
simgear/scene/model/animation.hxx
simgear/scene/model/animation_test.cxx [new file with mode: 0644]

index e10994b0bbe6775604c03adb940fc84b7f03764c..416b74af922dd709710fbceedab8ff87f55bc083 100644 (file)
@@ -17,6 +17,7 @@ set(HEADERS
     SGRotateTransform.hxx
     SGScaleTransform.hxx
     SGText.hxx
+    SGTrackToAnimation.hxx
     SGTranslateTransform.hxx
     animation.hxx
     model.hxx
@@ -42,6 +43,7 @@ set(SOURCES
     SGRotateTransform.cxx
     SGScaleTransform.cxx
     SGText.cxx
+    SGTrackToAnimation.cxx
     SGTranslateTransform.cxx
     animation.cxx
     model.cxx
@@ -53,3 +55,11 @@ set(SOURCES
     )
 
 simgear_scene_component(model scene/model "${SOURCES}" "${HEADERS}")
+
+if(ENABLE_TESTS)
+
+  add_executable(test_animations animation_test.cxx)
+  target_link_libraries(test_animations ${TEST_LIBS} ${OPENSCENEGRAPH_LIBRARIES})
+  add_test(test_animations ${EXECUTABLE_OUTPUT_PATH}/test_animations)
+
+endif(ENABLE_TESTS)
\ No newline at end of file
index ddc41030d55ddaf53d3591e65adeb33b057e4d23..8a03538a1528ae62ea5ab03ad337b5bb39d409bf 100644 (file)
@@ -727,7 +727,7 @@ SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
     _animationValue = value->simplify();
     
     
-    readRotationCenterAndAxis(configNode, _center, _axis);
+    readRotationCenterAndAxis(_center, _axis);
 }
 
 osg::Group*
diff --git a/simgear/scene/model/SGTrackToAnimation.cxx b/simgear/scene/model/SGTrackToAnimation.cxx
new file mode 100644 (file)
index 0000000..9541520
--- /dev/null
@@ -0,0 +1,246 @@
+// TrackTo animation
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#include "SGRotateTransform.hxx"
+#include "SGTrackToAnimation.hxx"
+
+#include <simgear/scene/util/OsgMath.hxx>
+#include <osg/Transform>
+#include <cassert>
+
+/**
+ *
+ */
+static osg::NodePath requireNodePath( osg::Node* node,
+                                      osg::Node* haltTraversalAtNode = 0 )
+{
+  const osg::NodePathList node_paths =
+    node->getParentalNodePaths(haltTraversalAtNode);
+  return node_paths.at(0);
+}
+
+/**
+ * Get a subpath of an osg::NodePath
+ *
+ * @param path  Path to extract subpath from
+ * @param start Number of elements to skip from start of #path
+ *
+ * @return Subpath starting with node at position #start
+ */
+static osg::NodePath subPath( const osg::NodePath& path,
+                              size_t start )
+{
+  if( start >= path.size() )
+    return osg::NodePath();
+
+  osg::NodePath np(path.size() - start);
+  for(size_t i = start; i < path.size(); ++i)
+    np[i - start] = path[i];
+
+  return np;
+}
+
+/**
+ * Visitor to find a group by its name.
+ */
+class FindGroupVisitor:
+  public osg::NodeVisitor
+{
+  public:
+
+    FindGroupVisitor(const std::string& name):
+        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+        _name(name),
+        _group(0)
+    {
+      if( name.empty() )
+        SG_LOG(SG_IO, SG_WARN, "FindGroupVisitor: empty name provided");
+    }
+
+    osg::Group* getGroup() const
+    {
+      return _group;
+    }
+
+    virtual void apply(osg::Group& group)
+    {
+      if( _name != group.getName() )
+        return traverse(group);
+
+      if( !_group )
+        _group = &group;
+      else
+        SG_LOG(SG_IO, SG_WARN, "FindGroupVisitor: name not unique");
+    }
+
+  protected:
+
+    std::string _name;
+    osg::Group *_group;
+};
+
+//------------------------------------------------------------------------------
+class SGTrackToAnimation::UpdateCallback:
+  public osg::NodeCallback
+{
+  public:
+    UpdateCallback( osg::Group* target,
+                    const SGTrackToAnimation* anim ):
+      _target(target)
+    {
+      setName("SGTrackToAnimation::UpdateCallback");
+
+      _node_center = toOsg( anim->readVec3("center", "-m") );
+      _target_center = toOsg( anim->readVec3("target-center", "-m") );
+      _lock_axis = toOsg( anim->readVec3("lock-axis") );
+      _track_axis = toOsg( anim->readVec3("track-axis") );
+
+      if( _lock_axis.normalize() == 0.0 )
+      {
+        anim->log(SG_WARN, "invalid lock-axis");
+        _lock_axis.set(0, 1, 0);
+      }
+
+      for(;;)
+      {
+        float proj = _lock_axis * _track_axis;
+        if( proj != 0.0 )
+        {
+          anim->log(SG_WARN, "track-axis not perpendicular to lock-axis");
+
+          // Make tracking axis perpendicular to locked axis
+          _track_axis -= _lock_axis * proj;
+        }
+
+        if( _track_axis.normalize() == 0.0 )
+        {
+          anim->log(SG_WARN, "invalid track-axis");
+          if( std::fabs(_lock_axis.x()) < 0.1 )
+            _track_axis.set(1, 0, 0);
+          else
+            _track_axis.set(0, 1, 0);
+        }
+        else
+          break;
+      }
+
+      _up_axis = _lock_axis ^ _track_axis;
+    }
+
+    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+    {
+      SGRotateTransform* tf = static_cast<SGRotateTransform*>(node);
+
+      if( _target )
+      {
+        // Get path to animated node and calculated simplified paths to the
+        // nearest common parent node of both the animated node and the target
+        // node
+
+                                  // start at parent to not get false results by
+                                  // also including animation transformation
+        osg::NodePath node_path = requireNodePath(node->getParent(0)),
+                      target_path = requireNodePath(_target);
+        size_t tp_size = target_path.size(),
+               np_size = node_path.size();
+        size_t common_parents = 0;
+
+        for(; common_parents < std::min(tp_size, np_size); ++common_parents)
+        {
+          if( target_path[common_parents] != node_path[common_parents] )
+            break;
+        }
+
+        _node_path = subPath(node_path, common_parents);
+        _target_path = subPath(target_path, common_parents);
+        _target = 0;
+
+        tf->setCenter( toSG(_node_center) );
+        tf->setAxis( toSG(_lock_axis) );
+      }
+
+      osg::Vec3 target_pos = ( osg::computeLocalToWorld(_target_path)
+                             * osg::computeWorldToLocal(_node_path)
+                             ).preMult(_target_center),
+                       dir = target_pos - _node_center;
+
+      // Ensure direction is perpendicular to lock axis
+      float proj = _lock_axis * dir;
+      if( proj != 0.0 )
+        dir -= _lock_axis * proj;
+
+      float x = dir * _track_axis,
+            y = dir * _up_axis;
+      tf->setAngleRad( std::atan2(y, x) );
+
+      traverse(node, nv);
+    }
+  protected:
+    osg::Vec3       _node_center,
+                    _target_center,
+                    _lock_axis,
+                    _track_axis,
+                    _up_axis;
+    osg::Group     *_target;
+    osg::NodePath   _node_path,
+                    _target_path;
+};
+
+//------------------------------------------------------------------------------
+SGTrackToAnimation::SGTrackToAnimation( osg::Node* node,
+                                        const SGPropertyNode* configNode,
+                                        SGPropertyNode* modelRoot ):
+  SGAnimation(configNode, modelRoot),
+  _target_group(0)
+{
+  std::string target = configNode->getStringValue("target-name");
+  std::cout << "track " << target << std::endl;
+  configNode->printOn(std::cout);
+
+  FindGroupVisitor target_finder(target);
+  node->accept(target_finder);
+
+  if( !(_target_group = target_finder.getGroup()) )
+    log(SG_ALERT, "target not found: '" + target + '\'');
+}
+
+//------------------------------------------------------------------------------
+osg::Group* SGTrackToAnimation::createAnimationGroup(osg::Group& parent)
+{
+  if( !_target_group )
+    return 0;
+
+  SGRotateTransform* transform = new SGRotateTransform;
+  transform->setName("locked-track animation");
+  transform->setUpdateCallback(new UpdateCallback(_target_group, this));
+  parent.addChild(transform);
+  return transform;
+}
+
+//------------------------------------------------------------------------------
+void SGTrackToAnimation::log(sgDebugPriority p, const std::string& msg) const
+{
+  SG_LOG
+  (
+    SG_IO,
+    p,
+    // TODO handle multiple object-names?
+    "SGTrackToAnimation(" << getConfig()->getStringValue("object-name") << "): "
+    << msg
+  );
+}
diff --git a/simgear/scene/model/SGTrackToAnimation.hxx b/simgear/scene/model/SGTrackToAnimation.hxx
new file mode 100644 (file)
index 0000000..717d955
--- /dev/null
@@ -0,0 +1,45 @@
+// TrackTo animation
+//
+// http://wiki.blender.org/index.php/Doc:2.6/Manual/Constraints/Tracking/Locked_Track
+// TODO: http://wiki.blender.org/index.php/Doc:2.6/Manual/Constraints/Tracking/Track_To
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#ifndef SG_TRACK_TO_ANIMATION_HXX_
+#define SG_TRACK_TO_ANIMATION_HXX_
+
+#include <simgear/scene/model/animation.hxx>
+
+class SGTrackToAnimation:
+  public SGAnimation
+{
+  public:
+    SGTrackToAnimation( osg::Node* node,
+                        const SGPropertyNode* configNode,
+                        SGPropertyNode* modelRoot );
+
+    virtual osg::Group* createAnimationGroup(osg::Group& parent);
+
+  protected:
+    class UpdateCallback;
+
+    osg::Group     *_target_group;
+
+    void log(sgDebugPriority p, const std::string& msg) const;
+};
+
+#endif /* SG_TRACK_TO_ANIMATION_HXX_ */
index 962fc78387f2ddd2b0dd5ebeac47545efbb63e0d..2e1f80fdae499372d625ff400269637e70591724 100644 (file)
@@ -57,6 +57,7 @@
 #include "SGScaleTransform.hxx"
 #include "SGInteractionAnimation.hxx"
 #include "SGPickAnimation.hxx"
+#include "SGTrackToAnimation.hxx"
 
 #include "ConditionNode.hxx"
 
@@ -65,7 +66,6 @@ using OpenThreads::ReentrantMutex;
 using OpenThreads::ScopedLock;
 
 using namespace simgear;
-\f
 ////////////////////////////////////////////////////////////////////////
 // Static utility functions.
 ////////////////////////////////////////////////////////////////////////
@@ -206,7 +206,6 @@ read_value(const SGPropertyNode* configNode, SGPropertyNode* modelRoot,
   return 0;
 }
 
-\f
 ////////////////////////////////////////////////////////////////////////
 // Animation installer
 ////////////////////////////////////////////////////////////////////////
@@ -424,6 +423,9 @@ SGAnimation::animate(osg::Node* node, const SGPropertyNode* configNode,
   } else if (type == "timed") {
     SGTimedAnimation animInst(configNode, modelRoot);
     animInst.apply(node);
+  } else if (type == "track-to" || type == "locked-track") {
+    SGTrackToAnimation animInst(node, configNode, modelRoot);
+    animInst.apply(node);
   } else if (type == "translate") {
     SGTranslateAnimation animInst(configNode, modelRoot);
     animInst.apply(node);
@@ -530,6 +532,41 @@ SGAnimation::installInGroup(const std::string& name, osg::Group& group,
   }
 }
 
+//------------------------------------------------------------------------------
+SGVec3d SGAnimation::readVec3( const std::string& name,
+                               const std::string& suffix,
+                               const SGVec3d& def ) const
+{
+  SGVec3d vec;
+  vec[0] = _configNode->getDoubleValue(name + "/x" + suffix, def.x());
+  vec[1] = _configNode->getDoubleValue(name + "/y" + suffix, def.y());
+  vec[2] = _configNode->getDoubleValue(name + "/z" + suffix, def.z());
+  return vec;
+}
+
+//------------------------------------------------------------------------------
+// factored out to share with SGKnobAnimation
+void SGAnimation::readRotationCenterAndAxis( SGVec3d& center,
+                                             SGVec3d& axis ) const
+{
+  center = SGVec3d::zeros();
+  if( _configNode->hasValue("axis/x1-m") )
+  {
+    SGVec3d v1 = readVec3("axis", "1-m"), // axis/[xyz]1-m
+            v2 = readVec3("axis", "2-m"); // axis/[xyz]2-m
+    center = 0.5*(v1+v2);
+    axis = v2 - v1;
+  }
+  else
+  {
+    axis = readVec3("axis");
+  }
+  if( 8 * SGLimitsd::min() < norm(axis) )
+    axis = normalize(axis);
+
+  center = readVec3("center", "-m", center);
+}
+
 void
 SGAnimation::removeMode(osg::Node& node, osg::StateAttribute::GLMode mode)
 {
@@ -584,7 +621,7 @@ SGAnimation::getCondition() const
 }
 
 
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of null animation
 ////////////////////////////////////////////////////////////////////////
@@ -606,7 +643,7 @@ SGGroupAnimation::createAnimationGroup(osg::Group& parent)
   return group;
 }
 
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of translate animation
 ////////////////////////////////////////////////////////////////////////
@@ -666,7 +703,7 @@ SGTranslateAnimation::createAnimationGroup(osg::Group& parent)
   return transform;
 }
 
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of rotate/spin animation
 ////////////////////////////////////////////////////////////////////////
@@ -837,33 +874,6 @@ void SpinAnimCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
     }
 }
 
-// factored out to share with SGKnobAnimation
-void readRotationCenterAndAxis(const SGPropertyNode* configNode, SGVec3d& center, SGVec3d& axis)
-{
-    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]);
-}
-
 SGVec3d readTranslateAxis(const SGPropertyNode* configNode)
 {
     SGVec3d axis;
@@ -905,7 +915,7 @@ SGRotateAnimation::SGRotateAnimation(const SGPropertyNode* configNode,
   else
     _initialValue = 0;
   
-  readRotationCenterAndAxis(configNode, _center, _axis);
+  readRotationCenterAndAxis(_center, _axis);
 }
 
 osg::Group*
@@ -935,7 +945,7 @@ SGRotateAnimation::createAnimationGroup(osg::Group& parent)
     }
 }
 
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of scale animation
 ////////////////////////////////////////////////////////////////////////
@@ -1069,7 +1079,7 @@ SGScaleAnimation::createAnimationGroup(osg::Group& parent)
   return transform;
 }
 
-\f
+
 // Don't create a new state state everytime we need GL_NORMALIZE!
 
 namespace
@@ -1215,7 +1225,7 @@ namespace
    &SGDistScaleAnimation::Transform::writeLocalData
    );
 }
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of flash animation
 ////////////////////////////////////////////////////////////////////////
@@ -1371,7 +1381,7 @@ namespace
    &SGFlashAnimation::Transform::writeLocalData
    );
 }
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of billboard animation
 ////////////////////////////////////////////////////////////////////////
@@ -1456,7 +1466,7 @@ namespace
    &SGBillboardAnimation::Transform::writeLocalData
    );
 }
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of a range animation
 ////////////////////////////////////////////////////////////////////////
@@ -1561,7 +1571,7 @@ SGRangeAnimation::createAnimationGroup(osg::Group& parent)
   return group;
 }
 
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of a select animation
 ////////////////////////////////////////////////////////////////////////
@@ -1591,7 +1601,7 @@ SGSelectAnimation::createAnimationGroup(osg::Group& parent)
 }
 
 
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of alpha test animation
 ////////////////////////////////////////////////////////////////////////
@@ -1661,7 +1671,7 @@ SGAlphaTestAnimation::install(osg::Node& node)
   }
 }
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Blend animation installer
 //////////////////////////////////////////////////////////////////////
@@ -1780,7 +1790,7 @@ SGBlendAnimation::install(osg::Node& node)
   node.accept(visitor);
 }
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Timed animation installer
 //////////////////////////////////////////////////////////////////////
@@ -1891,7 +1901,7 @@ SGTimedAnimation::createAnimationGroup(osg::Group& parent)
   return sw;
 }
 
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // dynamically switch on/off shadows
 ////////////////////////////////////////////////////////////////////////
@@ -1937,7 +1947,7 @@ SGShadowAnimation::createAnimationGroup(osg::Group& parent)
   return group;
 }
 
-\f
+
 ////////////////////////////////////////////////////////////////////////
 // Implementation of SGTexTransformAnimation
 ////////////////////////////////////////////////////////////////////////
index 9262926e13a37c8f27adaa4d66df4487907efdcc..9716dd21a26d6e949f639a93a48cf86ab16b0697 100644 (file)
@@ -35,13 +35,11 @@ SGExpressiond*
 read_value(const SGPropertyNode* configNode, SGPropertyNode* modelRoot,
            const char* unit, double defMin, double defMax);
 
-void readRotationCenterAndAxis(const SGPropertyNode* configNode, SGVec3d& center, SGVec3d& axis);
 SGVec3d readTranslateAxis(const SGPropertyNode* configNode);
 
-//////////////////////////////////////////////////////////////////////
-// Base class for animation installers
-//////////////////////////////////////////////////////////////////////
-
+/**
+ * Base class for animation installers
+ */
 class SGAnimation : protected osg::NodeVisitor {
 public:
   SGAnimation(const SGPropertyNode* configNode, SGPropertyNode* modelRoot);
@@ -60,6 +58,21 @@ protected:
 
   virtual void apply(osg::Group& group);
 
+  /**
+   * Read a 3d vector from the configuration property node.
+   *
+   * Reads values from @a name/[xyz]@a prefix and defaults to the according
+   * value of @a def for each value which is not set.
+   *
+   * @param name    Name of the root node containing all coordinates
+   * @param suffix  Suffix appended to each child node (x,y,z)
+   * @param def     Vector containing default values
+   */
+  SGVec3d readVec3( const std::string& name,
+                    const std::string& suffix = "",
+                    const SGVec3d& def = SGVec3d() ) const;
+  void readRotationCenterAndAxis(SGVec3d& center, SGVec3d& axis) const;
+
   void removeMode(osg::Node& node, osg::StateAttribute::GLMode mode);
   void removeAttribute(osg::Node& node, osg::StateAttribute::Type type);
   void removeTextureMode(osg::Node& node, unsigned unit,
@@ -100,7 +113,7 @@ private:
   bool _enableHOT;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Null animation installer
 //////////////////////////////////////////////////////////////////////
@@ -111,7 +124,7 @@ public:
   virtual osg::Group* createAnimationGroup(osg::Group& parent);
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Translate animation installer
 //////////////////////////////////////////////////////////////////////
@@ -129,7 +142,7 @@ private:
   double _initialValue;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Rotate/Spin animation installer
 //////////////////////////////////////////////////////////////////////
@@ -148,7 +161,7 @@ private:
   bool _isSpin;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Scale animation installer
 //////////////////////////////////////////////////////////////////////
@@ -166,7 +179,7 @@ private:
   SGVec3d _center;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // dist scale animation installer
 //////////////////////////////////////////////////////////////////////
@@ -179,7 +192,7 @@ public:
   class Transform;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // dist scale animation installer
 //////////////////////////////////////////////////////////////////////
@@ -193,7 +206,7 @@ public:
   class Transform;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // dist scale animation installer
 //////////////////////////////////////////////////////////////////////
@@ -206,7 +219,7 @@ public:
   class Transform;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Range animation installer
 //////////////////////////////////////////////////////////////////////
@@ -224,7 +237,7 @@ private:
   SGVec2d _initialValue;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Select animation installer
 //////////////////////////////////////////////////////////////////////
@@ -236,7 +249,7 @@ public:
   virtual osg::Group* createAnimationGroup(osg::Group& parent);
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Alpha test animation installer
 //////////////////////////////////////////////////////////////////////
@@ -248,7 +261,7 @@ public:
   virtual void install(osg::Node& node);
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Blend animation installer
 //////////////////////////////////////////////////////////////////////
@@ -265,7 +278,7 @@ private:
   SGSharedPtr<SGExpressiond> _animationValue;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Timed animation installer
 //////////////////////////////////////////////////////////////////////
@@ -279,7 +292,7 @@ private:
   class UpdateCallback;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Shadow animation installer
 //////////////////////////////////////////////////////////////////////
@@ -293,7 +306,7 @@ private:
   class UpdateCallback;
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // TextureTransform animation
 //////////////////////////////////////////////////////////////////////
@@ -314,7 +327,7 @@ private:
                        UpdateCallback* updateCallback);
 };
 
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Shader animation
 //////////////////////////////////////////////////////////////////////
@@ -329,7 +342,7 @@ private:
   class UpdateCallback;
   osg::ref_ptr<osg::Texture2D> _effect_texture;
 };
-\f
+
 //////////////////////////////////////////////////////////////////////
 // Light animation
 //////////////////////////////////////////////////////////////////////
diff --git a/simgear/scene/model/animation_test.cxx b/simgear/scene/model/animation_test.cxx
new file mode 100644 (file)
index 0000000..a072004
--- /dev/null
@@ -0,0 +1,69 @@
+#include "animation.hxx"
+
+#include <cstring>
+#include <iostream>
+
+#define COMPARE(a, b) \
+  if( (a) != (b) ) \
+  { \
+    std::cerr << "line " << __LINE__ << ": failed: "\
+              << #a << " != " << #b\
+              << " [" << (a) << " != " << (b) << "]" << std::endl; \
+    return 1; \
+  }
+
+#define VERIFY(a) \
+  if( !(a) ) \
+  { \
+    std::cerr << "failed: line " << __LINE__ << ": " << #a << std::endl; \
+    return 1; \
+  }
+
+struct AnimationTest:
+  public SGAnimation
+{
+  AnimationTest(const SGPropertyNode* n):
+    SGAnimation(n, 0)
+  {}
+
+  void readConfig()
+  {
+    readRotationCenterAndAxis(center, axis);
+  }
+
+  SGVec3d center,
+          axis;
+};
+
+int main(int argc, char* argv[])
+{
+  SGPropertyNode_ptr config = new SGPropertyNode;
+  AnimationTest anim(config);
+
+  SGVec3d v1(1, 2, 3),
+          v2(-1, 4, 0);
+  config->setDoubleValue("axis/x1-m", v1.x());
+  config->setDoubleValue("axis/y1-m", v1.y());
+  config->setDoubleValue("axis/z1-m", v1.z());
+  config->setDoubleValue("axis/x2-m", v2.x());
+  config->setDoubleValue("axis/y2-m", v2.y());
+  config->setDoubleValue("axis/z2-m", v2.z());
+  anim.readConfig();
+
+  COMPARE(anim.center, (v1 + v2) * 0.5)
+  COMPARE(anim.axis, normalize(v2 - v1))
+
+  config->removeChild("axis", 0, false);
+  config->setDoubleValue("center/x-m", v1.x());
+  config->setDoubleValue("center/y-m", v1.y());
+  config->setDoubleValue("center/z-m", v1.z());
+  config->setDoubleValue("axis/x", v2.x());
+  config->setDoubleValue("axis/y", v2.y());
+  config->setDoubleValue("axis/z", v2.z());
+  anim.readConfig();
+
+  COMPARE(anim.center, v1)
+  COMPARE(anim.axis, normalize(v2))
+
+  return 0;
+}