tsAnimate.cpp

Engine/source/ts/tsAnimate.cpp

More...

Detailed Description

Public Functions

compareThreads(const void * e1, const void * e2)

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2012 GarageGames, LLC
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24#include "ts/tsShapeInstance.h"
  25
  26//----------------------------------------------------------------------------------
  27// some utility functions
  28//-------------------------------------------------------------------------------------
  29
  30S32 QSORT_CALLBACK FN_CDECL compareThreads( const void* e1, const void* e2)
  31{
  32   const TSThread * th1 = *(const TSThread**)e1;
  33   const TSThread * th2 = *(const TSThread**)e2;
  34   return (*th1 < *th2);
  35}
  36
  37void TSShapeInstance::sortThreads()
  38{
  39   PROFILE_SCOPE( TSShapeInstance_sortThreads );
  40   dQsort(mThreadList.address(),mThreadList.size(),sizeof(TSThread*),compareThreads);
  41   dQsort(mTransitionThreads.address(),mTransitionThreads.size(),sizeof(TSThread*),compareThreads);
  42}
  43
  44void TSShapeInstance::setDirty(U32 dirty)
  45{
  46   AssertFatal((dirty & AllDirtyMask) == dirty,"TSShapeInstance::setDirty: illegal dirty flags");
  47   for (S32 i=0; i<mShape->subShapeFirstNode.size(); i++)
  48      mDirtyFlags[i] |= dirty;
  49}
  50
  51void TSShapeInstance::clearDirty(U32 dirty)
  52{
  53   AssertFatal((dirty & AllDirtyMask) == dirty,"TSShapeInstance::clearDirty: illegal dirty flags");
  54   for (S32 i=0; i<mShape->subShapeFirstNode.size(); i++)
  55      mDirtyFlags[i] &= ~dirty;
  56}
  57
  58//-------------------------------------------------------------------------------------
  59// Animate nodes
  60//-------------------------------------------------------------------------------------
  61
  62void TSShapeInstance::animateNodes(S32 ss)
  63{
  64   PROFILE_SCOPE( TSShapeInstance_animateNodes );
  65
  66   if (!mShape->nodes.size())
  67      return;
  68
  69   // @todo: When a node is added, we need to make sure to resize the nodeTransforms array as well
  70   mNodeTransforms.setSize(mShape->nodes.size());
  71
  72   // temporary storage for node transforms
  73   smNodeCurrentRotations.setSize(mShape->nodes.size());
  74   smNodeCurrentTranslations.setSize(mShape->nodes.size());
  75   smNodeLocalTransforms.setSize(mShape->nodes.size());
  76   smRotationThreads.setSize(mShape->nodes.size());
  77   smTranslationThreads.setSize(mShape->nodes.size());
  78
  79   TSIntegerSet rotBeenSet;
  80   TSIntegerSet tranBeenSet;
  81   TSIntegerSet scaleBeenSet;
  82   rotBeenSet.setAll(mShape->nodes.size());
  83   tranBeenSet.setAll(mShape->nodes.size());
  84   scaleBeenSet.setAll(mShape->nodes.size());
  85   smNodeLocalTransformDirty.clearAll();
  86
  87   S32 i,j,nodeIndex,a,b,start,end,firstBlend = mThreadList.size();
  88   for (i=0; i<mThreadList.size(); i++)
  89   {
  90      TSThread * th = mThreadList[i];
  91
  92      const TSShape::Sequence* threadSequence = th->getSequence();
  93
  94      if (threadSequence->isBlend())
  95      {
  96         // blend sequences need default (if not set by other sequence)
  97         // break rather than continue because the rest will be blends too
  98         firstBlend = i;
  99         break;
 100      }
 101      rotBeenSet.takeAway(threadSequence->rotationMatters);
 102      tranBeenSet.takeAway(threadSequence->translationMatters);
 103      scaleBeenSet.takeAway(threadSequence->scaleMatters);
 104   }
 105   rotBeenSet.takeAway(mCallbackNodes);
 106   rotBeenSet.takeAway(mHandsOffNodes);
 107   rotBeenSet.overlap(mMaskRotationNodes);
 108
 109   TSIntegerSet maskPosNodes=<a href="/coding/class/classtsshapeinstance/#classtsshapeinstance_1a754a6ea85f253ed7d1fd9c46e4a69ab4">mMaskPosXNodes</a>;
 110   maskPosNodes.overlap(mMaskPosYNodes);
 111   maskPosNodes.overlap(mMaskPosZNodes);
 112   tranBeenSet.overlap(maskPosNodes);
 113
 114   tranBeenSet.takeAway(mCallbackNodes);
 115   tranBeenSet.takeAway(mHandsOffNodes);
 116   // can't add masked nodes since x, y, & z masked separately...
 117   // we'll set default regardless of mask status
 118
 119   // all the nodes marked above need to have the default transform
 120   a = mShape->subShapeFirstNode[ss];
 121   b = a + mShape->subShapeNumNodes[ss];
 122   for (i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 123   {
 124      if (rotBeenSet.test(i))
 125      {
 126         mShape->defaultRotations[i].getQuatF(&smNodeCurrentRotations[i]);
 127         smRotationThreads[i] = NULL;
 128      }
 129      if (tranBeenSet.test(i))
 130      {
 131         smNodeCurrentTranslations[i] = mShape->defaultTranslations[i];
 132         smTranslationThreads[i] = NULL;
 133      }
 134   }
 135
 136   // don't want a transform in these cases...
 137   rotBeenSet.overlap(mHandsOffNodes);
 138   rotBeenSet.overlap(mCallbackNodes);
 139   tranBeenSet.takeAway(maskPosNodes);
 140   tranBeenSet.overlap(mHandsOffNodes);
 141   tranBeenSet.overlap(mCallbackNodes);
 142
 143   // default scale
 144   if (scaleCurrentlyAnimated())
 145      handleDefaultScale(a,b,scaleBeenSet);
 146
 147   // handle non-blend sequences
 148   for (i=0; i<firstBlend; i++)
 149   {
 150      TSThread * th = mThreadList[i];
 151
 152      j=0;
 153      start = th->getSequence()->rotationMatters.start();
 154      end   = b;
 155      for (nodeIndex=start; nodeIndex<end; th->getSequence()->rotationMatters.next(nodeIndex), j++)
 156      {
 157         // skip nodes outside of this detail
 158         if (nodeIndex<a)
 159            continue;
 160         if (!rotBeenSet.test(nodeIndex))
 161         {
 162            QuatF q1,q2;
 163            mShape->getRotation(*th->getSequence(),th->keyNum1,j,&q1);
 164            mShape->getRotation(*th->getSequence(),th->keyNum2,j,&q2);
 165            TSTransform::interpolate(q1,q2,th->keyPos,&smNodeCurrentRotations[nodeIndex]);
 166            rotBeenSet.set(nodeIndex);
 167            smRotationThreads[nodeIndex] = th;
 168         }
 169      }
 170
 171      j=0;
 172      start = th->getSequence()->translationMatters.start();
 173      end   = b;
 174      for (nodeIndex=start; nodeIndex<end; th->getSequence()->translationMatters.next(nodeIndex), j++)
 175      {
 176         if (nodeIndex<a)
 177            continue;
 178         if (!tranBeenSet.test(nodeIndex))
 179         {
 180            if (maskPosNodes.test(nodeIndex))
 181               handleMaskedPositionNode(th,nodeIndex,j);
 182            else
 183            {
 184               const Point3F & p1 = mShape->getTranslation(*th->getSequence(),th->keyNum1,j);
 185               const Point3F & p2 = mShape->getTranslation(*th->getSequence(),th->keyNum2,j);
 186               TSTransform::interpolate(p1,p2,th->keyPos,&smNodeCurrentTranslations[nodeIndex]);
 187               smTranslationThreads[nodeIndex] = th;
 188            }
 189            tranBeenSet.set(nodeIndex);
 190         }
 191      }
 192
 193      if (scaleCurrentlyAnimated())
 194         handleAnimatedScale(th,a,b,scaleBeenSet);
 195   }
 196
 197   // compute transforms
 198   for (i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 199   {
 200      if (!mHandsOffNodes.test(i))
 201         TSTransform::setMatrix(smNodeCurrentRotations[i],smNodeCurrentTranslations[i],&smNodeLocalTransforms[i]);
 202      else
 203         smNodeLocalTransforms[i] = mNodeTransforms[i];     // in case mNodeTransform was changed externally
 204   }
 205
 206   // add scale onto transforms
 207   if (scaleCurrentlyAnimated())
 208      handleNodeScale(a,b);
 209
 210   // get callbacks...
 211   start = getMax(mCallbackNodes.start(),a);
 212   end = getMin(mCallbackNodes.end(),b);
 213   for (i=0; i<mNodeCallbacks.size(); i++)
 214   {
 215      AssertFatal(mNodeCallbacks[i].callback, "No callback method defined");
 216      S32 nodeIdx = mNodeCallbacks[i].nodeIndex;
 217      if (nodeIdx >=start && nodeIdx<end)
 218      {
 219         mNodeCallbacks[i].callback->setNodeTransform(this, nodeIdx, smNodeLocalTransforms[nodeIdx]);
 220         smNodeLocalTransformDirty.set(nodeIdx);
 221      }
 222   }
 223
 224   // handle blend sequences
 225   for (i=firstBlend; i<mThreadList.size(); i++)
 226   {
 227      TSThread * th = mThreadList[i];
 228      if (th->blendDisabled)
 229         continue;
 230
 231      handleBlendSequence(th,a,b);
 232   }
 233
 234   // transitions...
 235   if (inTransition())
 236      handleTransitionNodes(a,b);
 237
 238   // multiply transforms...
 239   for (i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 240   {
 241      S32 parentIdx = mShape->nodes[i].parentIndex;
 242      if (parentIdx < 0)
 243         mNodeTransforms[i] = smNodeLocalTransforms[i];
 244      else
 245         mNodeTransforms[i].mul(mNodeTransforms[parentIdx],smNodeLocalTransforms[i]);
 246   }
 247}
 248
 249void TSShapeInstance::handleDefaultScale(S32 a, S32 b, TSIntegerSet & scaleBeenSet)
 250{
 251   // set default scale values (i.e., identity) and do any initialization
 252   // relating to animated scale (since scale normally not animated)
 253
 254   smScaleThreads.setSize(mShape->nodes.size());
 255   scaleBeenSet.takeAway(mCallbackNodes);
 256   scaleBeenSet.takeAway(mHandsOffNodes);
 257   if (animatesUniformScale())
 258   {
 259      smNodeCurrentUniformScales.setSize(mShape->nodes.size());
 260      for (S32 i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 261         if (scaleBeenSet.test(i))
 262         {
 263            smNodeCurrentUniformScales[i] = 1.0f;
 264            smScaleThreads[i] = NULL;
 265         }
 266   }
 267   else if (animatesAlignedScale())
 268   {
 269      smNodeCurrentAlignedScales.setSize(mShape->nodes.size());
 270      for (S32 i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 271         if (scaleBeenSet.test(i))
 272         {
 273            smNodeCurrentAlignedScales[i].set(1.0f,1.0f,1.0f);
 274            smScaleThreads[i] = NULL;
 275         }
 276   }
 277   else
 278   {
 279      smNodeCurrentArbitraryScales.setSize(mShape->nodes.size());
 280      for (S32 i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 281         if (scaleBeenSet.test(i))
 282         {
 283            smNodeCurrentArbitraryScales[i].identity();
 284            smScaleThreads[i] = NULL;
 285         }
 286   }
 287
 288   scaleBeenSet.overlap(mHandsOffNodes);
 289   scaleBeenSet.overlap(mCallbackNodes);
 290}
 291
 292void TSShapeInstance::updateTransitionNodeTransforms(TSIntegerSet& transitionNodes)
 293{
 294   // handle transitions
 295   transitionNodes.clearAll();
 296   transitionNodes.overlap(mTransitionRotationNodes);
 297   transitionNodes.overlap(mTransitionTranslationNodes);
 298   transitionNodes.overlap(mTransitionScaleNodes);
 299   transitionNodes.takeAway(mHandsOffNodes);
 300
 301   // Decompose transforms for nodes affected by the transition. Only need to do
 302   // for blended or scale-animated nodes, as all others are already up to date
 303   for (S32 i=transitionNodes.start(); i<MAX_TS_SET_SIZE; transitionNodes.next(i))
 304   {
 305      if (smNodeLocalTransformDirty.test(i))
 306      {
 307         if (scaleCurrentlyAnimated())
 308         {
 309            // @todo:No support for scale yet => need to do proper affine decomposition here
 310            smNodeCurrentTranslations[i] = smNodeLocalTransforms[i].getPosition();
 311            smNodeCurrentRotations[i].set(smNodeLocalTransforms[i]);
 312         }
 313         else
 314         {
 315            // Scale is identity => can do a cheap decomposition
 316            smNodeCurrentTranslations[i] = smNodeLocalTransforms[i].getPosition();
 317            smNodeCurrentRotations[i].set(smNodeLocalTransforms[i]);
 318         }
 319      }
 320   }
 321}
 322
 323void TSShapeInstance::handleTransitionNodes(S32 a, S32 b)
 324{
 325   TSIntegerSet transitionNodes;
 326   updateTransitionNodeTransforms(transitionNodes);
 327
 328   S32 nodeIndex;
 329   S32 start = mTransitionRotationNodes.start();
 330   S32 end   = b;
 331   for (nodeIndex=start; nodeIndex<end; mTransitionRotationNodes.next(nodeIndex))
 332   {
 333      if (nodeIndex<a)
 334         continue;
 335      TSThread * thread = smRotationThreads[nodeIndex];
 336      thread = thread && thread->transitionData.inTransition ? thread : NULL;
 337      if (!thread)
 338      {
 339         // if not controlled by a sequence in transition then there must be
 340         // some other thread out there that used to control us that is in
 341         // transition now...use that thread to control interpolation
 342         for (S32 i=0; i<mTransitionThreads.size(); i++)
 343         {
 344            if (mTransitionThreads[i]->transitionData.oldRotationNodes.test(nodeIndex) || mTransitionThreads[i]->getSequence()->rotationMatters.test(nodeIndex))
 345            {
 346               thread = mTransitionThreads[i];
 347               break;
 348            }
 349         }
 350         AssertFatal(thread!=<a href="/coding/file/types_8lint_8h/#types_8lint_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>,"TSShapeInstance::handleRotTransitionNodes (rotation)");
 351      }
 352      QuatF tmpQ;
 353      TSTransform::interpolate(mNodeReferenceRotations[nodeIndex].getQuatF(&tmpQ),smNodeCurrentRotations[nodeIndex],thread->transitionData.pos,&smNodeCurrentRotations[nodeIndex]);
 354   }
 355
 356   // then translation
 357   start = mTransitionTranslationNodes.start();
 358   end   = b;
 359   for (nodeIndex=start; nodeIndex<end; mTransitionTranslationNodes.next(nodeIndex))
 360   {
 361      TSThread * thread = smTranslationThreads[nodeIndex];
 362      thread = thread && thread->transitionData.inTransition ? thread : NULL;
 363      if (!thread)
 364      {
 365         // if not controlled by a sequence in transition then there must be
 366         // some other thread out there that used to control us that is in
 367         // transition now...use that thread to control interpolation
 368         for (S32 i=0; i<mTransitionThreads.size(); i++)
 369         {
 370            if (mTransitionThreads[i]->transitionData.oldTranslationNodes.test(nodeIndex) || mTransitionThreads[i]->getSequence()->translationMatters.test(nodeIndex))
 371            {
 372               thread = mTransitionThreads[i];
 373               break;
 374            }
 375         }
 376         AssertFatal(thread!=<a href="/coding/file/types_8lint_8h/#types_8lint_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>,"TSShapeInstance::handleTransitionNodes (translation).");
 377      }
 378      Point3F & p = smNodeCurrentTranslations[nodeIndex];
 379      Point3F & p1 = mNodeReferenceTranslations[nodeIndex];
 380      Point3F & p2 = p;
 381      F32 k = thread->transitionData.pos;
 382      p.x = p1.x + k * (p2.x-p1.x);
 383      p.y = p1.y + k * (p2.y-p1.y);
 384      p.z = p1.z + k * (p2.z-p1.z);
 385   }
 386
 387   // then scale...
 388   if (scaleCurrentlyAnimated())
 389   {
 390      start = mTransitionScaleNodes.start();
 391      end   = b;
 392      for (nodeIndex=start; nodeIndex<end; mTransitionScaleNodes.next(nodeIndex))
 393      {
 394         TSThread * thread = smScaleThreads[nodeIndex];
 395         thread = thread && thread->transitionData.inTransition ? thread : NULL;
 396         if (!thread)
 397         {
 398            // if not controlled by a sequence in transition then there must be
 399            // some other thread out there that used to control us that is in
 400            // transition now...use that thread to control interpolation
 401            for (S32 i=0; i<mTransitionThreads.size(); i++)
 402            {
 403               if (mTransitionThreads[i]->transitionData.oldScaleNodes.test(nodeIndex) || mTransitionThreads[i]->getSequence()->scaleMatters.test(nodeIndex))
 404               {
 405                  thread = mTransitionThreads[i];
 406                  break;
 407               }
 408            }
 409            AssertFatal(thread!=<a href="/coding/file/types_8lint_8h/#types_8lint_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>,"TSShapeInstance::handleTransitionNodes (scale).");
 410         }
 411         if (animatesUniformScale())
 412            smNodeCurrentUniformScales[nodeIndex] += thread->transitionData.pos * (mNodeReferenceUniformScales[nodeIndex]-smNodeCurrentUniformScales[nodeIndex]);
 413         else if (animatesAlignedScale())
 414            TSTransform::interpolate(mNodeReferenceScaleFactors[nodeIndex],smNodeCurrentAlignedScales[nodeIndex],thread->transitionData.pos,&smNodeCurrentAlignedScales[nodeIndex]);
 415         else
 416         {
 417            QuatF q;
 418            TSTransform::interpolate(mNodeReferenceScaleFactors[nodeIndex],smNodeCurrentArbitraryScales[nodeIndex].mScale,thread->transitionData.pos,&smNodeCurrentArbitraryScales[nodeIndex].mScale);
 419            TSTransform::interpolate(mNodeReferenceArbitraryScaleRots[nodeIndex].getQuatF(&q),smNodeCurrentArbitraryScales[nodeIndex].mRotate,thread->transitionData.pos,&smNodeCurrentArbitraryScales[nodeIndex].mRotate);
 420         }
 421      }
 422   }
 423
 424   // update transforms for transition nodes
 425   start = transitionNodes.start();
 426   end   = b;
 427   for (nodeIndex=start; nodeIndex<end; transitionNodes.next(nodeIndex))
 428   {
 429      TSTransform::setMatrix(smNodeCurrentRotations[nodeIndex], smNodeCurrentTranslations[nodeIndex], &smNodeLocalTransforms[nodeIndex]);
 430      if (scaleCurrentlyAnimated())
 431      {
 432         if (animatesUniformScale())
 433            TSTransform::applyScale(smNodeCurrentUniformScales[nodeIndex],&smNodeLocalTransforms[nodeIndex]);
 434         else if (animatesAlignedScale())
 435               TSTransform::applyScale(smNodeCurrentAlignedScales[nodeIndex],&smNodeLocalTransforms[nodeIndex]);
 436         else
 437            TSTransform::applyScale(smNodeCurrentArbitraryScales[nodeIndex],&smNodeLocalTransforms[nodeIndex]);
 438      }
 439   }
 440}
 441
 442void TSShapeInstance::handleNodeScale(S32 a, S32 b)
 443{
 444   if (animatesUniformScale())
 445   {
 446      for (S32 i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 447         if (!mHandsOffNodes.test(i))
 448            TSTransform::applyScale(smNodeCurrentUniformScales[i],&smNodeLocalTransforms[i]);
 449   }
 450   else if (animatesAlignedScale())
 451   {
 452      for (S32 i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 453         if (!mHandsOffNodes.test(i))
 454            TSTransform::applyScale(smNodeCurrentAlignedScales[i],&smNodeLocalTransforms[i]);
 455   }
 456   else
 457   {
 458      for (S32 i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 459         if (!mHandsOffNodes.test(i))
 460            TSTransform::applyScale(smNodeCurrentArbitraryScales[i],&smNodeLocalTransforms[i]);
 461   }
 462
 463   TSIntegerSet scaledNodes;
 464   scaledNodes.difference(mHandsOffNodes);
 465   smNodeLocalTransformDirty.overlap(scaledNodes);
 466}
 467
 468void TSShapeInstance::handleAnimatedScale(TSThread * thread, S32 a, S32 b, TSIntegerSet & scaleBeenSet)
 469{
 470   S32 j=0;
 471   S32 start = thread->getSequence()->scaleMatters.start();
 472   S32 end   = b;
 473
 474   // code the scale conversion (might need to "upgrade" from uniform to arbitrary, e.g.)
 475   // code uniform, aligned, and arbitrary as 0,1, and 2, respectively,
 476   // with sequence coding in first two bits, shape coding in next two bits
 477   S32 code = 0;
 478   if (thread->getSequence()->animatesAlignedScale())
 479      code += 1;
 480   else if (thread->getSequence()->animatesArbitraryScale())
 481      code += 2;
 482   if (animatesAlignedScale())
 483      code += 4;
 484   if (animatesArbitraryScale())
 485      code += 8;
 486
 487   F32 uniformScale = 1.0f;
 488   Point3F alignedScale(0.0f, 0.0f, 0.0f);
 489   TSScale arbitraryScale;
 490   for (S32 nodeIndex=start; nodeIndex<end; thread->getSequence()->scaleMatters.next(nodeIndex), j++)
 491   {
 492      if (nodeIndex<a)
 493         continue;
 494
 495      if (!scaleBeenSet.test(nodeIndex))
 496      {
 497         // compute scale in sequence format
 498         switch (code)
 499         {           // Sequence   Shape
 500            case 0:  // uniform -> uniform
 501            case 4:  // uniform -> aligned
 502            case 8:  // uniform -> arbitrary
 503            {
 504               F32 s1 = mShape->getUniformScale(*thread->getSequence(),thread->keyNum1,j);
 505               F32 s2 = mShape->getUniformScale(*thread->getSequence(),thread->keyNum2,j);
 506               uniformScale = TSTransform::interpolate(s1,s2,thread->keyPos);
 507               alignedScale.set(uniformScale,uniformScale,uniformScale);
 508               break;
 509            }
 510            case 5:  // aligned -> aligned
 511            case 9:  // aligned -> arbitrary
 512            {
 513               const Point3F & s1 = mShape->getAlignedScale(*thread->getSequence(),thread->keyNum1,j);
 514               const Point3F & s2 = mShape->getAlignedScale(*thread->getSequence(),thread->keyNum2,j);
 515               TSTransform::interpolate(s1,s2,thread->keyPos,&alignedScale);
 516               break;
 517            }
 518            case 10: // arbitrary -> arbitary
 519            {
 520               TSScale s1,s2;
 521               mShape->getArbitraryScale(*thread->getSequence(),thread->keyNum1,j,&s1);
 522               mShape->getArbitraryScale(*thread->getSequence(),thread->keyNum2,j,&s2);
 523               TSTransform::interpolate(s1,s2,thread->keyPos,&arbitraryScale);
 524               break;
 525            }
 526            default: AssertFatal(0,"TSShapeInstance::handleAnimatedScale"); break;
 527         }
 528
 529         switch (code)
 530         {
 531            case 0:  // uniform -> uniform
 532            {
 533               smNodeCurrentUniformScales[nodeIndex] = uniformScale;
 534               break;
 535            }
 536            case 4:  // uniform -> aligned
 537            case 5:  // aligned -> aligned
 538               smNodeCurrentAlignedScales[nodeIndex] = alignedScale;
 539               break;
 540            case 8:  // uniform -> arbitrary
 541            case 9:  // aligned -> arbitrary
 542            {
 543               smNodeCurrentArbitraryScales[nodeIndex].identity();
 544               smNodeCurrentArbitraryScales[nodeIndex].mScale = alignedScale;
 545               break;
 546            }
 547            case 10: // arbitrary -> arbitary
 548            {
 549               smNodeCurrentArbitraryScales[nodeIndex] = arbitraryScale;
 550               break;
 551            }
 552            default: AssertFatal(0,"TSShapeInstance::handleAnimatedScale"); break;
 553         }
 554         smScaleThreads[nodeIndex] = thread;
 555         scaleBeenSet.set(nodeIndex);
 556      }
 557   }
 558}
 559
 560void TSShapeInstance::handleMaskedPositionNode(TSThread * th, S32 nodeIndex, S32 offset)
 561{
 562   const Point3F & p1 = mShape->getTranslation(*th->getSequence(),th->keyNum1,offset);
 563   const Point3F & p2 = mShape->getTranslation(*th->getSequence(),th->keyNum2,offset);
 564   Point3F p;
 565   TSTransform::interpolate(p1,p2,th->keyPos,&p);
 566
 567   if (!mMaskPosXNodes.test(nodeIndex))
 568      smNodeCurrentTranslations[nodeIndex].x = p.x;
 569
 570   if (!mMaskPosYNodes.test(nodeIndex))
 571      smNodeCurrentTranslations[nodeIndex].y = p.y;
 572
 573   if (!mMaskPosZNodes.test(nodeIndex))
 574      smNodeCurrentTranslations[nodeIndex].z = p.z;
 575}
 576
 577void TSShapeInstance::handleBlendSequence(TSThread * thread, S32 a, S32 b)
 578{
 579   S32 jrot=0;
 580   S32 jtrans=0;
 581   S32 jscale=0;
 582
 583   const TSShape::Sequence* threadSequence = thread->getSequence();
 584
 585   TSIntegerSet nodeMatters = threadSequence->translationMatters;
 586   nodeMatters.overlap(threadSequence->rotationMatters);
 587   nodeMatters.overlap(threadSequence->scaleMatters);
 588   nodeMatters.takeAway(mHandsOffNodes);
 589   S32 start = nodeMatters.start();
 590   S32 end   = b;
 591   for (S32 nodeIndex=start; nodeIndex<end; nodeMatters.next(nodeIndex))
 592   {
 593      // skip nodes outside of this detail
 594      if (start<a || mDisableBlendNodes.test(nodeIndex))
 595      {
 596         if (threadSequence->rotationMatters.test(nodeIndex))
 597            jrot++;
 598         if (threadSequence->translationMatters.test(nodeIndex))
 599            jtrans++;
 600         if (threadSequence->scaleMatters.test(nodeIndex))
 601            jscale++;
 602         continue;
 603      }
 604
 605      MatrixF mat(true);
 606      if (threadSequence->rotationMatters.test(nodeIndex))
 607      {
 608         QuatF q1,q2;
 609         mShape->getRotation(*threadSequence,thread->keyNum1,jrot,&q1);
 610         mShape->getRotation(*threadSequence,thread->keyNum2,jrot,&q2);
 611         QuatF quat;
 612         TSTransform::interpolate(q1,q2,thread->keyPos,&quat);
 613         TSTransform::setMatrix(quat,&mat);
 614         jrot++;
 615      }
 616
 617      if (threadSequence->translationMatters.test(nodeIndex))
 618      {
 619         const Point3F & p1 = mShape->getTranslation(*threadSequence,thread->keyNum1,jtrans);
 620         const Point3F & p2 = mShape->getTranslation(*threadSequence,thread->keyNum2,jtrans);
 621         Point3F p;
 622         TSTransform::interpolate(p1,p2,thread->keyPos,&p);
 623         mat.setColumn(3,p);
 624         jtrans++;
 625      }
 626
 627      if (threadSequence->scaleMatters.test(nodeIndex))
 628      {
 629         if (threadSequence->animatesUniformScale())
 630         {
 631            F32 s1 = mShape->getUniformScale(*threadSequence,thread->keyNum1,jscale);
 632            F32 s2 = mShape->getUniformScale(*threadSequence,thread->keyNum2,jscale);
 633            F32 scale = TSTransform::interpolate(s1,s2,thread->keyPos);
 634            TSTransform::applyScale(scale,&mat);
 635         }
 636         else if (animatesAlignedScale())
 637         {
 638            Point3F s1 = mShape->getAlignedScale(*threadSequence,thread->keyNum1,jscale);
 639            Point3F s2 = mShape->getAlignedScale(*threadSequence,thread->keyNum2,jscale);
 640            Point3F scale;
 641            TSTransform::interpolate(s1,s2,thread->keyPos,&scale);
 642            TSTransform::applyScale(scale,&mat);
 643         }
 644         else
 645         {
 646            TSScale s1,s2;
 647            mShape->getArbitraryScale(*threadSequence,thread->keyNum1,jscale,&s1);
 648            mShape->getArbitraryScale(*threadSequence,thread->keyNum2,jscale,&s2);
 649            TSScale scale;
 650            TSTransform::interpolate(s1,s2,thread->keyPos,&scale);
 651            TSTransform::applyScale(scale,&mat);
 652         }
 653         jscale++;
 654      }
 655
 656      // apply blend transform
 657      smNodeLocalTransforms[nodeIndex].mul(mat);
 658      smNodeLocalTransformDirty.set(nodeIndex);
 659   }
 660}
 661
 662//-------------------------------------------------------------------------------------
 663// Other Animation:
 664//-------------------------------------------------------------------------------------
 665
 666void TSShapeInstance::animateVisibility(S32 ss)
 667{
 668   PROFILE_SCOPE( TSShapeInstance_animateVisibility );
 669
 670   S32 i;
 671   if (!mMeshObjects.size())
 672      return;
 673
 674   // find out who needs default values set
 675   TSIntegerSet beenSet;
 676   beenSet.setAll(mMeshObjects.size());
 677   for (i=0; i<mThreadList.size(); i++)
 678      beenSet.takeAway(mThreadList[i]->getSequence()->visMatters);
 679
 680   // set defaults
 681   S32 a = mShape->subShapeFirstObject[ss];
 682   S32 b = a + mShape->subShapeNumObjects[ss];
 683   for (i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 684   {
 685      if (beenSet.test(i))
 686         mMeshObjects[i].visible = mShape->objectStates[i].vis;
 687   }
 688
 689   // go through each thread and set visibility on those objects that
 690   // are not set yet and are controlled by that thread
 691   for (i=0; i<mThreadList.size(); i++)
 692   {
 693      TSThread * th = mThreadList[i];
 694
 695      const TSShape::Sequence* threadSequence = th->getSequence();
 696
 697      // For better or worse, object states are stored together (frame,
 698      // matFrame, visibility all in one structure).  Thus, indexing into
 699      // object state array for animation for any of these attributes needs to
 700      // take into account whether or not the other attributes are also animated.
 701      // The object states should eventually be separated (like the node states were)
 702      // in order to save memory and save the following step.
 703      TSIntegerSet objectMatters = threadSequence->frameMatters;
 704      objectMatters.overlap(threadSequence->matFrameMatters);
 705      objectMatters.overlap(threadSequence->visMatters);
 706
 707      // skip to beginning of this sub-shape
 708      S32 j=0;
 709      S32 start = objectMatters.start();
 710      S32 end = b;
 711      for (S32 objectIndex = start; objectIndex<end; objectMatters.next(objectIndex), j++)
 712      {
 713         if (!beenSet.test(objectIndex) && threadSequence->visMatters.test(objectIndex))
 714         {
 715            F32 state1 = mShape->getObjectState(*threadSequence,th->keyNum1,j).vis;
 716            F32 state2 = mShape->getObjectState(*threadSequence,th->keyNum2,j).vis;
 717            if ((state1-state2) * (state1-state2) > 0.99f)
 718               // goes from 0 to 1 -- discreet jump
 719               mMeshObjects[objectIndex].visible = th->keyPos<0.5f ? state1 : state2;
 720            else
 721               // interpolate between keyframes when visibility change is gradual
 722               mMeshObjects[objectIndex].visible = (1.0f-th->keyPos) * state1 + th->keyPos * state2;
 723
 724            // record change so that later threads don't over-write us...
 725            beenSet.set(objectIndex);
 726         }
 727      }
 728   }
 729}
 730
 731void TSShapeInstance::animateFrame(S32 ss)
 732{
 733   PROFILE_SCOPE( TSShapeInstance_animateFrame );
 734
 735   S32 i;
 736   if (!mMeshObjects.size())
 737      return;
 738
 739   // find out who needs default values set
 740   TSIntegerSet beenSet;
 741   beenSet.setAll(mMeshObjects.size());
 742   for (i=0; i<mThreadList.size(); i++)
 743      beenSet.takeAway(mThreadList[i]->getSequence()->frameMatters);
 744
 745   // set defaults
 746   S32 a = mShape->subShapeFirstObject[ss];
 747   S32 b = a + mShape->subShapeNumObjects[ss];
 748   for (i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 749      if (beenSet.test(i))
 750         mMeshObjects[i].frame = mShape->objectStates[i].frameIndex;
 751
 752   // go through each thread and set frame on those objects that
 753   // are not set yet and are controlled by that thread
 754   for (i=0; i<mThreadList.size(); i++)
 755   {
 756      TSThread * th = mThreadList[i];
 757
 758      const TSShape::Sequence* threadSequence = th->getSequence();
 759
 760      // For better or worse, object states are stored together (frame,
 761      // matFrame, visibility all in one structure).  Thus, indexing into
 762      // object state array for animation for any of these attributes needs to
 763      // take into account whether or not the other attributes are also animated.
 764      // The object states should eventually be separated (like the node states were)
 765      // in order to save memory and save the following step.
 766      TSIntegerSet objectMatters = threadSequence->frameMatters;
 767      objectMatters.overlap(threadSequence->matFrameMatters);
 768      objectMatters.overlap(threadSequence->visMatters);
 769
 770      // skip to beginning of this sub-shape
 771      S32 j=0;
 772      S32 start = objectMatters.start();
 773      S32 end = b;
 774      for (S32 objectIndex = start; objectIndex<end; objectMatters.next(objectIndex), j++)
 775      {
 776         if (!beenSet.test(objectIndex) && threadSequence->frameMatters.test(objectIndex))
 777         {
 778            S32 key = (th->keyPos<0.5f) ? th->keyNum1 : th->keyNum2;
 779            mMeshObjects[objectIndex].frame = mShape->getObjectState(*threadSequence,key,j).frameIndex;
 780
 781            // record change so that later threads don't over-write us...
 782            beenSet.set(objectIndex);
 783         }
 784      }
 785   }
 786}
 787
 788void TSShapeInstance::animateMatFrame(S32 ss)
 789{
 790   PROFILE_SCOPE( TSShapeInstance_animateMatFrame );
 791
 792   S32 i;
 793   if (!mMeshObjects.size())
 794      return;
 795
 796   // find out who needs default values set
 797   TSIntegerSet beenSet;
 798   beenSet.setAll(mMeshObjects.size());
 799   for (i=0; i<mThreadList.size(); i++)
 800      beenSet.takeAway(mThreadList[i]->getSequence()->matFrameMatters);
 801
 802   // set defaults
 803   S32 a = mShape->subShapeFirstObject[ss];
 804   S32 b = a + mShape->subShapeNumObjects[ss];
 805   for (i=<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a>; i<b; i++)
 806      if (beenSet.test(i))
 807         mMeshObjects[i].matFrame = mShape->objectStates[i].matFrameIndex;
 808
 809   // go through each thread and set matFrame on those objects that
 810   // are not set yet and are controlled by that thread
 811   for (i=0; i<mThreadList.size(); i++)
 812   {
 813      TSThread * th = mThreadList[i];
 814
 815      const TSShape::Sequence* threadSequence = th->getSequence();
 816
 817      // For better or worse, object states are stored together (frame,
 818      // matFrame, visibility all in one structure).  Thus, indexing into
 819      // object state array for animation for any of these attributes needs to
 820      // take into account whether or not the other attributes are also animated.
 821      // The object states should eventually be separated (like the node states were)
 822      // in order to save memory and save the following step.
 823      TSIntegerSet objectMatters = threadSequence->frameMatters;
 824      objectMatters.overlap(threadSequence->matFrameMatters);
 825      objectMatters.overlap(threadSequence->visMatters);
 826
 827      // skip to beginining of this sub-shape
 828      S32 j=0;
 829      S32 start = objectMatters.start();
 830      S32 end = b;
 831      for (S32 objectIndex = start; objectIndex<end; objectMatters.next(objectIndex), j++)
 832      {
 833         if (!beenSet.test(objectIndex) && threadSequence->matFrameMatters.test(objectIndex))
 834         {
 835            S32 key = (th->keyPos<0.5f) ? th->keyNum1 : th->keyNum2;
 836            mMeshObjects[objectIndex].matFrame = mShape->getObjectState(*threadSequence,key,j).matFrameIndex;
 837
 838            // record change so that later threads don't over-write us...
 839            beenSet.set(objectIndex);
 840         }
 841      }
 842   }
 843}
 844
 845//-------------------------------------------------------------------------------------
 846// Animate (and initialize detail levels)
 847//-------------------------------------------------------------------------------------
 848
 849void TSShapeInstance::animate(S32 dl)
 850{
 851   PROFILE_SCOPE( TSShapeInstance_animate );
 852
 853   if (dl==-1)
 854      // nothing to do
 855      return;
 856
 857   S32 ss = mShape->details[dl].subShapeNum;
 858
 859   // this is a billboard detail...
 860   if (ss<0)
 861      return;
 862
 863   U32 dirtyFlags = mDirtyFlags[ss];
 864
 865   if (dirtyFlags & ThreadDirty)
 866      sortThreads();
 867
 868   // animate nodes?
 869   if (dirtyFlags & TransformDirty)
 870      animateNodes(ss);
 871
 872   // animate objects?
 873   if (dirtyFlags & VisDirty)
 874      animateVisibility(ss);
 875
 876   if (dirtyFlags & FrameDirty)
 877      animateFrame(ss);
 878
 879   if (dirtyFlags & MatFrameDirty)
 880      animateMatFrame(ss);
 881
 882   mDirtyFlags[ss] = 0;
 883}
 884
 885void TSShapeInstance::animateNodeSubtrees(bool forceFull)
 886{
 887   // animate all the nodes for all the detail levels...
 888
 889   if (forceFull)
 890      // force transforms to animate
 891      setDirty(TransformDirty);
 892
 893   for (S32 i=0; i<mShape->subShapeNumNodes.size(); i++)
 894   {
 895      if (mDirtyFlags[i] & TransformDirty)
 896      {
 897         animateNodes(i);
 898         mDirtyFlags[i] &= ~<a href="/coding/class/classtsshapeinstance/#classtsshapeinstance_1a5c635115e4ba7f94af7ebf54eddf58f8a14de86c2c1b55f5273f2396eda15f308">TransformDirty</a>;
 899      }
 900   }
 901}
 902
 903void TSShapeInstance::animateSubtrees(bool forceFull)
 904{
 905   // animate all the subtrees
 906
 907   if (forceFull)
 908      // force full animate
 909      setDirty(AllDirtyMask);
 910
 911   for (S32 i=0; i<mShape->subShapeNumNodes.size(); i++)
 912   {
 913      if (mDirtyFlags[i] & TransformDirty)
 914      {
 915         animate(i);
 916         mDirtyFlags[i] = 0;
 917      }
 918   }
 919}
 920
 921void TSShapeInstance::addPath(TSThread *gt, F32 start, F32 end, MatrixF *mat)
 922{
 923   // never get here while in transition...
 924   AssertFatal(!gt->transitionData.inTransition,"TSShapeInstance::addPath");
 925
 926   if (!mat)
 927      mat = &mGroundTransform;
 928
 929   MatrixF startInvM;
 930   gt->getGround(start,&startInvM);
 931   startInvM.inverse();
 932
 933   MatrixF endM;
 934   gt->getGround(end,&endM);
 935
 936   MatrixF addM;
 937   addM.mul(startInvM,endM);
 938   endM.mul(*mat,addM);
 939   *mat = endM;
 940}
 941
 942bool TSShapeInstance::initGround()
 943{
 944   for (S32 i=0; i<mThreadList.size(); i++)
 945   {
 946      TSThread * th = mThreadList[i];
 947      if (!th->transitionData.inTransition && th->getSequence()->numGroundFrames>0)
 948      {
 949         mGroundThread = th;
 950         return true;
 951      }
 952   }
 953   return false;
 954}
 955
 956void TSShapeInstance::animateGround()
 957{
 958   mGroundTransform.identity();
 959
 960   // pick thread which controlls ground transform
 961   // if we haven't already...
 962   if (!mGroundThread && !initGround())
 963      return;
 964
 965   S32 & loop    = mGroundThread->path.loop;
 966   F32 & start = mGroundThread->path.start;
 967   F32 & end   = mGroundThread->path.end;
 968
 969   // accumulate path transform
 970   if (loop>0)
 971   {
 972      addPath(mGroundThread,start,1.0f);
 973      while (--loop)
 974         addPath(mGroundThread,0.0f,1.0f);
 975      addPath(mGroundThread,0.0f,end);
 976   }
 977   else if (loop<0)
 978   {
 979      addPath(mGroundThread,start,0.0f);
 980      while (++loop)
 981         addPath(mGroundThread,1.0f,0.0f);
 982      addPath(mGroundThread,1.0f,end);
 983   }
 984   else
 985      addPath(mGroundThread,start,end);
 986   start = end; // in case user tries to animateGround twice
 987}
 988
 989void TSShapeInstance::deltaGround(TSThread * thread, F32 start, F32 end, MatrixF * mat)
 990{
 991   if (!mat)
 992      mat = &mGroundTransform;
 993
 994   mat->identity();
 995   if (thread->transitionData.inTransition)
 996      return;
 997
 998   F32 invDuration = 1.0f / thread->getDuration();
 999   start *= invDuration;
1000   end *= invDuration;
1001
1002   addPath(thread,start,end,mat);
1003}
1004
1005// Simple case of above- get ground delta at given position in unit range
1006void TSShapeInstance::deltaGround1(TSThread * thread, F32 start, F32 end, MatrixF& mat)
1007{
1008   mat.identity();
1009   if (thread->transitionData.inTransition)
1010      return;
1011   addPath(thread, start, end, &mat);
1012}
1013
1014void TSShapeInstance::setTriggerState(U32 stateNum, bool on)
1015{
1016   AssertFatal(stateNum<=32 && stateNum>0,"TSShapeInstance::setTriggerState: state index out of range");
1017
1018   stateNum--; // stateNum externally 1..32, internally 0..31
1019   U32 bit = 1 << stateNum;
1020   if (on)
1021      mTriggerStates |= bit;
1022   else
1023      mTriggerStates &= ~bit;
1024}
1025
1026void TSShapeInstance::setTriggerStateBit(U32 stateBit, bool on)
1027{
1028   if (on)
1029      mTriggerStates |= stateBit;
1030   else
1031      mTriggerStates &= ~stateBit;
1032}
1033
1034bool TSShapeInstance::getTriggerState(U32 stateNum, bool clearState)
1035{
1036   AssertFatal(stateNum<=32 && stateNum>0,"TSShapeInstance::getTriggerState: state index out of range");
1037
1038   stateNum--; // stateNum externally 1..32, internally 0..31
1039   U32 bit = 1 << stateNum;
1040   bool ret = ((mTriggerStates & bit)!=0);
1041   if (clearState)
1042      mTriggerStates &= ~bit;
1043   return ret;
1044}
1045
1046void TSShapeInstance::setNodeAnimationState(S32 nodeIndex, U32 animationState, TSCallback * callback)
1047{
1048   AssertFatal((animationState & ~(MaskNodeAll</a>|<a href="/coding/class/classtsshapeinstance/#classtsshapeinstance_1ad57ffe66d6e2961b25536ba911f2af1ca6a449dfb45b124cd8a1778b6f5509bce">MaskNodeHandsOff</a>|<a href="/coding/class/classtsshapeinstance/#classtsshapeinstance_1ad57ffe66d6e2961b25536ba911f2af1ca87ae243de67ef16d4968813882818ea4">MaskNodeCallback)) == 0,"TSShapeInstance::setNodeAnimationState (1)");
1049
1050   // don't handle callback nodes in this method
1051   if (callback)
1052      animationState |= MaskNodeCallback;
1053   else
1054      animationState &= ~<a href="/coding/class/classtsshapeinstance/#classtsshapeinstance_1ad57ffe66d6e2961b25536ba911f2af1ca87ae243de67ef16d4968813882818ea4">MaskNodeCallback</a>;
1055
1056   // hands-off takes precedance
1057   if (animationState & MaskNodeHandsOff)
1058      animationState = MaskNodeHandsOff | MaskNodeBlend;
1059   else if (animationState & MaskNodeCallback)
1060      animationState = MaskNodeCallback | MaskNodeBlend;
1061
1062   // if we're not changing anything then get out of here now
1063   if (animationState == getNodeAnimationState(nodeIndex))
1064      return;
1065
1066   setDirty(AllDirtyMask);
1067
1068   if (animationState & MaskNodeAllButBlend)
1069   {
1070      if (animationState & MaskNodeRotation)
1071         mMaskRotationNodes.set(nodeIndex);
1072      if (animationState & MaskNodePosX)
1073         mMaskPosXNodes.set(nodeIndex);
1074      if (animationState & MaskNodePosY)
1075         mMaskPosYNodes.set(nodeIndex);
1076      if (animationState & MaskNodePosZ)
1077         mMaskPosZNodes.set(nodeIndex);
1078   }
1079   else
1080   {
1081      // no masking clear out all the masking lists
1082      mMaskRotationNodes.clear(nodeIndex);
1083      mMaskPosXNodes.clear(nodeIndex);
1084      mMaskPosYNodes.clear(nodeIndex);
1085      mMaskPosZNodes.clear(nodeIndex);
1086   }
1087
1088   if (animationState & MaskNodeBlend)
1089      mDisableBlendNodes.set(nodeIndex);
1090   else
1091      mDisableBlendNodes.clear(nodeIndex);
1092
1093   if (animationState & MaskNodeHandsOff)
1094      mHandsOffNodes.set(nodeIndex);
1095   else
1096      mHandsOffNodes.clear(nodeIndex);
1097
1098   // clear out of node callbacks
1099   for (S32 i=0; i<mNodeCallbacks.size(); i++)
1100   {
1101      if (mNodeCallbacks[i].nodeIndex == nodeIndex)
1102      {
1103         mNodeCallbacks.erase_fast(i);
1104         break;
1105      }
1106   }
1107
1108   if (animationState & MaskNodeCallback)
1109   {
1110      mCallbackNodes.set(nodeIndex);
1111      mNodeCallbacks.increment();
1112      mNodeCallbacks.last().callback = callback;
1113      mNodeCallbacks.last().nodeIndex = nodeIndex;
1114   }
1115   else
1116      mCallbackNodes.clear(nodeIndex);
1117}
1118
1119U32 TSShapeInstance::getNodeAnimationState(S32 nodeIndex)
1120{
1121   U32 ret = 0;
1122   if (mMaskRotationNodes.test(nodeIndex))
1123      ret |= MaskNodeRotation;
1124   if (mMaskPosXNodes.test(nodeIndex))
1125      ret |= MaskNodePosX;
1126   if (mMaskPosYNodes.test(nodeIndex))
1127      ret |= MaskNodePosY;
1128   if (mMaskPosZNodes.test(nodeIndex))
1129      ret |= MaskNodePosZ;
1130   if (mDisableBlendNodes.test(nodeIndex))
1131      ret |= MaskNodeBlend;
1132   if (mHandsOffNodes.test(nodeIndex))
1133      ret |= MaskNodeHandsOff;
1134   if (mCallbackNodes.test(nodeIndex))
1135      ret |= MaskNodeCallback;
1136   return ret;
1137}
1138