From: Thomas Geymayer Date: Thu, 25 Apr 2013 21:22:04 +0000 (+0200) Subject: Add trackTo (locked-track) animation X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=559a5d146ab49f70bd10850f495be32086a60645;p=simgear.git Add trackTo (locked-track) animation --- diff --git a/simgear/scene/model/CMakeLists.txt b/simgear/scene/model/CMakeLists.txt index e10994b0..416b74af 100644 --- a/simgear/scene/model/CMakeLists.txt +++ b/simgear/scene/model/CMakeLists.txt @@ -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 diff --git a/simgear/scene/model/SGPickAnimation.cxx b/simgear/scene/model/SGPickAnimation.cxx index ddc41030..8a03538a 100644 --- a/simgear/scene/model/SGPickAnimation.cxx +++ b/simgear/scene/model/SGPickAnimation.cxx @@ -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 index 00000000..95415207 --- /dev/null +++ b/simgear/scene/model/SGTrackToAnimation.cxx @@ -0,0 +1,246 @@ +// TrackTo animation +// +// Copyright (C) 2013 Thomas Geymayer +// +// 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 +#include +#include + +/** + * + */ +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(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 index 00000000..717d9552 --- /dev/null +++ b/simgear/scene/model/SGTrackToAnimation.hxx @@ -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 +// +// 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 + +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_ */ diff --git a/simgear/scene/model/animation.cxx b/simgear/scene/model/animation.cxx index 962fc783..2e1f80fd 100644 --- a/simgear/scene/model/animation.cxx +++ b/simgear/scene/model/animation.cxx @@ -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; - //////////////////////////////////////////////////////////////////////// // Static utility functions. //////////////////////////////////////////////////////////////////////// @@ -206,7 +206,6 @@ read_value(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, return 0; } - //////////////////////////////////////////////////////////////////////// // 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 } - + //////////////////////////////////////////////////////////////////////// // Implementation of null animation //////////////////////////////////////////////////////////////////////// @@ -606,7 +643,7 @@ SGGroupAnimation::createAnimationGroup(osg::Group& parent) return group; } - + //////////////////////////////////////////////////////////////////////// // Implementation of translate animation //////////////////////////////////////////////////////////////////////// @@ -666,7 +703,7 @@ SGTranslateAnimation::createAnimationGroup(osg::Group& parent) return transform; } - + //////////////////////////////////////////////////////////////////////// // 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) } } - + //////////////////////////////////////////////////////////////////////// // Implementation of scale animation //////////////////////////////////////////////////////////////////////// @@ -1069,7 +1079,7 @@ SGScaleAnimation::createAnimationGroup(osg::Group& parent) return transform; } - + // Don't create a new state state everytime we need GL_NORMALIZE! namespace @@ -1215,7 +1225,7 @@ namespace &SGDistScaleAnimation::Transform::writeLocalData ); } - + //////////////////////////////////////////////////////////////////////// // Implementation of flash animation //////////////////////////////////////////////////////////////////////// @@ -1371,7 +1381,7 @@ namespace &SGFlashAnimation::Transform::writeLocalData ); } - + //////////////////////////////////////////////////////////////////////// // Implementation of billboard animation //////////////////////////////////////////////////////////////////////// @@ -1456,7 +1466,7 @@ namespace &SGBillboardAnimation::Transform::writeLocalData ); } - + //////////////////////////////////////////////////////////////////////// // Implementation of a range animation //////////////////////////////////////////////////////////////////////// @@ -1561,7 +1571,7 @@ SGRangeAnimation::createAnimationGroup(osg::Group& parent) return group; } - + //////////////////////////////////////////////////////////////////////// // Implementation of a select animation //////////////////////////////////////////////////////////////////////// @@ -1591,7 +1601,7 @@ SGSelectAnimation::createAnimationGroup(osg::Group& parent) } - + //////////////////////////////////////////////////////////////////////// // Implementation of alpha test animation //////////////////////////////////////////////////////////////////////// @@ -1661,7 +1671,7 @@ SGAlphaTestAnimation::install(osg::Node& node) } } - + ////////////////////////////////////////////////////////////////////// // Blend animation installer ////////////////////////////////////////////////////////////////////// @@ -1780,7 +1790,7 @@ SGBlendAnimation::install(osg::Node& node) node.accept(visitor); } - + ////////////////////////////////////////////////////////////////////// // Timed animation installer ////////////////////////////////////////////////////////////////////// @@ -1891,7 +1901,7 @@ SGTimedAnimation::createAnimationGroup(osg::Group& parent) return sw; } - + //////////////////////////////////////////////////////////////////////// // dynamically switch on/off shadows //////////////////////////////////////////////////////////////////////// @@ -1937,7 +1947,7 @@ SGShadowAnimation::createAnimationGroup(osg::Group& parent) return group; } - + //////////////////////////////////////////////////////////////////////// // Implementation of SGTexTransformAnimation //////////////////////////////////////////////////////////////////////// diff --git a/simgear/scene/model/animation.hxx b/simgear/scene/model/animation.hxx index 9262926e..9716dd21 100644 --- a/simgear/scene/model/animation.hxx +++ b/simgear/scene/model/animation.hxx @@ -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; }; - + ////////////////////////////////////////////////////////////////////// // Null animation installer ////////////////////////////////////////////////////////////////////// @@ -111,7 +124,7 @@ public: virtual osg::Group* createAnimationGroup(osg::Group& parent); }; - + ////////////////////////////////////////////////////////////////////// // Translate animation installer ////////////////////////////////////////////////////////////////////// @@ -129,7 +142,7 @@ private: double _initialValue; }; - + ////////////////////////////////////////////////////////////////////// // Rotate/Spin animation installer ////////////////////////////////////////////////////////////////////// @@ -148,7 +161,7 @@ private: bool _isSpin; }; - + ////////////////////////////////////////////////////////////////////// // Scale animation installer ////////////////////////////////////////////////////////////////////// @@ -166,7 +179,7 @@ private: SGVec3d _center; }; - + ////////////////////////////////////////////////////////////////////// // dist scale animation installer ////////////////////////////////////////////////////////////////////// @@ -179,7 +192,7 @@ public: class Transform; }; - + ////////////////////////////////////////////////////////////////////// // dist scale animation installer ////////////////////////////////////////////////////////////////////// @@ -193,7 +206,7 @@ public: class Transform; }; - + ////////////////////////////////////////////////////////////////////// // dist scale animation installer ////////////////////////////////////////////////////////////////////// @@ -206,7 +219,7 @@ public: class Transform; }; - + ////////////////////////////////////////////////////////////////////// // Range animation installer ////////////////////////////////////////////////////////////////////// @@ -224,7 +237,7 @@ private: SGVec2d _initialValue; }; - + ////////////////////////////////////////////////////////////////////// // Select animation installer ////////////////////////////////////////////////////////////////////// @@ -236,7 +249,7 @@ public: virtual osg::Group* createAnimationGroup(osg::Group& parent); }; - + ////////////////////////////////////////////////////////////////////// // Alpha test animation installer ////////////////////////////////////////////////////////////////////// @@ -248,7 +261,7 @@ public: virtual void install(osg::Node& node); }; - + ////////////////////////////////////////////////////////////////////// // Blend animation installer ////////////////////////////////////////////////////////////////////// @@ -265,7 +278,7 @@ private: SGSharedPtr _animationValue; }; - + ////////////////////////////////////////////////////////////////////// // Timed animation installer ////////////////////////////////////////////////////////////////////// @@ -279,7 +292,7 @@ private: class UpdateCallback; }; - + ////////////////////////////////////////////////////////////////////// // Shadow animation installer ////////////////////////////////////////////////////////////////////// @@ -293,7 +306,7 @@ private: class UpdateCallback; }; - + ////////////////////////////////////////////////////////////////////// // TextureTransform animation ////////////////////////////////////////////////////////////////////// @@ -314,7 +327,7 @@ private: UpdateCallback* updateCallback); }; - + ////////////////////////////////////////////////////////////////////// // Shader animation ////////////////////////////////////////////////////////////////////// @@ -329,7 +342,7 @@ private: class UpdateCallback; osg::ref_ptr _effect_texture; }; - + ////////////////////////////////////////////////////////////////////// // Light animation ////////////////////////////////////////////////////////////////////// diff --git a/simgear/scene/model/animation_test.cxx b/simgear/scene/model/animation_test.cxx new file mode 100644 index 00000000..a072004f --- /dev/null +++ b/simgear/scene/model/animation_test.cxx @@ -0,0 +1,69 @@ +#include "animation.hxx" + +#include +#include + +#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; +}