1101 lines
38 KiB
C++
Executable File
1101 lines
38 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "ts/tsShapeInstance.h"
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// some utility functions
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
S32 QSORT_CALLBACK FN_CDECL compareThreads( const void* e1, const void* e2)
|
|
{
|
|
const TSThread * th1 = *(const TSThread**)e1;
|
|
const TSThread * th2 = *(const TSThread**)e2;
|
|
return (*th1 < *th2);
|
|
}
|
|
|
|
void sortThreads(Vector<TSThread*> & threadList)
|
|
{
|
|
dQsort(threadList.address(),threadList.size(),sizeof(TSThread*),compareThreads);
|
|
}
|
|
|
|
void TSShapeInstance::setDirty(U32 dirty)
|
|
{
|
|
AssertFatal((dirty & AllDirtyMask) == dirty,"TSShapeInstance::setDirty: illegal dirty flags");
|
|
for (S32 i=0; i<mShape->subShapeFirstNode.size(); i++)
|
|
mDirtyFlags[i] |= dirty;
|
|
}
|
|
|
|
void TSShapeInstance::clearDirty(U32 dirty)
|
|
{
|
|
AssertFatal((dirty & AllDirtyMask) == dirty,"TSShapeInstance::clearDirty: illegal dirty flags");
|
|
for (S32 i=0; i<mShape->subShapeFirstNode.size(); i++)
|
|
mDirtyFlags[i] &= ~dirty;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// Animate nodes
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
void TSShapeInstance::animateNodes(S32 ss)
|
|
{
|
|
if (!mShape->nodes.size())
|
|
return;
|
|
|
|
// temporary storage for node transforms
|
|
smNodeCurrentRotations.setSize(mShape->nodes.size());
|
|
smNodeCurrentTranslations.setSize(mShape->nodes.size());
|
|
smRotationThreads.setSize(mShape->nodes.size());
|
|
smTranslationThreads.setSize(mShape->nodes.size());
|
|
|
|
TSIntegerSet rotBeenSet;
|
|
TSIntegerSet tranBeenSet;
|
|
TSIntegerSet scaleBeenSet;
|
|
rotBeenSet.setAll(mShape->nodes.size());
|
|
tranBeenSet.setAll(mShape->nodes.size());
|
|
scaleBeenSet.setAll(mShape->nodes.size());
|
|
|
|
S32 i,j,nodeIndex,a,b,start,end,firstBlend = mThreadList.size();
|
|
for (i=0; i<mThreadList.size(); i++)
|
|
{
|
|
TSThread * th = mThreadList[i];
|
|
|
|
if (th->sequence->isBlend())
|
|
{
|
|
// blend sequences need default (if not set by other sequence)
|
|
// break rather than continue because the rest will be blends too
|
|
firstBlend = i;
|
|
break;
|
|
}
|
|
rotBeenSet.takeAway(th->sequence->rotationMatters);
|
|
tranBeenSet.takeAway(th->sequence->translationMatters);
|
|
scaleBeenSet.takeAway(th->sequence->scaleMatters);
|
|
}
|
|
rotBeenSet.takeAway(mCallbackNodes);
|
|
rotBeenSet.takeAway(mHandsOffNodes);
|
|
rotBeenSet.overlap(mMaskRotationNodes);
|
|
|
|
TSIntegerSet maskPosNodes=mMaskPosXNodes;
|
|
maskPosNodes.overlap(mMaskPosYNodes);
|
|
maskPosNodes.overlap(mMaskPosZNodes);
|
|
tranBeenSet.overlap(maskPosNodes);
|
|
|
|
tranBeenSet.takeAway(mCallbackNodes);
|
|
tranBeenSet.takeAway(mHandsOffNodes);
|
|
// can't add masked nodes since x, y, & z masked separately...
|
|
// we'll set default regardless of mask status
|
|
|
|
// all the nodes marked above need to have the default transform
|
|
a = mShape->subShapeFirstNode[ss];
|
|
b = a + mShape->subShapeNumNodes[ss];
|
|
for (i=a; i<b; i++)
|
|
{
|
|
if (rotBeenSet.test(i))
|
|
{
|
|
mShape->defaultRotations[i].getQuatF(&smNodeCurrentRotations[i]);
|
|
smRotationThreads[i] = NULL;
|
|
}
|
|
if (tranBeenSet.test(i))
|
|
{
|
|
smNodeCurrentTranslations[i] = mShape->defaultTranslations[i];
|
|
smTranslationThreads[i] = NULL;
|
|
}
|
|
}
|
|
|
|
// don't want a transform in these cases...
|
|
rotBeenSet.overlap(mHandsOffNodes);
|
|
rotBeenSet.overlap(mCallbackNodes);
|
|
tranBeenSet.takeAway(maskPosNodes);
|
|
tranBeenSet.overlap(mHandsOffNodes);
|
|
tranBeenSet.overlap(mCallbackNodes);
|
|
|
|
// default scale
|
|
if (scaleCurrentlyAnimated())
|
|
handleDefaultScale(a,b,scaleBeenSet);
|
|
|
|
// handle non-blend sequences
|
|
for (i=0; i<firstBlend; i++)
|
|
{
|
|
TSThread * th = mThreadList[i];
|
|
|
|
j=0;
|
|
start = th->sequence->rotationMatters.start();
|
|
end = b;
|
|
for (nodeIndex=start; nodeIndex<end; th->sequence->rotationMatters.next(nodeIndex), j++)
|
|
{
|
|
// skip nodes outside of this detail
|
|
if (nodeIndex<a)
|
|
continue;
|
|
if (!rotBeenSet.test(nodeIndex))
|
|
{
|
|
QuatF q1,q2;
|
|
mShape->getRotation(*th->sequence,th->keyNum1,j,&q1);
|
|
mShape->getRotation(*th->sequence,th->keyNum2,j,&q2);
|
|
TSTransform::interpolate(q1,q2,th->keyPos,&smNodeCurrentRotations[nodeIndex]);
|
|
rotBeenSet.set(nodeIndex);
|
|
smRotationThreads[nodeIndex] = th;
|
|
}
|
|
}
|
|
|
|
j=0;
|
|
start = th->sequence->translationMatters.start();
|
|
end = b;
|
|
for (nodeIndex=start; nodeIndex<end; th->sequence->translationMatters.next(nodeIndex), j++)
|
|
{
|
|
if (nodeIndex<a)
|
|
continue;
|
|
if (!tranBeenSet.test(nodeIndex))
|
|
{
|
|
if (maskPosNodes.test(nodeIndex))
|
|
handleMaskedPositionNode(th,nodeIndex,j);
|
|
else
|
|
{
|
|
const Point3F & p1 = mShape->getTranslation(*th->sequence,th->keyNum1,j);
|
|
const Point3F & p2 = mShape->getTranslation(*th->sequence,th->keyNum2,j);
|
|
TSTransform::interpolate(p1,p2,th->keyPos,&smNodeCurrentTranslations[nodeIndex]);
|
|
smTranslationThreads[nodeIndex] = th;
|
|
}
|
|
tranBeenSet.set(nodeIndex);
|
|
}
|
|
}
|
|
|
|
if (scaleCurrentlyAnimated())
|
|
handleAnimatedScale(th,a,b,scaleBeenSet);
|
|
}
|
|
|
|
// transitions...
|
|
if (inTransition())
|
|
handleTransitionNodes(a,b);
|
|
|
|
// compute transforms
|
|
for (i=a; i<b; i++)
|
|
if (!mHandsOffNodes.test(i))
|
|
TSTransform::setMatrix(smNodeCurrentRotations[i],smNodeCurrentTranslations[i],&mNodeTransforms[i]);
|
|
|
|
// add scale onto transforms
|
|
if (scaleCurrentlyAnimated())
|
|
handleNodeScale(a,b);
|
|
|
|
// get callbacks...
|
|
start = getMax(mCallbackNodes.start(),a);
|
|
end = getMin(mCallbackNodes.end(),b);
|
|
for (i=start; i<end; mCallbackNodes.next(i))
|
|
mCallback(this,&mNodeTransforms[i],i,mCallbackData);
|
|
|
|
// handle blend sequences
|
|
for (i=firstBlend; i<mThreadList.size(); i++)
|
|
{
|
|
TSThread * th = mThreadList[i];
|
|
if (th->blendDisabled)
|
|
continue;
|
|
|
|
handleBlendSequence(th,a,b);
|
|
}
|
|
|
|
// multiply transforms...
|
|
for (i=a; i<b; i++)
|
|
{
|
|
S32 parentIdx = mShape->nodes[i].parentIndex;
|
|
if (parentIdx>=0)
|
|
{
|
|
MatrixF localMat = mNodeTransforms[i];
|
|
mNodeTransforms[i].mul(mNodeTransforms[parentIdx],localMat);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::handleDefaultScale(S32 a, S32 b, TSIntegerSet & scaleBeenSet)
|
|
{
|
|
// set default scale values (i.e., identity) and do any initialization
|
|
// relating to animated scale (since scale normally not animated)
|
|
|
|
smScaleThreads.setSize(mShape->nodes.size());
|
|
scaleBeenSet.takeAway(mCallbackNodes);
|
|
scaleBeenSet.takeAway(mHandsOffNodes);
|
|
if (animatesUniformScale())
|
|
{
|
|
smNodeCurrentUniformScales.setSize(mShape->nodes.size());
|
|
for (S32 i=a; i<b; i++)
|
|
if (scaleBeenSet.test(i))
|
|
{
|
|
smNodeCurrentUniformScales[i] = 1.0f;
|
|
smScaleThreads[i] = NULL;
|
|
}
|
|
}
|
|
else if (animatesAlignedScale())
|
|
{
|
|
smNodeCurrentAlignedScales.setSize(mShape->nodes.size());
|
|
for (S32 i=a; i<b; i++)
|
|
if (scaleBeenSet.test(i))
|
|
{
|
|
smNodeCurrentAlignedScales[i].set(1.0f,1.0f,1.0f);
|
|
smScaleThreads[i] = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
smNodeCurrentArbitraryScales.setSize(mShape->nodes.size());
|
|
for (S32 i=a; i<b; i++)
|
|
if (scaleBeenSet.test(i))
|
|
{
|
|
smNodeCurrentArbitraryScales[i].identity();
|
|
smScaleThreads[i] = NULL;
|
|
}
|
|
}
|
|
|
|
scaleBeenSet.overlap(mHandsOffNodes);
|
|
scaleBeenSet.overlap(mCallbackNodes);
|
|
}
|
|
|
|
void TSShapeInstance::handleTransitionNodes(S32 a, S32 b)
|
|
{
|
|
// handle transitions
|
|
S32 nodeIndex;
|
|
S32 start = mTransitionRotationNodes.start();
|
|
S32 end = b;
|
|
for (nodeIndex=start; nodeIndex<end; mTransitionRotationNodes.next(nodeIndex))
|
|
{
|
|
if (nodeIndex<a)
|
|
continue;
|
|
TSThread * thread = smRotationThreads[nodeIndex];
|
|
thread = thread && thread->transitionData.inTransition ? thread : NULL;
|
|
if (!thread)
|
|
{
|
|
// if not controlled by a sequence in transition then there must be
|
|
// some other thread out there that used to control us that is in
|
|
// transition now...use that thread to control interpolation
|
|
for (S32 i=0; i<mTransitionThreads.size(); i++)
|
|
{
|
|
if (mTransitionThreads[i]->transitionData.oldRotationNodes.test(nodeIndex) || mTransitionThreads[i]->sequence->rotationMatters.test(nodeIndex))
|
|
{
|
|
thread = mTransitionThreads[i];
|
|
break;
|
|
}
|
|
}
|
|
AssertFatal(thread!=NULL,"TSShapeInstance::handleRotTransitionNodes (rotation)");
|
|
}
|
|
QuatF tmpQ;
|
|
TSTransform::interpolate(mNodeReferenceRotations[nodeIndex].getQuatF(&tmpQ),smNodeCurrentRotations[nodeIndex],thread->transitionData.pos,&smNodeCurrentRotations[nodeIndex]);
|
|
}
|
|
|
|
// then translation
|
|
start = mTransitionTranslationNodes.start();
|
|
end = b;
|
|
for (nodeIndex=start; nodeIndex<end; mTransitionTranslationNodes.next(nodeIndex))
|
|
{
|
|
TSThread * thread = smTranslationThreads[nodeIndex];
|
|
thread = thread && thread->transitionData.inTransition ? thread : NULL;
|
|
if (!thread)
|
|
{
|
|
// if not controlled by a sequence in transition then there must be
|
|
// some other thread out there that used to control us that is in
|
|
// transition now...use that thread to control interpolation
|
|
for (S32 i=0; i<mTransitionThreads.size(); i++)
|
|
{
|
|
if (mTransitionThreads[i]->transitionData.oldTranslationNodes.test(nodeIndex) || mTransitionThreads[i]->sequence->translationMatters.test(nodeIndex))
|
|
{
|
|
thread = mTransitionThreads[i];
|
|
break;
|
|
}
|
|
}
|
|
AssertFatal(thread!=NULL,"TSShapeInstance::handleTransitionNodes (translation).");
|
|
}
|
|
Point3F & p = smNodeCurrentTranslations[nodeIndex];
|
|
Point3F & p1 = mNodeReferenceTranslations[nodeIndex];
|
|
Point3F & p2 = p;
|
|
F32 k = thread->transitionData.pos;
|
|
p.x = p1.x + k * (p2.x-p1.x);
|
|
p.y = p1.y + k * (p2.y-p1.y);
|
|
p.z = p1.z + k * (p2.z-p1.z);
|
|
}
|
|
|
|
// then scale...
|
|
if (scaleCurrentlyAnimated())
|
|
{
|
|
start = mTransitionScaleNodes.start();
|
|
end = b;
|
|
for (nodeIndex=start; nodeIndex<end; mTransitionScaleNodes.next(nodeIndex))
|
|
{
|
|
TSThread * thread = smScaleThreads[nodeIndex];
|
|
thread = thread && thread->transitionData.inTransition ? thread : NULL;
|
|
if (!thread)
|
|
{
|
|
// if not controlled by a sequence in transition then there must be
|
|
// some other thread out there that used to control us that is in
|
|
// transition now...use that thread to control interpolation
|
|
for (S32 i=0; i<mTransitionThreads.size(); i++)
|
|
{
|
|
if (mTransitionThreads[i]->transitionData.oldScaleNodes.test(nodeIndex) || mTransitionThreads[i]->sequence->scaleMatters.test(nodeIndex))
|
|
{
|
|
thread = mTransitionThreads[i];
|
|
break;
|
|
}
|
|
}
|
|
AssertFatal(thread!=NULL,"TSShapeInstance::handleTransitionNodes (scale).");
|
|
}
|
|
if (animatesUniformScale())
|
|
smNodeCurrentUniformScales[nodeIndex] += thread->transitionData.pos * (mNodeReferenceUniformScales[nodeIndex]-smNodeCurrentUniformScales[nodeIndex]);
|
|
else if (animatesAlignedScale())
|
|
TSTransform::interpolate(mNodeReferenceScaleFactors[nodeIndex],smNodeCurrentAlignedScales[nodeIndex],thread->transitionData.pos,&smNodeCurrentAlignedScales[nodeIndex]);
|
|
else
|
|
{
|
|
QuatF q;
|
|
TSTransform::interpolate(mNodeReferenceScaleFactors[nodeIndex],smNodeCurrentArbitraryScales[nodeIndex].mScale,thread->transitionData.pos,&smNodeCurrentArbitraryScales[nodeIndex].mScale);
|
|
TSTransform::interpolate(mNodeReferenceArbitraryScaleRots[nodeIndex].getQuatF(&q),smNodeCurrentArbitraryScales[nodeIndex].mRotate,thread->transitionData.pos,&smNodeCurrentArbitraryScales[nodeIndex].mRotate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::handleNodeScale(S32 a, S32 b)
|
|
{
|
|
if (animatesUniformScale())
|
|
{
|
|
for (S32 i=a; i<b; i++)
|
|
if (!mHandsOffNodes.test(i))
|
|
TSTransform::applyScale(smNodeCurrentUniformScales[i],&mNodeTransforms[i]);
|
|
}
|
|
else if (animatesAlignedScale())
|
|
{
|
|
for (S32 i=a; i<b; i++)
|
|
if (!mHandsOffNodes.test(i))
|
|
TSTransform::applyScale(smNodeCurrentAlignedScales[i],&mNodeTransforms[i]);
|
|
}
|
|
else
|
|
{
|
|
for (S32 i=a; i<b; i++)
|
|
if (!mHandsOffNodes.test(i))
|
|
TSTransform::applyScale(smNodeCurrentArbitraryScales[i],&mNodeTransforms[i]);
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::handleAnimatedScale(TSThread * thread, S32 a, S32 b, TSIntegerSet & scaleBeenSet)
|
|
{
|
|
S32 j=0;
|
|
S32 start = thread->sequence->scaleMatters.start();
|
|
S32 end = b;
|
|
|
|
// code the scale conversion (might need to "upgrade" from uniform to arbitrary, e.g.)
|
|
// code uniform, aligned, and arbitrary as 0,1, and 2, respectively,
|
|
// with sequence coding in first two bits, shape coding in next two bits
|
|
S32 code = 0;
|
|
if (thread->sequence->animatesAlignedScale())
|
|
code += 1;
|
|
else if (thread->sequence->animatesArbitraryScale())
|
|
code += 2;
|
|
if (animatesAlignedScale())
|
|
code +=3;
|
|
if (animatesArbitraryScale())
|
|
code += 6;
|
|
|
|
F32 uniformScale;
|
|
Point3F alignedScale;
|
|
TSScale arbitraryScale;
|
|
for (S32 nodeIndex=start; nodeIndex<end; thread->sequence->scaleMatters.next(nodeIndex), j++)
|
|
{
|
|
if (nodeIndex<a)
|
|
continue;
|
|
|
|
if (!scaleBeenSet.test(nodeIndex))
|
|
{
|
|
// compute scale in sequence format
|
|
switch (code)
|
|
{
|
|
case 0: // uniform -> uniform
|
|
case 1: // uniform -> aligned
|
|
case 2: // uniform -> arbitrary
|
|
{
|
|
F32 s1 = mShape->getUniformScale(*thread->sequence,thread->keyNum1,j);
|
|
F32 s2 = mShape->getUniformScale(*thread->sequence,thread->keyNum2,j);
|
|
uniformScale = TSTransform::interpolate(s1,s2,thread->keyPos);
|
|
alignedScale.set(uniformScale,uniformScale,uniformScale);
|
|
break;
|
|
}
|
|
case 4: // aligned -> aligned
|
|
case 5: // aligned -> arbitrary
|
|
{
|
|
const Point3F & s1 = mShape->getAlignedScale(*thread->sequence,thread->keyNum1,j);
|
|
const Point3F & s2 = mShape->getAlignedScale(*thread->sequence,thread->keyNum2,j);
|
|
TSTransform::interpolate(s1,s2,thread->keyPos,&alignedScale);
|
|
break;
|
|
}
|
|
case 8: // arbitrary -> arbitary
|
|
{
|
|
TSScale s1,s2;
|
|
mShape->getArbitraryScale(*thread->sequence,thread->keyNum1,j,&s1);
|
|
mShape->getArbitraryScale(*thread->sequence,thread->keyNum2,j,&s2);
|
|
TSTransform::interpolate(s1,s2,thread->keyPos,&arbitraryScale);
|
|
break;
|
|
}
|
|
default: AssertFatal(0,"TSShapeInstance::handleAnimatedScale"); break;
|
|
}
|
|
|
|
switch (code)
|
|
{
|
|
case 0: // uniform -> uniform
|
|
{
|
|
smNodeCurrentUniformScales[nodeIndex] = uniformScale;
|
|
break;
|
|
}
|
|
case 1: // uniform -> aligned
|
|
case 4: // aligned -> aligned
|
|
smNodeCurrentAlignedScales[nodeIndex] = alignedScale;
|
|
break;
|
|
case 2: // uniform -> arbitrary
|
|
case 5: // aligned -> arbitrary
|
|
{
|
|
smNodeCurrentArbitraryScales[nodeIndex].identity();
|
|
smNodeCurrentArbitraryScales[nodeIndex].mScale = alignedScale;
|
|
break;
|
|
}
|
|
case 8: // arbitrary -> arbitary
|
|
{
|
|
smNodeCurrentArbitraryScales[nodeIndex] = arbitraryScale;
|
|
break;
|
|
}
|
|
default: AssertFatal(0,"TSShapeInstance::handleAnimatedScale"); break;
|
|
}
|
|
smScaleThreads[nodeIndex] = thread;
|
|
scaleBeenSet.set(nodeIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::handleMaskedPositionNode(TSThread * th, S32 nodeIndex, S32 offset)
|
|
{
|
|
const Point3F & p1 = mShape->getTranslation(*th->sequence,th->keyNum1,offset);
|
|
const Point3F & p2 = mShape->getTranslation(*th->sequence,th->keyNum2,offset);
|
|
Point3F p;
|
|
TSTransform::interpolate(p1,p2,th->keyPos,&p);
|
|
|
|
if (!mMaskPosXNodes.test(nodeIndex))
|
|
smNodeCurrentTranslations[nodeIndex].x = p.x;
|
|
|
|
if (!mMaskPosYNodes.test(nodeIndex))
|
|
smNodeCurrentTranslations[nodeIndex].y = p.y;
|
|
|
|
if (!mMaskPosZNodes.test(nodeIndex))
|
|
smNodeCurrentTranslations[nodeIndex].z = p.z;
|
|
}
|
|
|
|
void TSShapeInstance::handleBlendSequence(TSThread * thread, S32 a, S32 b)
|
|
{
|
|
S32 jrot=0;
|
|
S32 jtrans=0;
|
|
S32 jscale=0;
|
|
TSIntegerSet nodeMatters = thread->sequence->translationMatters;
|
|
nodeMatters.overlap(thread->sequence->rotationMatters);
|
|
nodeMatters.overlap(thread->sequence->scaleMatters);
|
|
S32 start = nodeMatters.start();
|
|
S32 end = b;
|
|
for (S32 nodeIndex=start; nodeIndex<end; nodeMatters.next(nodeIndex))
|
|
{
|
|
// skip nodes outside of this detail
|
|
if (start<a || mDisableBlendNodes.test(nodeIndex))
|
|
{
|
|
if (thread->sequence->rotationMatters.test(nodeIndex))
|
|
jrot++;
|
|
if (thread->sequence->translationMatters.test(nodeIndex))
|
|
jtrans++;
|
|
if (thread->sequence->scaleMatters.test(nodeIndex))
|
|
jscale++;
|
|
continue;
|
|
}
|
|
|
|
MatrixF mat(true);
|
|
if (thread->sequence->rotationMatters.test(nodeIndex))
|
|
{
|
|
QuatF q1,q2;
|
|
mShape->getRotation(*thread->sequence,thread->keyNum1,jrot,&q1);
|
|
mShape->getRotation(*thread->sequence,thread->keyNum2,jrot,&q2);
|
|
QuatF quat;
|
|
TSTransform::interpolate(q1,q2,thread->keyPos,&quat);
|
|
TSTransform::setMatrix(quat,&mat);
|
|
jrot++;
|
|
}
|
|
|
|
if (thread->sequence->translationMatters.test(nodeIndex))
|
|
{
|
|
const Point3F & p1 = mShape->getTranslation(*thread->sequence,thread->keyNum1,jtrans);
|
|
const Point3F & p2 = mShape->getTranslation(*thread->sequence,thread->keyNum2,jtrans);
|
|
Point3F p;
|
|
TSTransform::interpolate(p1,p2,thread->keyPos,&p);
|
|
mat.setColumn(3,p);
|
|
jtrans++;
|
|
}
|
|
|
|
if (thread->sequence->scaleMatters.test(nodeIndex))
|
|
{
|
|
if (thread->sequence->animatesUniformScale())
|
|
{
|
|
F32 s1 = mShape->getUniformScale(*thread->sequence,thread->keyNum1,jscale);
|
|
F32 s2 = mShape->getUniformScale(*thread->sequence,thread->keyNum2,jscale);
|
|
F32 scale = TSTransform::interpolate(s1,s2,thread->keyPos);
|
|
TSTransform::applyScale(scale,&mat);
|
|
}
|
|
else if (animatesAlignedScale())
|
|
{
|
|
Point3F s1 = mShape->getAlignedScale(*thread->sequence,thread->keyNum1,jscale);
|
|
Point3F s2 = mShape->getAlignedScale(*thread->sequence,thread->keyNum2,jscale);
|
|
Point3F scale;
|
|
TSTransform::interpolate(s1,s2,thread->keyPos,&scale);
|
|
TSTransform::applyScale(scale,&mat);
|
|
}
|
|
else
|
|
{
|
|
TSScale s1,s2;
|
|
mShape->getArbitraryScale(*thread->sequence,thread->keyNum1,jscale,&s1);
|
|
mShape->getArbitraryScale(*thread->sequence,thread->keyNum2,jscale,&s2);
|
|
TSScale scale;
|
|
TSTransform::interpolate(s1,s2,thread->keyPos,&scale);
|
|
TSTransform::applyScale(scale,&mat);
|
|
}
|
|
jscale++;
|
|
}
|
|
|
|
// apply blend transform
|
|
mNodeTransforms[nodeIndex].mul(mat);
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// Other Animation:
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
void TSShapeInstance::animateIfls()
|
|
{
|
|
// for each ifl material decide which thread controls it and set it up
|
|
for (S32 i=0; i<mIflMaterialInstances.size(); i++)
|
|
{
|
|
IflMaterialInstance & iflMaterialInstance = mIflMaterialInstances[i];
|
|
iflMaterialInstance.frame = 0; // make sure that at least default value is set
|
|
for (S32 j=0; j<mThreadList.size(); j++)
|
|
{
|
|
TSThread * th = mThreadList[j];
|
|
if (th->sequence->iflMatters.test(i))
|
|
{
|
|
// lookup ifl properties
|
|
S32 firstFrameOffTimeIndex = iflMaterialInstance.iflMaterial->firstFrameOffTimeIndex;
|
|
S32 numFrames = iflMaterialInstance.iflMaterial->numFrames;
|
|
F32 iflDur = numFrames ? mShape->iflFrameOffTimes[firstFrameOffTimeIndex+numFrames-1] : 0.0f;
|
|
// where are we in the ifl
|
|
F32 time = th->pos * th->sequence->duration + th->sequence->toolBegin;
|
|
if (time>iflDur && iflDur>0.0f)
|
|
// handle looping ifl
|
|
time -= iflDur * (F32) ((S32) (time/iflDur));
|
|
// look up frame -- consider binary search
|
|
S32 k;
|
|
for (k=0; k<numFrames-1 && time > mShape->iflFrameOffTimes[firstFrameOffTimeIndex+k]; k++)
|
|
;
|
|
iflMaterialInstance.frame = k;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ifl is same for all sub-shapes, so clear them all out now
|
|
clearDirty(IflDirty);
|
|
}
|
|
|
|
void TSShapeInstance::animateVisibility(S32 ss)
|
|
{
|
|
S32 i;
|
|
if (!mMeshObjects.size())
|
|
return;
|
|
|
|
// find out who needs default values set
|
|
TSIntegerSet beenSet;
|
|
beenSet.setAll(mMeshObjects.size());
|
|
for (i=0; i<mThreadList.size(); i++)
|
|
beenSet.takeAway(mThreadList[i]->sequence->visMatters);
|
|
|
|
// set defaults
|
|
S32 a = mShape->subShapeFirstObject[ss];
|
|
S32 b = a + mShape->subShapeNumObjects[ss];
|
|
for (i=a; i<b; i++)
|
|
if (beenSet.test(i))
|
|
mMeshObjects[i].visible = mShape->objectStates[i].vis;
|
|
|
|
// go through each thread and set visibility on those objects that
|
|
// are not set yet and are controlled by that thread
|
|
for (i=0; i<mThreadList.size(); i++)
|
|
{
|
|
TSThread * th = mThreadList[i];
|
|
|
|
// the following is annoying...it should be changed at some point, but
|
|
// maybe not before T2 ships. Object states are stored together (frame,
|
|
// matFrame, visibility all in one structure). Thus, indexing into
|
|
// object state array for animation for any of these attributes needs to
|
|
// take into account whether or not the other attributes are also animated.
|
|
// The object states should eventually be separated (like the node states were)
|
|
// in order to save memory and save the following step.
|
|
TSIntegerSet objectMatters = th->sequence->frameMatters;
|
|
objectMatters.overlap(th->sequence->matFrameMatters);
|
|
objectMatters.overlap(th->sequence->visMatters);
|
|
|
|
// skip to beginining of this sub-shape
|
|
S32 j=0;
|
|
S32 start = objectMatters.start();
|
|
S32 end = b;
|
|
for (S32 objectIndex = start; objectIndex<b; objectMatters.next(objectIndex), j++)
|
|
{
|
|
if (!beenSet.test(objectIndex) && th->sequence->visMatters.test(objectIndex))
|
|
{
|
|
F32 state1 = mShape->getObjectState(*th->sequence,th->keyNum1,j).vis;
|
|
F32 state2 = mShape->getObjectState(*th->sequence,th->keyNum2,j).vis;
|
|
if ((state1-state2) * (state1-state2) > 0.99f)
|
|
// goes from 0 to 1 -- discreet jump
|
|
mMeshObjects[objectIndex].visible = th->keyPos<0.5f ? state1 : state2;
|
|
else
|
|
// interpolate between keyframes when visibility change is gradual
|
|
mMeshObjects[objectIndex].visible = (1.0f-th->keyPos) * state1 + th->keyPos * state2;
|
|
|
|
// record change so that later threads don't over-write us...
|
|
beenSet.set(objectIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::animateFrame(S32 ss)
|
|
{
|
|
S32 i;
|
|
if (!mMeshObjects.size())
|
|
return;
|
|
|
|
// find out who needs default values set
|
|
TSIntegerSet beenSet;
|
|
beenSet.setAll(mMeshObjects.size());
|
|
for (i=0; i<mThreadList.size(); i++)
|
|
beenSet.takeAway(mThreadList[i]->sequence->frameMatters);
|
|
|
|
// set defaults
|
|
S32 a = mShape->subShapeFirstObject[ss];
|
|
S32 b = a + mShape->subShapeNumObjects[ss];
|
|
for (i=a; i<b; i++)
|
|
if (beenSet.test(i))
|
|
mMeshObjects[i].frame = mShape->objectStates[i].frameIndex;
|
|
|
|
// go through each thread and set frame on those objects that
|
|
// are not set yet and are controlled by that thread
|
|
for (i=0; i<mThreadList.size(); i++)
|
|
{
|
|
TSThread * th = mThreadList[i];
|
|
|
|
// the following is annoying...it should be changed at some point, but
|
|
// maybe not before T2 ships. Object states are stored together (frame,
|
|
// matFrame, visibility all in one structure). Thus, indexing into
|
|
// object state array for animation for any of these attributes needs to
|
|
// take into account whether or not the other attributes are also animated.
|
|
// The object states should eventually be separated (like the node states were)
|
|
// in order to save memory and save the following step.
|
|
TSIntegerSet objectMatters = th->sequence->frameMatters;
|
|
objectMatters.overlap(th->sequence->matFrameMatters);
|
|
objectMatters.overlap(th->sequence->visMatters);
|
|
|
|
// skip to beginining of this sub-shape
|
|
S32 j=0;
|
|
S32 start = objectMatters.start();
|
|
S32 end = b;
|
|
for (S32 objectIndex = start; objectIndex<b; objectMatters.next(objectIndex), j++)
|
|
{
|
|
if (!beenSet.test(objectIndex) && th->sequence->frameMatters.test(objectIndex))
|
|
{
|
|
S32 key = (th->keyPos<0.5f) ? th->keyNum1 : th->keyNum2;
|
|
mMeshObjects[objectIndex].frame = mShape->getObjectState(*th->sequence,key,j).frameIndex;
|
|
|
|
// record change so that later threads don't over-write us...
|
|
beenSet.set(objectIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::animateMatFrame(S32 ss)
|
|
{
|
|
S32 i;
|
|
if (!mMeshObjects.size())
|
|
return;
|
|
|
|
// find out who needs default values set
|
|
TSIntegerSet beenSet;
|
|
beenSet.setAll(mMeshObjects.size());
|
|
for (i=0; i<mThreadList.size(); i++)
|
|
beenSet.takeAway(mThreadList[i]->sequence->matFrameMatters);
|
|
|
|
// set defaults
|
|
S32 a = mShape->subShapeFirstObject[ss];
|
|
S32 b = a + mShape->subShapeNumObjects[ss];
|
|
for (i=a; i<b; i++)
|
|
if (beenSet.test(i))
|
|
mMeshObjects[i].matFrame = mShape->objectStates[i].matFrameIndex;
|
|
|
|
// go through each thread and set matFrame on those objects that
|
|
// are not set yet and are controlled by that thread
|
|
for (i=0; i<mThreadList.size(); i++)
|
|
{
|
|
TSThread * th = mThreadList[i];
|
|
|
|
// the following is annoying...it should be changed at some point, but
|
|
// maybe not before T2 ships. Object states are stored together (frame,
|
|
// matFrame, visibility all in one structure). Thus, indexing into
|
|
// object state array for animation for any of these attributes needs to
|
|
// take into account whether or not the other attributes are also animated.
|
|
// The object states should eventually be separated (like the node states were)
|
|
// in order to save memory and save the following step.
|
|
TSIntegerSet objectMatters = th->sequence->frameMatters;
|
|
objectMatters.overlap(th->sequence->matFrameMatters);
|
|
objectMatters.overlap(th->sequence->visMatters);
|
|
|
|
// skip to beginining of this sub-shape
|
|
S32 j=0;
|
|
S32 start = objectMatters.start();
|
|
S32 end = b;
|
|
for (S32 objectIndex = start; objectIndex<end; objectMatters.next(objectIndex), j++)
|
|
{
|
|
if (!beenSet.test(objectIndex) && th->sequence->matFrameMatters.test(objectIndex))
|
|
{
|
|
S32 key = (th->keyPos<0.5f) ? th->keyNum1 : th->keyNum2;
|
|
mMeshObjects[objectIndex].matFrame = mShape->getObjectState(*th->sequence,key,j).matFrameIndex;
|
|
|
|
// record change so that later threads don't over-write us...
|
|
beenSet.set(objectIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::animateDecals(S32 ss)
|
|
{
|
|
S32 i;
|
|
if (!mDecalObjects.size())
|
|
return;
|
|
|
|
// find out who needs default values set
|
|
TSIntegerSet beenSet;
|
|
beenSet.setAll(mDecalObjects.size());
|
|
for (i=0; i<mThreadList.size(); i++)
|
|
beenSet.takeAway(mThreadList[i]->sequence->decalMatters);
|
|
|
|
// set defaults
|
|
S32 a = mShape->subShapeFirstDecal[ss];
|
|
S32 b = a + mShape->subShapeNumDecals[ss];
|
|
for (i=a; i<b; i++)
|
|
if (beenSet.test(i))
|
|
mDecalObjects[i].frame = mShape->decalStates[i].frameIndex;
|
|
|
|
// go through each thread and set frame on those decals that
|
|
// are not set yet and are controlled by that thread
|
|
for (i=0; i<mThreadList.size(); i++)
|
|
{
|
|
TSThread * th = mThreadList[i];
|
|
|
|
// skip to beginining of this sub-shape
|
|
S32 j=0;
|
|
S32 start = th->sequence->decalMatters.start();
|
|
S32 end = b;
|
|
for (S32 decalIndex=start; decalIndex<end; th->sequence->decalMatters.next(decalIndex), j++)
|
|
{
|
|
if (!beenSet.test(decalIndex))
|
|
{
|
|
S32 key = (th->keyPos<0.5f) ? th->keyNum1 : th->keyNum2;
|
|
mDecalObjects[decalIndex].frame = mShape->getDecalState(*th->sequence,key,j).frameIndex;
|
|
|
|
// record change so that later threads don't over-write us...
|
|
beenSet.set(decalIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// Animate (and initialize detail levels)
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
void TSShapeInstance::animate()
|
|
{
|
|
animate(mCurrentDetailLevel);
|
|
}
|
|
|
|
void TSShapeInstance::animate(S32 dl)
|
|
{
|
|
if (dl==-1)
|
|
// nothing to do
|
|
return;
|
|
|
|
S32 ss = mShape->details[dl].subShapeNum;
|
|
|
|
// this is a billboard detail...
|
|
if (ss<0)
|
|
return;
|
|
|
|
U32 dirtyFlags = mDirtyFlags[ss];
|
|
|
|
if (dirtyFlags & ThreadDirty)
|
|
{
|
|
sortThreads(mThreadList);
|
|
sortThreads(mTransitionThreads);
|
|
}
|
|
|
|
// animate ifl's?
|
|
if (dirtyFlags & IflDirty)
|
|
animateIfls();
|
|
|
|
// animate nodes?
|
|
if (dirtyFlags & TransformDirty)
|
|
animateNodes(ss);
|
|
|
|
// animate objects?
|
|
if (dirtyFlags & VisDirty)
|
|
animateVisibility(ss);
|
|
if (dirtyFlags & FrameDirty)
|
|
animateFrame(ss);
|
|
if (dirtyFlags & MatFrameDirty)
|
|
animateMatFrame(ss);
|
|
|
|
// animate decals?
|
|
if (dirtyFlags & DecalDirty)
|
|
animateDecals(ss);
|
|
|
|
mDirtyFlags[ss] = 0;
|
|
}
|
|
|
|
void TSShapeInstance::animateNodeSubtrees(bool forceFull)
|
|
{
|
|
// animate all the nodes for all the detail levels...
|
|
|
|
if (forceFull)
|
|
// force transforms to animate
|
|
setDirty(TransformDirty);
|
|
|
|
for (S32 i=0; i<mShape->subShapeNumNodes.size(); i++)
|
|
{
|
|
if (mDirtyFlags[i] & TransformDirty)
|
|
{
|
|
animateNodes(i);
|
|
mDirtyFlags[i] &= ~TransformDirty;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::animateSubtrees(bool forceFull)
|
|
{
|
|
// animate all the subtrees
|
|
|
|
if (forceFull)
|
|
// force full animate
|
|
setDirty(AllDirtyMask);
|
|
|
|
for (S32 i=0; i<mShape->details.size(); i++)
|
|
{
|
|
S32 ss = mShape->details[i].subShapeNum;
|
|
if (ss < 0) continue; // skip billboards
|
|
|
|
if (mDirtyFlags[ss] & TransformDirty)
|
|
{
|
|
animate(i);
|
|
mDirtyFlags[ss] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::addPath(TSThread *gt, F32 start, F32 end, MatrixF *mat)
|
|
{
|
|
// never get here while in transition...
|
|
AssertFatal(!gt->transitionData.inTransition, "TSShapeInstance::addPath - attempted to process while in transition state.");
|
|
|
|
if (!mat)
|
|
mat = &mGroundTransform;
|
|
|
|
MatrixF startInvM;
|
|
gt->getGround(start,&startInvM);
|
|
startInvM.inverse();
|
|
|
|
MatrixF endM;
|
|
gt->getGround(end,&endM);
|
|
|
|
MatrixF addM;
|
|
addM.mul(endM,startInvM);
|
|
endM.mul(addM,*mat);
|
|
*mat = endM;
|
|
}
|
|
|
|
bool TSShapeInstance::initGround()
|
|
{
|
|
for (S32 i=0; i<mThreadList.size(); i++)
|
|
{
|
|
TSThread * th = mThreadList[i];
|
|
if (!th->transitionData.inTransition && th->sequence->numGroundFrames>0)
|
|
{
|
|
mGroundThread = th;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TSShapeInstance::animateGround()
|
|
{
|
|
mGroundTransform.identity();
|
|
|
|
// pick thread which controls ground transform
|
|
// if we haven't already...
|
|
if (!mGroundThread && !initGround())
|
|
return;
|
|
|
|
S32 & loop = mGroundThread->path.loop;
|
|
float & start = mGroundThread->path.start;
|
|
float & end = mGroundThread->path.end;
|
|
|
|
// accumulate path transform
|
|
if (loop>0)
|
|
{
|
|
addPath(mGroundThread,start,1.0f);
|
|
while (--loop)
|
|
addPath(mGroundThread,0.0f,1.0f);
|
|
addPath(mGroundThread,0.0f,end);
|
|
}
|
|
else if (loop<0)
|
|
{
|
|
addPath(mGroundThread,start,0.0f);
|
|
while (++loop)
|
|
addPath(mGroundThread,1.0f,0.0f);
|
|
addPath(mGroundThread,1.0f,end);
|
|
}
|
|
else
|
|
addPath(mGroundThread,start,end);
|
|
start = end; // in case user tries to animateGround twice
|
|
}
|
|
|
|
void TSShapeInstance::deltaGround(TSThread * thread, F32 start, F32 end, MatrixF * mat)
|
|
{
|
|
if (!mat)
|
|
mat = &mGroundTransform;
|
|
|
|
mat->identity();
|
|
if (thread->transitionData.inTransition)
|
|
return;
|
|
|
|
F32 invDuration = 1.0f / thread->getDuration();
|
|
start *= invDuration;
|
|
end *= invDuration;
|
|
|
|
addPath(thread,start,end,mat);
|
|
}
|
|
|
|
// Simple case of above- get ground delta at given position in unit range
|
|
void TSShapeInstance::deltaGround1(TSThread * thread, F32 start, F32 end, MatrixF& mat)
|
|
{
|
|
mat.identity();
|
|
if (thread->transitionData.inTransition)
|
|
return;
|
|
addPath(thread, start, end, &mat);
|
|
}
|
|
|
|
void TSShapeInstance::setTriggerState(U32 stateNum, bool on)
|
|
{
|
|
AssertFatal(stateNum<=32 && stateNum>0,"TSShapeInstance::setTriggerState: state index out of range");
|
|
|
|
stateNum--; // stateNum externally 1..32, internally 0..31
|
|
U32 bit = 1 << stateNum;
|
|
if (on)
|
|
mTriggerStates |= bit;
|
|
else
|
|
mTriggerStates &= ~bit;
|
|
}
|
|
|
|
void TSShapeInstance::setTriggerStateBit(U32 stateBit, bool on)
|
|
{
|
|
if (on)
|
|
mTriggerStates |= stateBit;
|
|
else
|
|
mTriggerStates &= ~stateBit;
|
|
}
|
|
|
|
bool TSShapeInstance::getTriggerState(U32 stateNum, bool clearState)
|
|
{
|
|
AssertFatal(stateNum<=32 && stateNum>0,"TSShapeInstance::getTriggerState: state index out of range");
|
|
|
|
stateNum--; // stateNum externally 1..32, internally 0..31
|
|
U32 bit = 1 << stateNum;
|
|
bool ret = ((mTriggerStates & bit)!=0);
|
|
if (clearState)
|
|
mTriggerStates &= ~bit;
|
|
return ret;
|
|
}
|
|
|
|
void TSShapeInstance::setNodeAnimationState(S32 nodeIndex, U32 animationState)
|
|
{
|
|
AssertFatal((animationState & ~(MaskNodeAll|MaskNodeHandsOff|MaskNodeCallback)) == 0,"TSShapeInstance::setNodeAnimationState (1)");
|
|
|
|
// hands-off & callback takes precedance
|
|
if (animationState & MaskNodeHandsOff)
|
|
animationState = MaskNodeHandsOff | MaskNodeBlend;
|
|
else if (animationState & MaskNodeCallback)
|
|
animationState = MaskNodeCallback | MaskNodeBlend;
|
|
|
|
// if we're not changing anything then get out of here now
|
|
if (animationState == getNodeAnimationState(nodeIndex))
|
|
return;
|
|
|
|
// feel so dirty...
|
|
setDirty(AllDirtyMask);
|
|
|
|
if (animationState & MaskNodeAllButBlend)
|
|
{
|
|
if (animationState & MaskNodeRotation)
|
|
mMaskRotationNodes.set(nodeIndex);
|
|
if (animationState & MaskNodePosX)
|
|
mMaskPosXNodes.set(nodeIndex);
|
|
if (animationState & MaskNodePosY)
|
|
mMaskPosYNodes.set(nodeIndex);
|
|
if (animationState & MaskNodePosZ)
|
|
mMaskPosZNodes.set(nodeIndex);
|
|
}
|
|
else
|
|
{
|
|
// no masking clear out all the masking lists
|
|
mMaskRotationNodes.clear(nodeIndex);
|
|
mMaskPosXNodes.clear(nodeIndex);
|
|
mMaskPosYNodes.clear(nodeIndex);
|
|
mMaskPosZNodes.clear(nodeIndex);
|
|
}
|
|
|
|
if (animationState & MaskNodeBlend)
|
|
mDisableBlendNodes.set(nodeIndex);
|
|
else
|
|
mDisableBlendNodes.clear(nodeIndex);
|
|
|
|
if (animationState & MaskNodeHandsOff)
|
|
mHandsOffNodes.set(nodeIndex);
|
|
else
|
|
mHandsOffNodes.clear(nodeIndex);
|
|
|
|
if (animationState & MaskNodeCallback)
|
|
mCallbackNodes.set(nodeIndex);
|
|
else
|
|
mCallbackNodes.clear(nodeIndex);
|
|
}
|
|
|
|
U32 TSShapeInstance::getNodeAnimationState(S32 nodeIndex)
|
|
{
|
|
U32 ret = 0;
|
|
if (mMaskRotationNodes.test(nodeIndex))
|
|
ret |= MaskNodeRotation;
|
|
if (mMaskPosXNodes.test(nodeIndex))
|
|
ret |= MaskNodePosX;
|
|
if (mMaskPosYNodes.test(nodeIndex))
|
|
ret |= MaskNodePosY;
|
|
if (mMaskPosZNodes.test(nodeIndex))
|
|
ret |= MaskNodePosZ;
|
|
if (mDisableBlendNodes.test(nodeIndex))
|
|
ret |= MaskNodeBlend;
|
|
if (mHandsOffNodes.test(nodeIndex))
|
|
ret |= MaskNodeHandsOff;
|
|
if (mCallbackNodes.test(nodeIndex))
|
|
ret |= MaskNodeCallback;
|
|
return ret;
|
|
}
|