]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/SGTrackToAnimation.cxx
Extend locked-track animation to support a slave element.
[simgear.git] / simgear / scene / model / SGTrackToAnimation.cxx
1 // TrackTo animation
2 //
3 // Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Library General Public License for more details.
14 //
15 // You should have received a copy of the GNU Library General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
18
19 #include "SGRotateTransform.hxx"
20 #include "SGTrackToAnimation.hxx"
21
22 #include <simgear/scene/util/OsgMath.hxx>
23 #include <osg/Transform>
24 #include <cassert>
25
26 /**
27  *
28  */
29 static osg::NodePath requireNodePath( osg::Node* node,
30                                       osg::Node* haltTraversalAtNode = 0 )
31 {
32   const osg::NodePathList node_paths =
33     node->getParentalNodePaths(haltTraversalAtNode);
34   return node_paths.at(0);
35 }
36
37 /**
38  * Get a subpath of an osg::NodePath
39  *
40  * @param path  Path to extract subpath from
41  * @param start Number of elements to skip from start of #path
42  *
43  * @return Subpath starting with node at position #start
44  */
45 static osg::NodePath subPath( const osg::NodePath& path,
46                               size_t start )
47 {
48   if( start >= path.size() )
49     return osg::NodePath();
50
51   osg::NodePath np(path.size() - start);
52   for(size_t i = start; i < path.size(); ++i)
53     np[i - start] = path[i];
54
55   return np;
56 }
57
58 /**
59  * Visitor to find a group by its name.
60  */
61 class FindGroupVisitor:
62   public osg::NodeVisitor
63 {
64   public:
65
66     FindGroupVisitor(const std::string& name):
67         osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
68         _name(name),
69         _group(0)
70     {
71       if( name.empty() )
72         SG_LOG(SG_IO, SG_WARN, "FindGroupVisitor: empty name provided");
73     }
74
75     osg::Group* getGroup() const
76     {
77       return _group;
78     }
79
80     virtual void apply(osg::Group& group)
81     {
82       if( _name != group.getName() )
83         return traverse(group);
84
85       if( !_group )
86         _group = &group;
87       else
88         SG_LOG(SG_IO, SG_WARN, "FindGroupVisitor: name not unique");
89     }
90
91   protected:
92
93     std::string _name;
94     osg::Group *_group;
95 };
96
97 /**
98  * Get angle of a triangle given by three side lengths
99  *
100  * @return Angle enclosed by @c side1 and @c side2
101  */
102 double angleFromSSS(double side1, double side2, double opposite)
103 {
104   return std::acos
105   (
106     ( SGMiscd::pow<2>(side1)
107     + SGMiscd::pow<2>(side2)
108     - SGMiscd::pow<2>(opposite)
109     ) / (2 * side1 * side2)
110   );
111 }
112
113 //------------------------------------------------------------------------------
114 class SGTrackToAnimation::UpdateCallback:
115   public osg::NodeCallback
116 {
117   public:
118     UpdateCallback( osg::Group* target,
119                     const SGTrackToAnimation* anim,
120                     SGRotateTransform* slave_tf ):
121       _target(target),
122       _slave_tf(slave_tf),
123       _root_length(0),
124       _slave_length(0),
125       _root_initial_angle(0)
126     {
127       setName("SGTrackToAnimation::UpdateCallback");
128
129       _node_center = toOsg( anim->readVec3("center", "-m") );
130       _slave_center = toOsg( anim->readVec3("slave-center", "-m") );
131       _target_center = toOsg( anim->readVec3("target-center", "-m") );
132       _lock_axis = toOsg( anim->readVec3("lock-axis") );
133       _track_axis = toOsg( anim->readVec3("track-axis") );
134
135       if( _lock_axis.normalize() == 0.0 )
136       {
137         anim->log(SG_WARN, "invalid lock-axis");
138         _lock_axis.set(0, 1, 0);
139       }
140
141       if( _slave_center != osg::Vec3() )
142       {
143         _root_length = (_slave_center - _node_center).length();
144         _slave_length = (_target_center - _slave_center).length();
145         double dist = (_target_center - _node_center).length();
146
147         _root_initial_angle = angleFromSSS(_root_length, dist, _slave_length);
148
149         // If no rotation should be applied to the slave element it is looking
150         // in the same direction then the root node. Inside the triangle given
151         // by the root length, slave length and distance from the root node to
152         // the target node, this equals an angle of 180 degrees.
153         _slave_initial_angle = angleFromSSS(_root_length, _slave_length, dist)
154                              - SGMiscd::pi();
155
156         _track_axis = _target_center - _node_center;
157       }
158
159       for(;;)
160       {
161         float proj = _lock_axis * _track_axis;
162         if( proj != 0.0 )
163         {
164           anim->log(SG_WARN, "track-axis not perpendicular to lock-axis");
165
166           // Make tracking axis perpendicular to locked axis
167           _track_axis -= _lock_axis * proj;
168         }
169
170         if( _track_axis.normalize() == 0.0 )
171         {
172           anim->log(SG_WARN, "invalid track-axis");
173           if( std::fabs(_lock_axis.x()) < 0.1 )
174             _track_axis.set(1, 0, 0);
175           else
176             _track_axis.set(0, 1, 0);
177         }
178         else
179           break;
180       }
181
182       _up_axis = _lock_axis ^ _track_axis;
183     }
184
185     virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
186     {
187       SGRotateTransform* tf = static_cast<SGRotateTransform*>(node);
188
189       // We need to wait with this initialization steps until the first update
190       // as this allows us to be sure all animations have already been installed
191       // and are therefore also accounted for calculating the animation.
192       if( _target )
193       {
194         // Get path to animated node and calculated simplified paths to the
195         // nearest common parent node of both the animated node and the target
196         // node
197
198                                   // start at parent to not get false results by
199                                   // also including animation transformation
200         osg::NodePath node_path = requireNodePath(node->getParent(0)),
201                       target_path = requireNodePath(_target);
202         size_t tp_size = target_path.size(),
203                np_size = node_path.size();
204         size_t common_parents = 0;
205
206         for(; common_parents < std::min(tp_size, np_size); ++common_parents)
207         {
208           if( target_path[common_parents] != node_path[common_parents] )
209             break;
210         }
211
212         _node_path = subPath(node_path, common_parents);
213         _target_path = subPath(target_path, common_parents);
214         _target = 0;
215
216         tf->setCenter( toSG(_node_center) );
217         tf->setAxis( toSG(_lock_axis) );
218
219         if( _slave_tf )
220         {
221           _slave_tf->setCenter( toSG(_slave_center) );
222           _slave_tf->setAxis( toSG(_lock_axis) );
223         }
224       }
225
226       osg::Vec3 target_pos = ( osg::computeLocalToWorld(_target_path)
227                              * osg::computeWorldToLocal(_node_path)
228                              ).preMult(_target_center),
229                        dir = target_pos - _node_center;
230
231       double offset = -_root_initial_angle;
232       if( _root_length > 0 )
233       {
234         double dist = dir.length(),
235                slave_angle = -_slave_initial_angle;
236         if( dist < _root_length + _slave_length )
237         {
238           offset += angleFromSSS(_root_length, dist, _slave_length);
239
240           if( _slave_tf )
241             slave_angle += angleFromSSS(_root_length, _slave_length, dist)
242                          - SGMiscd::pi();
243         }
244
245         if( _slave_tf )
246           _slave_tf->setAngleRad(slave_angle);
247       }
248
249       // Ensure direction is perpendicular to lock axis
250       float proj = _lock_axis * dir;
251       if( proj != 0.0 )
252         dir -= _lock_axis * proj;
253
254       float x = dir * _track_axis,
255             y = dir * _up_axis;
256       tf->setAngleRad( std::atan2(y, x) + offset );
257
258       traverse(node, nv);
259     }
260   protected:
261
262     osg::Vec3           _node_center,
263                         _slave_center,
264                         _target_center,
265                         _lock_axis,
266                         _track_axis,
267                         _up_axis;
268     osg::Group         *_target;
269     SGRotateTransform  *_slave_tf;
270     osg::NodePath       _node_path,
271                         _target_path;
272     double              _root_length,
273                         _slave_length,
274                         _root_initial_angle,
275                         _slave_initial_angle;
276 };
277
278 //------------------------------------------------------------------------------
279 SGTrackToAnimation::SGTrackToAnimation( osg::Node* node,
280                                         const SGPropertyNode* configNode,
281                                         SGPropertyNode* modelRoot ):
282   SGAnimation(configNode, modelRoot),
283   _target_group(0),
284   _slave_group(0)
285 {
286   std::string target = configNode->getStringValue("target-name");
287   FindGroupVisitor target_finder(target);
288   node->accept(target_finder);
289
290   if( !(_target_group = target_finder.getGroup()) )
291     log(SG_ALERT, "target not found: '" + target + '\'');
292
293   std::string slave = configNode->getStringValue("slave-name");
294   if( !slave.empty() )
295   {
296     FindGroupVisitor slave_finder(slave);
297     node->accept(slave_finder);
298     _slave_group = slave_finder.getGroup();
299   }
300 }
301
302 //------------------------------------------------------------------------------
303 osg::Group* SGTrackToAnimation::createAnimationGroup(osg::Group& parent)
304 {
305   if( !_target_group )
306     return 0;
307
308   SGRotateTransform* slave_tf = 0;
309   if( _slave_group )
310   {
311     slave_tf = new SGRotateTransform;
312     slave_tf->setName("locked-track slave animation");
313
314     osg::Group* parent = _slave_group->getParent(0);
315     slave_tf->addChild(_slave_group);
316     parent->removeChild(_slave_group);
317     parent->addChild(slave_tf);
318   }
319
320   SGRotateTransform* tf = new SGRotateTransform;
321   tf->setName("locked-track animation");
322   tf->setUpdateCallback(new UpdateCallback(_target_group, this, slave_tf));
323   parent.addChild(tf);
324
325   return tf;
326 }
327
328 //------------------------------------------------------------------------------
329 void SGTrackToAnimation::log(sgDebugPriority p, const std::string& msg) const
330 {
331   SG_LOG
332   (
333     SG_IO,
334     p,
335     // TODO handle multiple object-names?
336     "SGTrackToAnimation(" << getConfig()->getStringValue("object-name") << "): "
337     << msg
338   );
339 }