1 /*
2     Copyright (c) 2008-2009 NetAllied Systems GmbH
3 
4 	This file is part of COLLADAMaya.
5 
6     Portions of the code are:
7     Copyright (c) 2005-2007 Feeling Software Inc.
8     Copyright (c) 2005-2007 Sony Computer Entertainment America
9     Copyright (c) 2004-2005 Alias Systems Corp.
10 
11     Licensed under the MIT Open Source License,
12     for details please see LICENSE file or the website
13     http://www.opensource.org/licenses/mit-license.php
14 */
15 
16 #include "COLLADAMayaStableHeaders.h"
17 
18 #include <maya/MAnimUtil.h>
19 #include <maya/MAnimControl.h>
20 #include <maya/MFnMatrixData.h>
21 
22 #include "COLLADAMayaExportOptions.h"
23 #include "COLLADAMayaAnimationSampleCache.h"
24 #include "COLLADAMayaAnimationTools.h"
25 #include "COLLADAMayaAnimationHelper.h"
26 #include "COLLADAMayaDagHelper.h"
27 #include "COLLADAMayaSyntax.h"
28 
29 namespace COLLADAMaya
30 {
31 
32     std::vector<float> AnimationHelper::mSamplingTimes;
33 
34     // -------------------------------------------
getAnimatingNode(const MPlug & plug)35     MObject AnimationHelper::getAnimatingNode ( const MPlug& plug )
36     {
37         // Early withdrawal: check for no direct connections on plug
38         MObject animating = DagHelper::getSourceNodeConnectedTo ( plug );
39         if ( animating.isNull() ) return animating;
40 
41         // By-pass any unit conversion nodes
42         while ( animating.hasFn ( MFn::kUnitConversion ) )
43         {
44             animating = DagHelper::getSourceNodeConnectedTo ( animating, ATTR_INPUt );
45         }
46 
47         return animating;
48     }
49 
50     // -------------------------------------------
51     // Figure out whether a given plug is animated
isAnimated(AnimationSampleCache * acache,const MObject & node,const String & attribute)52     AnimationResult AnimationHelper::isAnimated (
53         AnimationSampleCache *acache,
54         const MObject& node,
55         const String& attribute )
56     {
57         MStatus status;
58         MPlug p = MFnDependencyNode ( node ).findPlug ( attribute.c_str(), &status );
59         return ( status != MStatus::kSuccess ) ? kISANIM_None : isAnimated ( acache, p );
60     }
61 
62     // -------------------------------------------
isAnimated(AnimationSampleCache * cache,const MPlug & plug)63     AnimationResult AnimationHelper::isAnimated (
64         AnimationSampleCache* cache,
65         const MPlug& plug )
66     {
67         // First check for sampling in our cache -- if this exists, it overrides
68         // all other considerations.  We could have sampling on a node without any
69         // connections (for example, IK driven nodes).
70         bool animated;
71         if ( cache->findCachePlug ( plug, animated ) )
72         {
73             return ( !animated ) ? kISANIM_None : kISANIM_Sample;
74         }
75         else
76         {
77             // Get the connected animating object
78             MObject connectedNode = getAnimatingNode ( plug );
79             if ( connectedNode == MObject::kNullObj ) return kISANIM_None;
80             else if ( connectedNode.hasFn ( MFn::kAnimCurve ) )
81             {
82                 MFnAnimCurve curveFn ( connectedNode );
83                 AnimationResult result = curveFn.numKeys() >= 1 ? kISANIM_Curve : kISANIM_None;
84 
85                 // The animCurve is considered to be static if it would return
86                 // the same value regardless of the evaluation time.
87                 // This basically means that the values of all the keys are
88                 // the same and the y component of all the tangents is 0.
89                 if ( ExportOptions::removeStaticCurves() )
90                 {
91                     if ( result == kISANIM_Curve && curveFn.isStatic() )
92                         result = kISANIM_None;
93                 }
94 
95                 return result;
96             }
97             else if ( connectedNode.hasFn ( MFn::kCharacter ) )
98             {
99                 return kISANIM_Character;
100             }
101         }
102         return kISANIM_None;
103     }
104 
105     class SamplingInterval
106     {
107 
108     public:
109         float start;
110         float end;
111         float period;
112     };
113 
114     // -------------------------------------------
115     // set the sampling function from the UI/command line.
116     // Returns validity of the function
setSamplingFunction(const MFloatArray & function)117     bool AnimationHelper::setSamplingFunction ( const MFloatArray& function )
118     {
119         std::vector<SamplingInterval> parsedFunction;
120 
121         // Order and parse the given floats as a function
122         uint elementCount = function.length();
123         if ( elementCount > 1 && elementCount % 3 != 0 ) return false;
124 
125         if ( elementCount == 0 )
126         {
127             generateSamplingFunction();
128             return true;
129         }
130         else if ( elementCount == 1 )
131         {
132             SamplingInterval interval;
133             interval.start = ( float ) animationStartTime().as ( MTime::kSeconds );
134             interval.end = ( float ) animationEndTime().as ( MTime::kSeconds );
135             interval.period = function[0];
136             parsedFunction.push_back ( interval );
137         }
138         else
139         {
140             uint intervalCount = elementCount / 3;
141             parsedFunction.resize ( intervalCount );
142             for ( uint i = 0; i < intervalCount; ++i )
143             {
144                 SamplingInterval& interval = parsedFunction[i];
145                 interval.start = function[i * 3];
146                 interval.end = function[i * 3 + 1];
147                 interval.period = function[i * 3 + 2];
148             }
149         }
150 
151         // Check for a valid sampling function
152         uint parsedFunctionSize = ( uint ) parsedFunction.size();
153         for ( uint i = 0; i < parsedFunctionSize; ++i )
154         {
155             SamplingInterval& interval = parsedFunction[i];
156             if ( interval.end <= interval.start ) return false;
157             if ( interval.period > interval.end - interval.start ) return false;
158             if ( i > 0 && parsedFunction[i - 1].end > interval.start ) return false;
159             if ( interval.period <= 0.0f ) return false;
160         }
161 
162         // Gather the sampling times
163         mSamplingTimes.clear();
164 
165         float previousTime = ( float ) animationStartTime().as ( MTime::kSeconds );
166         float previousPeriodTakenRatio = 1.0f;
167 
168         for ( std::vector<SamplingInterval>::iterator it = parsedFunction.begin(); it != parsedFunction.end(); ++it )
169         {
170             SamplingInterval& interval = ( *it );
171             float time = interval.start;
172             if ( time == previousTime )
173             {
174                 // Continuity in the sampling, calculate overlap start time
175                 time = time + ( 1.0f - previousPeriodTakenRatio ) * interval.period;
176             }
177 
178             for ( ; time <= interval.end - interval.period + FLT_TOLERANCE; time += interval.period )
179             {
180                 mSamplingTimes.push_back ( time );
181             }
182 
183             mSamplingTimes.push_back ( time );
184 
185             previousTime = interval.end;
186             previousPeriodTakenRatio = ( interval.end - time ) / interval.period;
187         }
188 
189         return true;
190     }
191 
192     // -------------------------------------------
generateSamplingFunction()193     void AnimationHelper::generateSamplingFunction()
194     {
195         mSamplingTimes.clear();
196 
197         // Avoid any potential precision accumulation problems by using the MTime class as an iterator
198         MTime startT = animationStartTime();
199         MTime endT = animationEndTime();
200         for ( MTime currentT = startT; currentT <= endT; ++currentT )
201         {
202             mSamplingTimes.push_back ( ( float ) currentT.as ( MTime::kSeconds ) );
203         }
204     }
205 
206     // -------------------------------------------
207     // Sample animated values for a given plug
sampleAnimatedPlug(AnimationSampleCache * cache,const MPlug & plug,AnimationCurve * curve,ConversionFunctor * converter)208     bool AnimationHelper::sampleAnimatedPlug (
209         AnimationSampleCache* cache,
210         const MPlug& plug,
211         AnimationCurve* curve,
212         ConversionFunctor* converter )
213     {
214         MStatus status;
215         if ( cache == NULL ) return false;
216 
217         std::vector<float>* inputs = NULL;
218         std::vector<float>* outputs = NULL;
219 		std::vector< std::pair<bool, Step> >* interpolation = NULL;
220 
221         // Buffer temporarly the inputs and outputs, so they can be sorted
222         if ( !cache->findCachePlug ( plug, inputs, outputs, interpolation ) || inputs == NULL || outputs == NULL || interpolation == NULL) return false;
223 
224         uint inputCount = ( uint ) inputs->size();
225 
226         // Look for a matching plug in the animation cache...
227         if ( ExportOptions::curveConstrainSampling() )
228         {
229             // Drop the keys and their outputs that don't belong to the attached animation curve
230             MFnAnimCurve curveFn ( plug, &status );
231             if ( status == MStatus::kSuccess && curveFn.numKeys() > 0 )
232             {
233                 float startTime = ( float ) curveFn.time ( 0 ).as ( MTime::kSeconds );
234                 float endTime = ( float ) curveFn.time ( curveFn.numKeys() - 1 ).as ( MTime::kSeconds );
235                 uint count = 0;
236 
237                 // To avoid memory re-allocations, start by counting the number of keys that are within the curve
238                 for ( uint i = 0; i < inputCount; ++i )
239                 {
240                     if ( inputs->at ( i ) + FLT_TOLERANCE >= startTime && inputs->at ( i ) - FLT_TOLERANCE <= endTime ) ++count;
241                 }
242                 curve->setKeyCount ( count, COLLADASW::LibraryAnimations::LINEAR );
243 
244                 // Copy over the keys belonging to the curve's timeframe
245                 for ( uint i = 0; i < inputCount; ++i )
246                 {
247                     if ( inputs->at ( i ) + FLT_TOLERANCE >= startTime && inputs->at ( i ) - FLT_TOLERANCE <= endTime )
248                     {
249                         curve->getKey ( i )->input = inputs->at ( i );
250                         curve->getKey ( i )->output = outputs->at ( i );
251                     }
252                 }
253             }
254             else if ( status != MStatus::kSuccess )
255             {
256                 // No curve found, so use the sampled inputs/outputs directly
257                 curve->setKeyCount ( inputs->size(), COLLADASW::LibraryAnimations::LINEAR );
258                 for ( uint i = 0; i < inputCount; ++i )
259                 {
260                     curve->getKey ( i )->input = inputs->at ( i );
261                     curve->getKey ( i )->output = outputs->at ( i );
262                 }
263             }
264         }
265         else
266         {
267             curve->setKeyCount ( inputs->size(), COLLADASW::LibraryAnimations::LINEAR );
268             for ( uint i = 0; i < inputCount; ++i )
269             {
270                 curve->getKey ( i )->input = inputs->at ( i );
271                 curve->getKey ( i )->output = outputs->at ( i );
272             }
273         }
274 
275         // Convert the samples
276         if ( converter != NULL )
277         {
278             curve->convertValues ( converter, converter );
279         }
280 
281         return true;
282     }
283 
284     // -------------------------------------------
sampleAnimatedTransform(AnimationSampleCache * cache,const MPlug & plug,AnimationCurveList & curves)285     bool AnimationHelper::sampleAnimatedTransform (
286         AnimationSampleCache* cache,
287         const MPlug& plug,
288         AnimationCurveList& curves )
289     {
290         if ( curves.size() != 16 ) return false;
291 
292 		std::vector<float>* inputs = NULL, *outputs = NULL;
293 		std::vector< std::pair<bool, Step> >* interpolation = NULL;
294 
295 		if (!cache->findCachePlug(plug, inputs, outputs, interpolation) || inputs == NULL || outputs == NULL || interpolation == NULL)
296             return false;
297 
298         size_t keyCount = inputs->size();
299         for ( size_t i = 0; i < 16; ++i )
300         {
301             curves[i]->setKeyCount ( keyCount, COLLADASW::LibraryAnimations::LINEAR );
302             for ( size_t j = 0; j < keyCount; ++j )
303             {
304                 AnimationKey* key = ( AnimationKey* ) curves[i]->getKey ( j );
305                 key->input = inputs->at ( j );
306                 key->output = outputs->at ( j*16 + i );
307 
308 				if (interpolation->at(j).first)
309 				{
310 					key->interpolation = COLLADASW::LibraryAnimations::STEP;
311 					key->transformTypeStep._transform = interpolation->at(j).second._transform;
312 
313 					for (int k = 0; k < 9; k++)
314 						key->transformTypeStep._type[k] = interpolation->at(j).second._type[k];
315 
316 					key->transformTypeStep._order = curves[i]->getParent()->getOrder();
317 				}
318 
319                 // Either here with flag "convertUnits" or in method
320                 // AnimationExporter::exportAnimationSource ( AnimationMultiCurve &animationCurve )
321 //                 if ( (i+1)%4 == 0 && (i+1) < 16 )
322 //                     key->output = MDistance::internalToUI ( outputs->at ( j*16 + i ) );
323 //                 else
324 //                     key->output = outputs->at ( j*16 + i );
325             }
326         }
327 
328         return true;
329     }
330 
331     // Since Maya 5.0 doesn't support MAnimControl::animation[Start/End]Time(), wrap it with the MEL command
332     //
333     // [JHoerner]: use minTime/maxTime rather than animationStartTime/animationEndTime.  Usually our artists
334     // use the former (narrower) range of the time slider and put "junk" beyond the edges.
335 #define TSTART "animationStartTime"
336 #define TEND   "animationEndTime"
337     //#define TSTART "minTime"
338     //#define TEND   "maxTime"
339 
340     // -------------------------------------------
animationStartTime()341     MTime AnimationHelper::animationStartTime()
342     {
343         MTime time ( MAnimControl::currentTime() );
344         double result;
345         MGlobal::executeCommand ( "playbackOptions -q -" TSTART, result );
346         time.setValue ( result );
347         return time;
348     }
349 
350     // -------------------------------------------
animationEndTime()351     MTime AnimationHelper::animationEndTime()
352     {
353         MTime time ( MAnimControl::currentTime() );
354         double result;
355         MGlobal::executeCommand ( "playbackOptions -q -" TEND, result );
356         time.setValue ( result );
357         return time;
358     }
359 
360     // -------------------------------------------
setAnimationStartTime(float _time)361     void AnimationHelper::setAnimationStartTime ( float _time )
362     {
363         MTime time ( _time, MTime::kSeconds );
364         double t = time.as ( MTime::uiUnit() );
365         MGlobal::executeCommand ( MString ( "playbackOptions -" TSTART " " ) + t );
366     }
367 
368     // -------------------------------------------
setAnimationEndTime(float _time)369     void AnimationHelper::setAnimationEndTime ( float _time )
370     {
371         MTime time ( _time, MTime::kSeconds );
372         double t = time.as ( MTime::uiUnit() );
373         MGlobal::executeCommand ( MString ( "playbackOptions -" TEND " " ) + t );
374     }
375 
376     // -------------------------------------------
getCurrentTime(MTime & time)377     void AnimationHelper::getCurrentTime ( MTime& time )
378     {
379         time = MAnimControl::currentTime();
380     }
381 
382     // -------------------------------------------
setCurrentTime(const MTime & time)383     void AnimationHelper::setCurrentTime ( const MTime& time )
384     {
385         MAnimControl::setCurrentTime ( time );
386     }
387 
388     // -------------------------------------------
getTargetedPlug(MPlug parentPlug,int index)389     MPlug AnimationHelper::getTargetedPlug ( MPlug parentPlug, int index )
390     {
391         if ( index >= 0 && parentPlug.isCompound() )
392         {
393             return parentPlug.child ( index );
394         }
395         else if ( index >= 0 && parentPlug.isArray() )
396         {
397             return parentPlug.elementByLogicalIndex ( index );
398         }
399         else return parentPlug;
400     }
401 
402     // -------------------------------------------
403     // Interpolation Type Handling
404     //
toInterpolation(MFnAnimCurve::TangentType outType)405     COLLADASW::LibraryAnimations::InterpolationType AnimationHelper::toInterpolation ( MFnAnimCurve::TangentType outType )
406     {
407         switch ( outType )
408         {
409         case MFnAnimCurve::kTangentGlobal:
410             return COLLADASW::LibraryAnimations::BEZIER;
411 
412         case MFnAnimCurve::kTangentFixed:
413             return COLLADASW::LibraryAnimations::BEZIER;
414 
415         case MFnAnimCurve::kTangentLinear:
416             return COLLADASW::LibraryAnimations::LINEAR;
417 
418         case MFnAnimCurve::kTangentFlat:
419             return COLLADASW::LibraryAnimations::BEZIER;
420 
421         case MFnAnimCurve::kTangentSmooth:
422             return COLLADASW::LibraryAnimations::BEZIER;
423 
424         case MFnAnimCurve::kTangentStep:
425             return COLLADASW::LibraryAnimations::STEP;
426 
427 		case MFnAnimCurve::kTangentStepNext:
428 			return COLLADASW::LibraryAnimations::STEP_NEXT;
429 
430         case MFnAnimCurve::kTangentClamped:
431             return COLLADASW::LibraryAnimations::BEZIER;
432 
433         default:
434             return COLLADASW::LibraryAnimations::BEZIER;
435         }
436     }
437 
438     // -------------------------------------------
toTangentType(COLLADASW::LibraryAnimations::InterpolationType type)439     MFnAnimCurve::TangentType AnimationHelper::toTangentType ( COLLADASW::LibraryAnimations::InterpolationType type )
440     {
441         switch ( type )
442         {
443 
444         case COLLADASW::LibraryAnimations::STEP:
445             return MFnAnimCurve::kTangentStep;
446 
447         case COLLADASW::LibraryAnimations::LINEAR:
448             return MFnAnimCurve::kTangentLinear;
449 
450         case COLLADASW::LibraryAnimations::BEZIER:
451             return MFnAnimCurve::kTangentFixed;
452 
453         default:
454             return MFnAnimCurve::kTangentClamped;
455         }
456     }
457 
458     // -------------------------------------------
mayaInfinityTypeToString(MFnAnimCurve::InfinityType type)459     const String AnimationHelper::mayaInfinityTypeToString ( MFnAnimCurve::InfinityType type )
460     {
461         switch ( type )
462         {
463 
464         case MFnAnimCurve::kConstant:
465             return MAYA_CONSTANT_INFINITY;
466 
467         case MFnAnimCurve::kLinear:
468             return MAYA_LINEAR_INFINITY;
469 
470         case MFnAnimCurve::kCycle:
471             return MAYA_CYCLE_INFINITY;
472 
473         case MFnAnimCurve::kCycleRelative:
474             return MAYA_CYCLE_RELATIVE_INFINITY;
475 
476         case MFnAnimCurve::kOscillate:
477             return MAYA_OSCILLATE_INFINITY;
478 
479         default:
480             return MAYA_CONSTANT_INFINITY;
481         }
482     }
483 
484     /*
485     // Pre/Post-Infinity Type Handling
486     InfinityType::Infinity AnimationHelper::ConvertInfinity(MFnAnimCurve::InfinityType type)
487     {
488      switch (type)
489      {
490      case MFnAnimCurve::kConstant: return InfinityType::CONSTANT;
491      case MFnAnimCurve::kLinear: return InfinityType::LINEAR;
492      case MFnAnimCurve::kCycle: return InfinityType::CYCLE;
493      case MFnAnimCurve::kCycleRelative: return InfinityType::CYCLE_RELATIVE;
494      case MFnAnimCurve::kOscillate: return InfinityType::OSCILLATE;
495      default: return Infinity::UNKNOWN;
496      }
497     }
498 
499     MFnAnimCurve::InfinityType AnimationHelper::ConvertInfinity(InfinityType::Infinity type)
500     {
501      switch (type)
502      {
503      case InfinityType::CONSTANT: return MFnAnimCurve::kConstant;
504      case InfinityType::LINEAR: return MFnAnimCurve::kLinear;
505      case InfinityType::CYCLE: return MFnAnimCurve::kCycle;
506      case InfinityType::CYCLE_RELATIVE: return MFnAnimCurve::kCycleRelative;
507      case InfinityType::OSCILLATE: return MFnAnimCurve::kOscillate;
508      default: return MFnAnimCurve::kConstant;
509      }
510     }
511     */
512 
513     // -------------------------------------------
isPhysicsAnimation(const MObject & o)514     bool AnimationHelper::isPhysicsAnimation ( const MObject& o )
515     {
516         if ( o.hasFn ( MFn::kChoice ) )
517         {
518             MFnDependencyNode n ( o );
519             MPlug p = n.findPlug ( "input" );
520             uint choiceCount = p.numElements();
521 
522             for ( uint i = 0; i < choiceCount; ++i )
523             {
524                 MPlug child = p.elementByPhysicalIndex ( i );
525                 MObject connection = DagHelper::getSourceNodeConnectedTo ( child );
526 
527                 if ( !connection.isNull() && connection != o )
528                     if ( isPhysicsAnimation ( connection ) ) return true;
529             }
530         }
531 
532         else if ( o.hasFn ( MFn::kRigidSolver ) || o.hasFn ( MFn::kRigid ) ) return true;
533 
534         return false;
535     }
536 
537     // -------------------------------------------
checkForSampling(AnimationSampleCache * cache,SampleType sampleType,const MPlug & plug)538     void AnimationHelper::checkForSampling (
539         AnimationSampleCache* cache,
540         SampleType sampleType,
541         const MPlug& plug )
542     {
543         switch ( sampleType & kValueMask )
544         {
545 
546         case kBoolean:
547         case kSingle:
548         {
549             bool forceSampling = ExportOptions::isSampling();
550 
551             if ( !forceSampling )
552             {
553                 MObject connection = AnimationHelper::getAnimatingNode ( plug );
554                 forceSampling |= !connection.isNull() && !connection.hasFn ( MFn::kCharacter ) && !connection.hasFn ( MFn::kAnimCurve );
555                 forceSampling &= !isPhysicsAnimation ( connection );
556             }
557             if ( forceSampling ) cache->cachePlug ( plug, false );
558 
559             break;
560         }
561 
562         case kMatrix:
563         {
564             bool forceSampling = cache->findCacheNode ( plug.node() );
565             if ( forceSampling ) cache->cachePlug ( plug, false );
566             break;
567         }
568 
569         case kVector:
570         case kColour:
571         case kVector2:
572         {
573             // Check for one node affecting the whole value.
574             bool forceSampling = ExportOptions::isSampling();
575             if ( !forceSampling )
576             {
577                 MObject connection = AnimationHelper::getAnimatingNode ( plug );
578                 forceSampling |= !connection.isNull() && !connection.hasFn ( MFn::kCharacter ) && !connection.hasFn ( MFn::kAnimCurve );
579                 forceSampling &= !isPhysicsAnimation ( connection );
580             }
581 
582             if ( forceSampling ) cache->cachePlug ( plug, false );
583             else
584             {
585                 // Check for nodes affecting the children.
586                 uint childCount = plug.numChildren();
587                 for ( uint i = 0; i < childCount; ++i )
588                 {
589                     MObject connection = AnimationHelper::getAnimatingNode ( plug.child ( i ) );
590                     bool sampleChild = !connection.isNull() && !connection.hasFn ( MFn::kCharacter ) && !connection.hasFn ( MFn::kAnimCurve );
591                     sampleChild &= !isPhysicsAnimation ( connection );
592 
593                     if ( sampleChild ) cache->cachePlug ( plug.child ( i ), false );
594                 }
595             }
596 
597             break;
598         }
599         }
600     }
601 }