1 /*
2  * Copyright 2006 Sony Computer Entertainment Inc.
3  *
4  * Licensed under the SCEA Shared Source License, Version 1.0 (the "License"); you may not use this
5  * file except in compliance with the License. You may obtain a copy of the License at:
6  * http://research.scea.com/scea_shared_source_license.html
7  *
8  * Unless required by applicable law or agreed to in writing, software distributed under the License
9  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10  * implied. See the License for the specific language governing permissions and limitations under the
11  * License.
12  */
13 
14 #include "daeWriter.h"
15 
16 #include <dom/domCOLLADA.h>
17 
18 #include <dom/domNode.h>
19 #include <dom/domConstants.h>
20 #include <dom/domLibrary_cameras.h>
21 #include <dom/domLibrary_lights.h>
22 #include <dae/domAny.h>
23 //#include <dom/domVisual_scene.h>
24 //#include <dom/domLibrary_visual_scenes.h>
25 
26 #include <osgSim/MultiSwitch>
27 #include <osgAnimation/AnimationManagerBase>
28 #include <osgAnimation/UpdateMatrixTransform>
29 #include <osg/Sequence>
30 #include <osg/Billboard>
31 #include <osg/CameraView>
32 #include <osgDB/ConvertUTF>
33 
34 
35 using namespace osgDAE;
36 
37 
38 void daeWriter::writeAnimations( osg::Node &node )
39 {
40     const std::string nodeNameUTF( _pluginOptions.namesUseCodepage ? osgDB::convertStringFromCurrentCodePageToUTF8(node.getName()) : node.getName() );
41     osg::Callback* ncb = node.getUpdateCallback();
42     if (ncb)
43     {
44         osgAnimation::AnimationManagerBase* am = dynamic_cast<osgAnimation::AnimationManagerBase*>(ncb);
45         if (am)
46         {
47             // Create library of animations if not existing yet
48             if (!_domLibraryAnimations)
49             {
50                 _domLibraryAnimations = daeSafeCast<domLibrary_animations>( dom->add( COLLADA_ELEMENT_LIBRARY_ANIMATIONS ) );
51             }
52 
53             osgAnimation::AnimationList animationList = am->getAnimationList();
54             for (size_t i = 0; i < animationList.size(); i++)
55             {
56                 domAnimation* pDomAnimation = daeSafeCast< domAnimation >( _domLibraryAnimations->add( COLLADA_ELEMENT_ANIMATION ) );
57                 domAnimation* pMainDomAnimation = pDomAnimation;
58 
59                 osg::ref_ptr<osgAnimation::Animation> animation = animationList[i];
60                 std::string animationName( animation->getName() );
61                 if (animationName.empty())
62                     animationName = "animation";
63                 animationName = uniquify( animationName );
64                 pDomAnimation->setId(animationName.c_str());
65 
66                 // Handle multiple channels within an animation
67                 osgAnimation::ChannelList animationChannels = animation->getChannels();
68                 for (size_t j=0; j < animationChannels.size(); j++)
69                 {
70                     osgAnimation::Channel* channel = animationChannels[j].get();
71                     std::string channelName( channel->getName() );
72                     std::string channelNameUTF( _pluginOptions.namesUseCodepage ? osgDB::convertStringFromCurrentCodePageToUTF8(channelName) : channelName );
73 
74                     // Wrap each animation channel into it's own child <animation> when more than 1 channel
75                     if (animationChannels.size() > 1)
76                     {
77                         pDomAnimation = daeSafeCast< domAnimation >( pMainDomAnimation->add( COLLADA_ELEMENT_ANIMATION ) );
78 
79                         if (channelName.empty())
80                         {
81                             channelName = "channel";
82                             channelNameUTF = channelName;
83                         }
84                         animationName = uniquify( channelName );
85                         pDomAnimation->setId(channelNameUTF.c_str());
86                     }
87 
88                     std::string sourceName( channelNameUTF + "_sampler" );
89                     std::string inputSourceName( channelNameUTF + "_input" );
90                     std::string outputSourceName( channelNameUTF + "_output" );
91                     std::string interpolationSourceName( channelNameUTF + "_interpolation" );
92 
93                     // Fill dom sources based on sampler
94                     osgAnimation::Sampler* animationSampler = channel->getSampler();
95                     osgAnimation::KeyframeContainer* kfc = animationSampler->getKeyframeContainer();
96                     int size = kfc->size();
97 
98                     float valueStride = 1;
99 
100                     domListOfFloats timeValues;
101                     domListOfFloats frameValues;
102                     domListOfNames interpolationValues;
103 
104                     osgAnimation::Vec3KeyframeContainer* v3kc = dynamic_cast<osgAnimation::Vec3KeyframeContainer*>(kfc);
105                     if (v3kc)
106                     {
107                         valueStride = 3;
108                         for (size_t i=0; i < v3kc->size(); i++)
109                         {
110                             timeValues.append((*v3kc)[i].getTime());
111                             osg::Vec3 vec = (*v3kc)[i].getValue();
112 
113                             // This needs some serious cleanup
114                             if (channelNameUTF.find("euler") != std::string::npos)
115                             {
116                                 frameValues.append(osg::RadiansToDegrees(vec.x()));
117                                 frameValues.append(osg::RadiansToDegrees(vec.y()));
118                                 frameValues.append(osg::RadiansToDegrees(vec.z()));
119                             }
120                             else
121                             {
122                                 frameValues.append(vec.x());
123                                 frameValues.append(vec.y());
124                                 frameValues.append(vec.z());
125                             }
126                             interpolationValues.append("LINEAR");
127                         }
128                     }
129                     osgAnimation::FloatKeyframeContainer* fkc = dynamic_cast<osgAnimation::FloatKeyframeContainer*>(kfc);
130                     if (fkc)
131                     {
132                         valueStride = 1;
133                         for (size_t i=0; i < fkc->size(); i++)
134                         {
135                             timeValues.append((*fkc)[i].getTime());
136                             frameValues.append((*fkc)[i].getValue());
137                             interpolationValues.append("LINEAR");
138                         }
139                     }
140 
141                     // time values
142                     domSource* pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
143                     pDomSource->setId(inputSourceName.c_str());
144 
145                     domFloat_array* pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
146                     std::string inputArrayName = inputSourceName + "_array";
147                     pDomFloatArray->setId(inputArrayName.c_str());
148                     pDomFloatArray->setCount(size);
149                     pDomFloatArray->setValue(timeValues);
150 
151                     domSource::domTechnique_common* pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));
152 
153                     domAccessor* pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
154                     std::string url = "#" + inputArrayName;
155                     pDomAccessor->setSource(url.c_str());
156                     pDomAccessor->setCount(size);
157                     pDomAccessor->setStride(1);
158 
159                     domParam* pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
160                     pDomParam->setName("TIME");
161                     pDomParam->setType(COLLADA_TYPE_FLOAT);
162 
163                     // <source> interpolator type
164                     pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
165                     pDomSource->setId(interpolationSourceName.c_str());
166 
167                     domName_array* pDomNameArray = daeSafeCast< domName_array >(pDomSource->add(COLLADA_ELEMENT_NAME_ARRAY));
168                     std::string interpolationArrayName = interpolationSourceName + "_array";
169                     pDomNameArray->setId(interpolationArrayName.c_str());
170                     pDomNameArray->setCount(size);
171                     pDomNameArray->setValue(interpolationValues);
172 
173                     // <technique_common>
174                     pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));
175 
176                     // <accessor count=size stride="1">
177                     pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
178                     url = "#" + interpolationArrayName;
179                     pDomAccessor->setSource(url.c_str());
180                     pDomAccessor->setCount(size);
181                     pDomAccessor->setStride(1);
182 
183                     // <param name="INTERPOLATION" type="name"/>
184                     pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
185                     pDomParam->setName("INTERPOLATION");
186                     pDomParam->setType(COLLADA_TYPE_NAME);
187 
188                     // Split up access to the euler float array into three sources, because we need to target three separate transforms
189                     if (channelNameUTF.find("euler") != std::string::npos)
190                     {
191                         pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
192                         std::string outputSourceNameX = outputSourceName + "_X";
193                         pDomSource->setId(outputSourceNameX.c_str());
194 
195                         pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
196                         std::string outputArrayNameX = outputSourceNameX + "_array";
197                         pDomFloatArray->setId(outputArrayNameX.c_str());
198 
199                         // TODO, flexible handling of different keyframe types, see osg exporter for inspiration
200                         pDomFloatArray->setCount(size * valueStride);
201                         pDomFloatArray->setValue(frameValues);
202 
203                         pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));
204 
205                         pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
206                         url = "#" + outputArrayNameX;
207                         pDomAccessor->setSource(url.c_str());
208                         pDomAccessor->setCount(size);
209                         pDomAccessor->setStride(valueStride);
210                         pDomAccessor->setOffset(0);
211                         // <param name="ANGLE" type="float"/>
212                         pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
213                         pDomParam->setName("ANGLE");
214                         pDomParam->setType(COLLADA_TYPE_FLOAT);
215 
216                         pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
217                         std::string outputSourceNameY = outputSourceName + "_Y";
218                         pDomSource->setId(outputSourceNameY.c_str());
219 
220                         pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
221                         std::string outputArrayNameY = outputSourceNameY + "_array";
222                         pDomFloatArray->setId(outputArrayNameY.c_str());
223 
224                         pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));
225 
226                         pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
227                         url = "#" + outputArrayNameY;
228                         pDomAccessor->setSource(url.c_str());
229                         pDomAccessor->setCount(size);
230                         pDomAccessor->setStride(valueStride);
231                         pDomAccessor->setOffset(1);
232                         // <param name="ANGLE" type="float"/>
233                         pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
234                         pDomParam->setName("ANGLE");
235                         pDomParam->setType(COLLADA_TYPE_FLOAT);
236 
237                         pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
238                         std::string outputSourceNameZ = outputSourceName + "_Z";
239                         pDomSource->setId(outputSourceNameZ.c_str());
240 
241                         pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
242                         std::string outputArrayNameZ = outputSourceNameZ + "_array";
243                         pDomFloatArray->setId(outputArrayNameZ.c_str());
244 
245                         pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));
246 
247                         pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
248                         url = "#" + outputArrayNameZ;
249                         pDomAccessor->setSource(url.c_str());
250                         pDomAccessor->setCount(size);
251                         pDomAccessor->setStride(valueStride);
252                         pDomAccessor->setOffset(2);
253                         // <param name="ANGLE" type="float"/>
254                         pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
255                         pDomParam->setName("ANGLE");
256                         pDomParam->setType(COLLADA_TYPE_FLOAT);
257 
258                         {
259                             domSampler* pDomSampler = daeSafeCast< domSampler >(pDomAnimation->add(COLLADA_ELEMENT_SAMPLER));
260                             std::string sourceNameX = sourceName + "_X";
261                             pDomSampler->setId(sourceNameX.c_str());
262 
263                             // Fill dom sampler based on common semantics
264                             {
265                                 domInputLocal* pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
266                                 pDomInput->setSemantic(COMMON_PROFILE_INPUT_INPUT);
267                                 std::string url = "#" + inputSourceName;
268                                 pDomInput->setSource(url.c_str());
269 
270                                 pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
271                                 pDomInput->setSemantic(COMMON_PROFILE_INPUT_OUTPUT);
272                                 url = "#" + outputSourceNameX;
273                                 pDomInput->setSource(url.c_str());
274 
275                                 pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
276                                 pDomInput->setSemantic(COMMON_PROFILE_INPUT_INTERPOLATION);
277                                 url = "#" + interpolationSourceName;
278                                 pDomInput->setSource(url.c_str());
279                             }
280 
281                             // Set sampler as source
282                             domChannel* pDomChannel = daeSafeCast< domChannel >(pDomAnimation->add(COLLADA_ELEMENT_CHANNEL));
283                             std::string url = "#" + sourceNameX;
284                             pDomChannel->setSource(url.c_str());
285 
286                             // targetName contains the name of the updateCallback
287                             std::string targetName = channel->getTargetName();
288 
289                             // based on the type of updateCallback we can determine the transform element to target eg. "nodeName/translation"
290                             osg::Node* node = _animatedNodeCollector.getTargetNode(targetName);
291                             if (node)
292                             {
293                                 std::string domChannelTargetName = nodeNameUTF + "/rotateX.ANGLE";
294                                 pDomChannel->setTarget(domChannelTargetName.c_str());
295                             }
296                             else
297                             {
298                                 OSG_WARN << "Could not find animation target '" << targetName << "'" << std::endl;
299                             }
300                         }
301 
302                         {
303                             domSampler* pDomSampler = daeSafeCast< domSampler >(pDomAnimation->add(COLLADA_ELEMENT_SAMPLER));
304                             std::string sourceNameY = sourceName + "_Y";
305                             pDomSampler->setId(sourceNameY.c_str());
306 
307                             // Fill dom sampler based on common semantics
308                             {
309                                 domInputLocal* pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
310                                 pDomInput->setSemantic(COMMON_PROFILE_INPUT_INPUT);
311                                 std::string url = "#" + inputSourceName;
312                                 pDomInput->setSource(url.c_str());
313 
314                                 pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
315                                 pDomInput->setSemantic(COMMON_PROFILE_INPUT_OUTPUT);
316                                 url = "#" + outputSourceNameY;
317                                 pDomInput->setSource(url.c_str());
318 
319                                 pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
320                                 pDomInput->setSemantic(COMMON_PROFILE_INPUT_INTERPOLATION);
321                                 url = "#" + interpolationSourceName;
322                                 pDomInput->setSource(url.c_str());
323                             }
324 
325                             // Set sampler as source
326                             domChannel* pDomChannel = daeSafeCast< domChannel >(pDomAnimation->add(COLLADA_ELEMENT_CHANNEL));
327                             std::string url = "#" + sourceNameY;
328                             pDomChannel->setSource(url.c_str());
329 
330                             // targetName contains the name of the updateCallback
331                             std::string targetName = channel->getTargetName();
332 
333                             // based on the type of updateCallback we can determine the transform element to target eg. "nodeName/translation"
334                             osg::Node* node = _animatedNodeCollector.getTargetNode(targetName);
335                             if (node)
336                             {
337                                 std::string domChannelTargetName = nodeNameUTF + "/rotateY.ANGLE";
338                                 pDomChannel->setTarget(domChannelTargetName.c_str());
339                             }
340                             else
341                             {
342                                 OSG_WARN << "Could not find animation target '" << targetName << "'" << std::endl;
343                             }
344                         }
345 
346                         {
347                             domSampler* pDomSampler = daeSafeCast< domSampler >(pDomAnimation->add(COLLADA_ELEMENT_SAMPLER));
348                             std::string sourceNameZ = sourceName + "_Z";
349                             pDomSampler->setId(sourceNameZ.c_str());
350 
351                             // Fill dom sampler based on common semantics
352                             {
353                                 domInputLocal* pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
354                                 pDomInput->setSemantic(COMMON_PROFILE_INPUT_INPUT);
355                                 std::string url = "#" + inputSourceName;
356                                 pDomInput->setSource(url.c_str());
357 
358                                 pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
359                                 pDomInput->setSemantic(COMMON_PROFILE_INPUT_OUTPUT);
360                                 url = "#" + outputSourceNameZ;
361                                 pDomInput->setSource(url.c_str());
362 
363                                 pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
364                                 pDomInput->setSemantic(COMMON_PROFILE_INPUT_INTERPOLATION);
365                                 url = "#" + interpolationSourceName;
366                                 pDomInput->setSource(url.c_str());
367                             }
368 
369                             // Set sampler as source
370                             domChannel* pDomChannel = daeSafeCast< domChannel >(pDomAnimation->add(COLLADA_ELEMENT_CHANNEL));
371                             std::string url = "#" + sourceNameZ;
372                             pDomChannel->setSource(url.c_str());
373 
374                             // targetName contains the name of the updateCallback
375                             std::string targetName = channel->getTargetName();
376 
377                             // based on the type of updateCallback we can determine the transform element to target eg. "nodeName/translation"
378                             osg::Node* node = _animatedNodeCollector.getTargetNode(targetName);
379                             if (node)
380                             {
381                                 std::string domChannelTargetName = nodeNameUTF + "/rotateZ.ANGLE";
382                                 pDomChannel->setTarget(domChannelTargetName.c_str());
383                             }
384                             else
385                             {
386                                 OSG_WARN << "Could not find animation target '" << targetName << "'" << std::endl;
387                             }
388                         }
389                     }
390                     else
391                     {
392                         // values in keyframecontainer
393                         pDomSource = daeSafeCast< domSource >(pDomAnimation->add(COLLADA_ELEMENT_SOURCE));
394                         pDomSource->setId(outputSourceName.c_str());
395 
396                         pDomFloatArray = daeSafeCast< domFloat_array >(pDomSource->add(COLLADA_ELEMENT_FLOAT_ARRAY));
397                         std::string outputArrayName = outputSourceName + "_array";
398                         pDomFloatArray->setId(outputArrayName.c_str());
399 
400                         // TODO, flexible handling of different keyframe types, see osg exporter for inspiration
401                         pDomFloatArray->setCount(size * valueStride);
402                         pDomFloatArray->setValue(frameValues);
403 
404                         pDomSourceTechniqueCommon = daeSafeCast< domSource::domTechnique_common >(pDomSource->add(COLLADA_ELEMENT_TECHNIQUE_COMMON));
405 
406                         pDomAccessor = daeSafeCast< domAccessor >(pDomSourceTechniqueCommon->add(COLLADA_ELEMENT_ACCESSOR));
407                         url = "#" + outputArrayName;
408                         pDomAccessor->setSource(url.c_str());
409                         pDomAccessor->setCount(size);
410                         pDomAccessor->setStride(valueStride);
411 
412                         if (v3kc)
413                         {
414                             // <param name="X" type="float"/>
415                             pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
416                             pDomParam->setName("X");
417                             pDomParam->setType(COLLADA_TYPE_FLOAT);
418                             // <param name="Y" type="float"/>
419                             pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
420                             pDomParam->setName("Y");
421                             pDomParam->setType(COLLADA_TYPE_FLOAT);
422                             // <param name="Z" type="float"/>
423                             pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
424                             pDomParam->setName("Z");
425                             pDomParam->setType(COLLADA_TYPE_FLOAT);
426                         }
427                         if (fkc)
428                         {
429                             // <param type="float"/>
430                             pDomParam = daeSafeCast< domParam >(pDomAccessor->add(COLLADA_ELEMENT_PARAM));
431                             pDomParam->setType(COLLADA_TYPE_FLOAT);
432                         }
433 
434                         domSampler* pDomSampler = daeSafeCast< domSampler >(pDomAnimation->add(COLLADA_ELEMENT_SAMPLER));
435                         pDomSampler->setId(sourceName.c_str());
436 
437                         // Fill dom sampler based on common semantics
438                         {
439                             domInputLocal* pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
440                             pDomInput->setSemantic(COMMON_PROFILE_INPUT_INPUT);
441                             std::string url = "#" + inputSourceName;
442                             pDomInput->setSource(url.c_str());
443 
444                             pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
445                             pDomInput->setSemantic(COMMON_PROFILE_INPUT_OUTPUT);
446                             url = "#" + outputSourceName;
447                             pDomInput->setSource(url.c_str());
448 
449                             pDomInput = daeSafeCast< domInputLocal >(pDomSampler->add(COLLADA_ELEMENT_INPUT));
450                             pDomInput->setSemantic(COMMON_PROFILE_INPUT_INTERPOLATION);
451                             url = "#" + interpolationSourceName;
452                             pDomInput->setSource(url.c_str());
453                         }
454 
455                         // Set sampler as source
456                         domChannel* pDomChannel = daeSafeCast< domChannel >(pDomAnimation->add(COLLADA_ELEMENT_CHANNEL));
457                         std::string url = "#" + sourceName;
458                         pDomChannel->setSource(url.c_str());
459 
460                         // targetName contains the name of the updateCallback
461                         std::string targetName = channel->getTargetName();
462 
463                         // based on the type of updateCallback we can determine the transform element to target eg. "nodeName/translation"
464                         osg::Node* node = _animatedNodeCollector.getTargetNode(targetName);
465                         if (node)
466                         {
467                             std::string domChannelTargetName = nodeNameUTF;
468 
469                             if (channelNameUTF.find("position") != std::string::npos)
470                             {
471                                 domChannelTargetName += "/translate";
472                             }
473                             else if (channelNameUTF.find("scale") != std::string::npos)
474                             {
475                                 domChannelTargetName += "/scale";
476                             }
477 
478                             pDomChannel->setTarget(domChannelTargetName.c_str());
479                         }
480                         else
481                         {
482                             OSG_WARN << "Could not find animation target '" << targetName << "'" << std::endl;
483                         }
484                     }
485                 }
486             }
487         }
488     }
489 }
490 
491