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 }