diff --git a/simgear/scene/model/SGTrackToAnimation.cxx b/simgear/scene/model/SGTrackToAnimation.cxx index 20f55e11..38a79b7d 100644 --- a/simgear/scene/model/SGTrackToAnimation.cxx +++ b/simgear/scene/model/SGTrackToAnimation.cxx @@ -94,18 +94,40 @@ class FindGroupVisitor: osg::Group *_group; }; +/** + * Get angle of a triangle given by three side lengths + * + * @return Angle enclosed by @c side1 and @c side2 + */ +double angleFromSSS(double side1, double side2, double opposite) +{ + return std::acos + ( + ( SGMiscd::pow<2>(side1) + + SGMiscd::pow<2>(side2) + - SGMiscd::pow<2>(opposite) + ) / (2 * side1 * side2) + ); +} + //------------------------------------------------------------------------------ class SGTrackToAnimation::UpdateCallback: public osg::NodeCallback { public: UpdateCallback( osg::Group* target, - const SGTrackToAnimation* anim ): - _target(target) + const SGTrackToAnimation* anim, + SGRotateTransform* slave_tf ): + _target(target), + _slave_tf(slave_tf), + _root_length(0), + _slave_length(0), + _root_initial_angle(0) { setName("SGTrackToAnimation::UpdateCallback"); _node_center = toOsg( anim->readVec3("center", "-m") ); + _slave_center = toOsg( anim->readVec3("slave-center", "-m") ); _target_center = toOsg( anim->readVec3("target-center", "-m") ); _lock_axis = toOsg( anim->readVec3("lock-axis") ); _track_axis = toOsg( anim->readVec3("track-axis") ); @@ -116,6 +138,24 @@ class SGTrackToAnimation::UpdateCallback: _lock_axis.set(0, 1, 0); } + if( _slave_center != osg::Vec3() ) + { + _root_length = (_slave_center - _node_center).length(); + _slave_length = (_target_center - _slave_center).length(); + double dist = (_target_center - _node_center).length(); + + _root_initial_angle = angleFromSSS(_root_length, dist, _slave_length); + + // If no rotation should be applied to the slave element it is looking + // in the same direction then the root node. Inside the triangle given + // by the root length, slave length and distance from the root node to + // the target node, this equals an angle of 180 degrees. + _slave_initial_angle = angleFromSSS(_root_length, _slave_length, dist) + - SGMiscd::pi(); + + _track_axis = _target_center - _node_center; + } + for(;;) { float proj = _lock_axis * _track_axis; @@ -146,6 +186,9 @@ class SGTrackToAnimation::UpdateCallback: { SGRotateTransform* tf = static_cast(node); + // We need to wait with this initialization steps until the first update + // as this allows us to be sure all animations have already been installed + // and are therefore also accounted for calculating the animation. if( _target ) { // Get path to animated node and calculated simplified paths to the @@ -172,6 +215,12 @@ class SGTrackToAnimation::UpdateCallback: tf->setCenter( toSG(_node_center) ); tf->setAxis( toSG(_lock_axis) ); + + if( _slave_tf ) + { + _slave_tf->setCenter( toSG(_slave_center) ); + _slave_tf->setAxis( toSG(_lock_axis) ); + } } osg::Vec3 target_pos = ( osg::computeLocalToWorld(_target_path) @@ -179,6 +228,24 @@ class SGTrackToAnimation::UpdateCallback: ).preMult(_target_center), dir = target_pos - _node_center; + double offset = -_root_initial_angle; + if( _root_length > 0 ) + { + double dist = dir.length(), + slave_angle = -_slave_initial_angle; + if( dist < _root_length + _slave_length ) + { + offset += angleFromSSS(_root_length, dist, _slave_length); + + if( _slave_tf ) + slave_angle += angleFromSSS(_root_length, _slave_length, dist) + - SGMiscd::pi(); + } + + if( _slave_tf ) + _slave_tf->setAngleRad(slave_angle); + } + // Ensure direction is perpendicular to lock axis float proj = _lock_axis * dir; if( proj != 0.0 ) @@ -186,19 +253,26 @@ class SGTrackToAnimation::UpdateCallback: float x = dir * _track_axis, y = dir * _up_axis; - tf->setAngleRad( std::atan2(y, x) ); + tf->setAngleRad( std::atan2(y, x) + offset ); 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; + + osg::Vec3 _node_center, + _slave_center, + _target_center, + _lock_axis, + _track_axis, + _up_axis; + osg::Group *_target; + SGRotateTransform *_slave_tf; + osg::NodePath _node_path, + _target_path; + double _root_length, + _slave_length, + _root_initial_angle, + _slave_initial_angle; }; //------------------------------------------------------------------------------ @@ -206,7 +280,8 @@ SGTrackToAnimation::SGTrackToAnimation( osg::Node* node, const SGPropertyNode* configNode, SGPropertyNode* modelRoot ): SGAnimation(configNode, modelRoot), - _target_group(0) + _target_group(0), + _slave_group(0) { std::string target = configNode->getStringValue("target-name"); FindGroupVisitor target_finder(target); @@ -214,6 +289,14 @@ SGTrackToAnimation::SGTrackToAnimation( osg::Node* node, if( !(_target_group = target_finder.getGroup()) ) log(SG_ALERT, "target not found: '" + target + '\''); + + std::string slave = configNode->getStringValue("slave-name"); + if( !slave.empty() ) + { + FindGroupVisitor slave_finder(slave); + node->accept(slave_finder); + _slave_group = slave_finder.getGroup(); + } } //------------------------------------------------------------------------------ @@ -222,11 +305,24 @@ 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; + SGRotateTransform* slave_tf = 0; + if( _slave_group ) + { + slave_tf = new SGRotateTransform; + slave_tf->setName("locked-track slave animation"); + + osg::Group* parent = _slave_group->getParent(0); + slave_tf->addChild(_slave_group); + parent->removeChild(_slave_group); + parent->addChild(slave_tf); + } + + SGRotateTransform* tf = new SGRotateTransform; + tf->setName("locked-track animation"); + tf->setUpdateCallback(new UpdateCallback(_target_group, this, slave_tf)); + parent.addChild(tf); + + return tf; } //------------------------------------------------------------------------------ diff --git a/simgear/scene/model/SGTrackToAnimation.hxx b/simgear/scene/model/SGTrackToAnimation.hxx index 717d9552..96445dec 100644 --- a/simgear/scene/model/SGTrackToAnimation.hxx +++ b/simgear/scene/model/SGTrackToAnimation.hxx @@ -24,6 +24,12 @@ #include +/** + * Animation to let an object always track another object. An optional second + * slave object can be specified which is rotate to always fit the space between + * the root object and the target object. This can be used to eg. create a gear + * scissor animation. + */ class SGTrackToAnimation: public SGAnimation { @@ -37,7 +43,8 @@ class SGTrackToAnimation: protected: class UpdateCallback; - osg::Group *_target_group; + osg::Group *_target_group, + *_slave_group; void log(sgDebugPriority p, const std::string& msg) const; }; diff --git a/simgear/scene/model/animation.cxx b/simgear/scene/model/animation.cxx index 2e1f80fd..180b1851 100644 --- a/simgear/scene/model/animation.cxx +++ b/simgear/scene/model/animation.cxx @@ -423,7 +423,7 @@ 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") { + } else if (type == "locked-track") { SGTrackToAnimation animInst(node, configNode, modelRoot); animInst.apply(node); } else if (type == "translate") { diff --git a/simgear/scene/model/animation.hxx b/simgear/scene/model/animation.hxx index 9716dd21..5afc8c19 100644 --- a/simgear/scene/model/animation.hxx +++ b/simgear/scene/model/animation.hxx @@ -70,7 +70,7 @@ protected: */ SGVec3d readVec3( const std::string& name, const std::string& suffix = "", - const SGVec3d& def = SGVec3d() ) const; + const SGVec3d& def = SGVec3d::zeros() ) const; void readRotationCenterAndAxis(SGVec3d& center, SGVec3d& axis) const; void removeMode(osg::Node& node, osg::StateAttribute::GLMode mode);