Torque3D/Engine/source/ts/tsThread.cpp
AzaezelX 03c99f845b fix crash with unclean exit
for player, if we're unmounting because we're being deleted, don't bother animating
for tsthreads in general, not much point in removing the threadlist, then checking if it's scaled
2025-07-14 08:08:22 -05:00

839 lines
25 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "ts/tsShapeInstance.h"
//-------------------------------------------------------------------------------------
// This file contains the shape instance thread class (defined in tsShapeInstance.h)
// and the tsShapeInstance functions to interface with the thread class.
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
// Thread class
//-------------------------------------------------------------------------------------
// given a position on the thread, choose correct keyframes
// slight difference between one-shot and cyclic sequences -- see comments below for details
void TSThread::selectKeyframes(F32 pos, const TSSequence * seq, S32 * k1, S32 * k2, F32 * kpos)
{
S32 numKF = seq->numKeyframes;
F32 kf;
if (seq->isCyclic())
{
// cyclic sequence:
// pos=0 and pos=1 are equivalent, so we don't have a keyframe at pos=1
// last keyframe corresponds to pos=n/(n-1) up to (not including) pos=1
// (where n == num keyframes)
AssertFatal(pos>=0.0f && pos<1.0f,"TSThread::selectKeyframes");
kf = pos * (F32) (numKF);
// set keyPos
if (kpos)
*kpos = kf - (S32) kf;
// make sure compiler doing what we want...
AssertFatal(*kpos>=0.0f && *kpos<1.0f,"TSThread::selectKeyframes");
S32 kfIdx1 = (S32) kf;
// following assert could happen if pos1<1 && pos1==1...paradoxically...
AssertFatal(kfIdx1<=seq->numKeyframes,"TSThread::selectKeyframes");
S32 kfIdx2 = (kfIdx1==seq->numKeyframes-1) ? 0 : kfIdx1+1;
if (k1)
*k1 = kfIdx1;
if (k2)
*k2 = kfIdx2;
}
else
{
// one-shot sequence:
// pos=0 and pos=1 are now different, so we have a keyframe at pos=1
// last keyframe corresponds to pos=1
// rest of the keyframes are equally spaced (so 1/(n-1) pos units long)
// (where n == num keyframes)
AssertFatal(pos>=0.0f && pos<=1.0f,"TSThread::selectKeyframes");
if (pos==1.0f)
{
if (kpos)
*kpos = 0.0f;
if (k1)
*k1 = seq->numKeyframes-1;
if (k2)
*k2 = seq->numKeyframes-1;
}
else
{
kf = pos * (F32) (numKF-1);
// set keyPos
if (kpos)
*kpos = kf - (S32) kf;
S32 kfIdx1 = (S32) kf;
// following assert could happen if pos1<1 && pos1==1...paradoxically...
AssertFatal(kfIdx1<seq->numKeyframes,"TSThread::selectKeyFrames: invalid keyframe!");
S32 kfIdx2 = kfIdx1+1;
if (k1)
*k1 = kfIdx1;
if (k2)
*k2 = kfIdx2;
}
}
}
void TSThread::getGround(F32 t, MatrixF * pMat)
{
static const QuatF unitRotation(0,0,0,1);
static const Point3F unitTranslation = Point3F::Zero;
const QuatF * q1, * q2;
QuatF rot1,rot2;
const Point3F * p1, * p2;
// if N = sequence->numGroundFrames, then there are N+1 positions we
// interpolate betweeen: 0/N, 1/N ... N/N
// we need to convert the p passed to us into 2 ground keyframes:
// the 0.99999f is in case 'p' is exactly 1.0f, which is legal but 'kf'
// needs to be strictly less than 'sequence->numGroundFrames'
F32 kf = 0.999999f * t * (F32) getSequence()->numGroundFrames;
// get frame number and interp param (kpos)
S32 frame = (S32)kf;
F32 kpos = kf - (F32)frame;
// now point pT1 and pT2 at transforms for keyframes 'frame' and 'frame+1'
// following a little strange: first ground keyframe (0/N in comment above) is
// assumed to be ident. and not found in the list.
if (frame)
{
p1 = &mShapeInstance->mShape->groundTranslations[getSequence()->firstGroundFrame + frame - 1];
q1 = &mShapeInstance->mShape->groundRotations[getSequence()->firstGroundFrame + frame - 1].getQuatF(&rot1);
}
else
{
p1 = &unitTranslation;
q1 = &unitRotation;
}
// similar to above, ground keyframe number 'frame+1' is actually offset by 'frame'
p2 = &mShapeInstance->mShape->groundTranslations[getSequence()->firstGroundFrame + frame];
q2 = &mShapeInstance->mShape->groundRotations[getSequence()->firstGroundFrame + frame].getQuatF(&rot2);
QuatF q;
Point3F p;
TSTransform::interpolate(*q1,*q2,kpos,&q);
TSTransform::interpolate(*p1,*p2,kpos,&p);
TSTransform::setMatrix(q,p,pMat);
}
void TSThread::setSequence(S32 seq, F32 toPos)
{
const TSShape * shape = mShapeInstance->mShape;
AssertFatal(shape && shape->sequences.size()>seq && toPos>=0.0f && toPos<=1.0f,
"TSThread::setSequence: invalid shape handle, sequence number, or position.");
mShapeInstance->clearTransition(this);
sequence = seq;
priority = getSequence()->priority;
mSeqPos = toPos;
makePath = getSequence()->makePath();
path.start = path.end = 0;
path.loop = 0;
// 1.0f doesn't exist on cyclic sequences
if (mSeqPos>0.9999f && getSequence()->isCyclic())
mSeqPos = 0.9999f;
// select keyframes
selectKeyframes(mSeqPos,getSequence(),&keyNum1,&keyNum2,&keyPos);
}
void TSThread::transitionToSequence(S32 seq, F32 toPos, F32 duration, bool continuePlay)
{
AssertFatal(duration>=0.0f,"TSThread::transitionToSequence: negative duration not allowed");
// make sure these nodes are smoothly interpolated to new positions...
// basically, any node we controlled just prior to transition, or at any stage
// of the transition is interpolated. If we start to transtion from A to B,
// but before reaching B we transtion to C, we interpolate all nodes controlled
// by A, B, or C to their new position.
if (transitionData.inTransition)
{
transitionData.oldRotationNodes.overlap(getSequence()->rotationMatters);
transitionData.oldTranslationNodes.overlap(getSequence()->translationMatters);
transitionData.oldScaleNodes.overlap(getSequence()->scaleMatters);
}
else
{
transitionData.oldRotationNodes = getSequence()->rotationMatters;
transitionData.oldTranslationNodes = getSequence()->translationMatters;
transitionData.oldScaleNodes = getSequence()->scaleMatters;
}
// set time characteristics of transition
transitionData.oldSequence = sequence;
transitionData.oldPos = mSeqPos;
transitionData.duration = duration;
transitionData.pos = 0.0f;
transitionData.direction = timeScale>0.0f ? 1.0f : -1.0f;
transitionData.targetScale = continuePlay ? 1.0f : 0.0f;
// in transition...
transitionData.inTransition = true;
// set target sequence data
sequence = seq;
priority = getSequence()->priority;
mSeqPos = toPos;
makePath = getSequence()->makePath();
path.start = path.end = 0;
path.loop = 0;
// 1.0f doesn't exist on cyclic sequences
if (mSeqPos>0.9999f && getSequence()->isCyclic())
mSeqPos = 0.9999f;
// select keyframes
selectKeyframes(mSeqPos,getSequence(),&keyNum1,&keyNum2,&keyPos);
}
bool TSThread::isInTransition()
{
return transitionData.inTransition;
}
void TSThread::animateTriggers()
{
if (!getSequence()->numTriggers)
return;
switch (path.loop)
{
case -1 :
activateTriggers(path.start,0);
activateTriggers(1,path.end);
break;
case 0 :
activateTriggers(path.start,path.end);
break;
case 1 :
activateTriggers(path.start,1);
activateTriggers(0,path.end);
break;
default:
{
if (path.loop>0)
{
activateTriggers(path.end,1);
activateTriggers(0,path.end);
}
else
{
activateTriggers(path.end,0);
activateTriggers(1,path.end);
}
}
}
}
void TSThread::activateTriggers(F32 a, F32 b)
{
S32 i;
const TSShape * shape = mShapeInstance->mShape;
S32 firstTrigger = getSequence()->firstTrigger;
S32 numTriggers = getSequence()->numTriggers;
// first find triggers at position a and b
// we assume there aren't many triggers, so
// search is linear
F32 lastPos = -1.0f;
S32 aIndex = numTriggers+firstTrigger; // initialized to handle case where pos past all triggers
S32 bIndex = numTriggers+firstTrigger; // initialized to handle case where pos past all triggers
for (i=firstTrigger; i<numTriggers+firstTrigger; i++)
{
TSShape::Trigger currentTrigger = shape->triggers[i];
// is a between this trigger and previous one...
if (a>lastPos && a <= currentTrigger.pos)
aIndex = i;
// is b between this trigger and previous one...
if (b>lastPos && b <= currentTrigger.pos)
bIndex = i;
lastPos = currentTrigger.pos;
}
// activate triggers between aIndex and bIndex (depends on direction)
if (aIndex<=bIndex)
{
for (i=aIndex; i<bIndex; i++)
{
U32 state = shape->triggers[i].state;
bool on = (state & TSShape::Trigger::StateOn)!=0;
mShapeInstance->setTriggerStateBit(state & TSShape::Trigger::StateMask, on);
}
}
else
{
for (i=aIndex-1; i>=bIndex; i--)
{
U32 state = shape->triggers[i].state;
bool on = (state & TSShape::Trigger::StateOn)!=0;
if (state & TSShape::Trigger::InvertOnReverse)
on = !on;
mShapeInstance->setTriggerStateBit(state & TSShape::Trigger::StateMask, on);
}
}
}
F32 TSThread::getPos()
{
return transitionData.inTransition ? transitionData.pos : mSeqPos;
}
F32 TSThread::getTime()
{
return transitionData.inTransition ? transitionData.pos * transitionData.duration : mSeqPos * getSequence()->duration;
}
F32 TSThread::getDuration()
{
return transitionData.inTransition ? transitionData.duration : getSequence()->duration;
}
F32 TSThread::getScaledDuration()
{
return getDuration() / mFabs(timeScale);
}
F32 TSThread::getTimeScale()
{
return timeScale;
}
void TSThread::setTimeScale(F32 ts)
{
timeScale = ts;
}
void TSThread::advancePos(F32 delta)
{
if (mFabs(delta)>0.00001f)
{
// make dirty what this thread changes
U32 dirtyFlags = getSequence()->dirtyFlags | (transitionData.inTransition ? TSShapeInstance::TransformDirty : 0);
for (S32 i=0; i<mShapeInstance->getShape()->subShapeFirstNode.size(); i++)
mShapeInstance->mDirtyFlags[i] |= dirtyFlags;
}
if (transitionData.inTransition)
{
transitionData.pos += transitionData.direction * delta;
if (transitionData.pos<0 || transitionData.pos>=1.0f)
{
mShapeInstance->clearTransition(this);
if (transitionData.pos<0.0f)
// return to old sequence
mShapeInstance->setSequence(this,transitionData.oldSequence,transitionData.oldPos);
}
// re-adjust delta to be correct time-wise
delta *= transitionData.targetScale * transitionData.duration / getSequence()->duration;
}
// even if we are in a transition, keep playing the sequence
if (makePath)
{
path.start = mSeqPos;
mSeqPos += delta;
if (!getSequence()->isCyclic())
{
mSeqPos = mClampF(mSeqPos, 0.0f, 1.0f);
path.loop = 0;
}
else
{
path.loop = (S32)mSeqPos;
if (mSeqPos < 0.0f)
path.loop--;
mSeqPos -= path.loop;
// following necessary because of floating point roundoff errors
if (mSeqPos < 0.0f) mSeqPos += 1.0f;
if (mSeqPos >= 1.0f) mSeqPos -= 1.0f;
}
path.end = mSeqPos;
animateTriggers(); // do this automatically...no need for user to call it
AssertFatal(mSeqPos >=0.0f && mSeqPos <=1.0f,"TSThread::advancePos (1)");
AssertFatal(!getSequence()->isCyclic() || mSeqPos<1.0f,"TSThread::advancePos (2)");
}
else
{
mSeqPos += delta;
if (!getSequence()->isCyclic())
mSeqPos = mClampF(mSeqPos, 0.0f, 1.0f);
else
{
mSeqPos -= S32(mSeqPos);
// following necessary because of floating point roundoff errors
if (mSeqPos < 0.0f) mSeqPos += 1.0f;
if (mSeqPos >= 1.0f) mSeqPos -= 1.0f;
}
AssertFatal(mSeqPos >=0.0f && mSeqPos <=1.0f,"TSThread::advancePos (3)");
AssertFatal(!getSequence()->isCyclic() || mSeqPos<1.0f,"TSThread::advancePos (4)");
}
// select keyframes
selectKeyframes(mSeqPos,getSequence(),&keyNum1,&keyNum2,&keyPos);
}
void TSThread::advanceTime(F32 delta)
{
advancePos(timeScale * delta / getDuration());
}
void TSThread::setPos(F32 pos)
{
advancePos(pos-getPos());
}
void TSThread::setTime(F32 time)
{
setPos(timeScale * time/getDuration());
}
S32 TSThread::getKeyframeCount()
{
AssertFatal(!transitionData.inTransition,"TSThread::getKeyframeCount: not while in transition");
return getSequence()->numKeyframes + 1;
}
S32 TSThread::getKeyframeNumber()
{
AssertFatal(!transitionData.inTransition,"TSThread::getKeyframeNumber: not while in transition");
return keyNum1;
}
void TSThread::setKeyframeNumber(S32 kf)
{
AssertFatal(kf>=0 && kf<= getSequence()->numKeyframes,
"TSThread::setKeyframeNumber: invalid frame specified.");
AssertFatal(!transitionData.inTransition,"TSThread::setKeyframeNumber: not while in transition");
keyNum1 = keyNum2 = kf;
keyPos = 0;
mSeqPos = 0;
}
TSThread::TSThread(TSShapeInstance * _shapeInst)
{
timeScale = 1.0f;
mShapeInstance = _shapeInst;
transitionData.inTransition = false;
blendDisabled = false;
setSequence(0,0.0f);
}
S32 TSThread::operator<(const TSThread & th2) const
{
if (getSequence()->isBlend() == th2.getSequence()->isBlend())
{
// both blend or neither blend, sort based on priority only -- higher priority first
S32 ret = 0; // do it this way to (hopefully) take advantage of 'conditional move' assembly instruction
if (priority > th2.priority)
ret = -1;
if (th2.priority > priority)
ret = 1;
return ret;
}
else
{
// one is blend, the other is not...sort based on blend -- non-blended first
AssertFatal(!getSequence()->isBlend() || !th2.getSequence()->isBlend(),"compareThreads: unequal 'trues'");
S32 ret = -1; // do it this way to (hopefully) take advantage of 'conditional move' assembly instruction
if (getSequence()->isBlend())
ret = 1;
return ret;
}
}
//-------------------------------------------------------------------------------------
// TSShapeInstance Thread Interface -- more implemented in header file
//-------------------------------------------------------------------------------------
TSThread * TSShapeInstance::addThread()
{
if (mShape->sequences.empty())
return NULL;
mThreadList.increment();
mThreadList.last() = new TSThread(this);
setDirty(AllDirtyMask);
return mThreadList.last();
}
TSThread * TSShapeInstance::getThread(S32 threadNumber)
{
AssertFatal(threadNumber < mThreadList.size() && threadNumber>=0,"TSShapeInstance::getThread: threadNumber out of bounds.");
return mThreadList[threadNumber];
}
void TSShapeInstance::destroyThread(TSThread * thread)
{
if (!thread)
return;
clearTransition(thread);
S32 i;
for (i=0; i<mThreadList.size(); i++)
if (thread==mThreadList[i])
break;
AssertFatal(i<mThreadList.size(),"TSShapeInstance::destroyThread was requested to destroy a thread that this instance doesn't own!");
delete mThreadList[i];
mThreadList.erase(i);
setDirty(AllDirtyMask);
}
U32 TSShapeInstance::threadCount()
{
return mThreadList.size();
}
void TSShapeInstance::setSequence(TSThread * thread, S32 seq, F32 pos)
{
if ( (thread->transitionData.inTransition && mTransitionThreads.size()>1) || mTransitionThreads.size()>0)
{
// if we have transitions, make sure transforms are up to date...
sortThreads();
animateNodeSubtrees();
}
thread->setSequence(seq,pos);
setDirty(AllDirtyMask);
mGroundThread = NULL;
if (mScaleCurrentlyAnimated && !thread->getSequence()->animatesScale())
checkScaleCurrentlyAnimated();
else if (!mScaleCurrentlyAnimated && thread->getSequence()->animatesScale())
mScaleCurrentlyAnimated=true;
updateTransitions();
}
U32 TSShapeInstance::getSequence(TSThread * thread)
{
//AssertFatal( thread->sequence >= 0, "TSShapeInstance::getSequence: range error A");
//AssertFatal( thread->sequence < mShape->sequences.size(), "TSShapeInstance::getSequence: range error B");
return (U32)thread->sequence;
}
void TSShapeInstance::transitionToSequence(TSThread * thread, S32 seq, F32 pos, F32 duration, bool continuePlay)
{
// make sure all transforms on all detail levels are accurate
sortThreads();
animateNodeSubtrees();
thread->transitionToSequence(seq,pos,duration,continuePlay);
setDirty(AllDirtyMask);
mGroundThread = NULL;
const TSShape::Sequence* threadSequence = thread->getSequence();
if (mScaleCurrentlyAnimated && !threadSequence->animatesScale())
checkScaleCurrentlyAnimated();
else if (!mScaleCurrentlyAnimated && threadSequence->animatesScale())
mScaleCurrentlyAnimated=true;
mTransitionRotationNodes.overlap(thread->transitionData.oldRotationNodes);
mTransitionRotationNodes.overlap(threadSequence->rotationMatters);
mTransitionTranslationNodes.overlap(thread->transitionData.oldTranslationNodes);
mTransitionTranslationNodes.overlap(threadSequence->translationMatters);
mTransitionScaleNodes.overlap(thread->transitionData.oldScaleNodes);
mTransitionScaleNodes.overlap(threadSequence->scaleMatters);
// if we aren't already in the list of transition threads, add us now
S32 i;
for (i=0; i<mTransitionThreads.size(); i++)
if (mTransitionThreads[i]==thread)
break;
if (i==mTransitionThreads.size())
mTransitionThreads.push_back(thread);
updateTransitions();
}
void TSShapeInstance::clearTransition(TSThread * thread)
{
if (!thread->transitionData.inTransition)
return;
// if other transitions are still playing,
// make sure transforms are up to date
if (mTransitionThreads.size()>1)
animateNodeSubtrees();
// turn off transition...
thread->transitionData.inTransition = false;
// remove us from transition list
S32 i;
if (mTransitionThreads.size() != 0) {
for (i=0; i<mTransitionThreads.size(); i++)
if (mTransitionThreads[i]==thread)
break;
AssertFatal(i!=mTransitionThreads.size(),"TSShapeInstance::clearTransition");
mTransitionThreads.erase(i);
}
// recompute transitionNodes
mTransitionRotationNodes.clearAll();
mTransitionTranslationNodes.clearAll();
mTransitionScaleNodes.clearAll();
for (i=0; i<mTransitionThreads.size(); i++)
{
mTransitionRotationNodes.overlap(mTransitionThreads[i]->transitionData.oldRotationNodes);
mTransitionRotationNodes.overlap(mTransitionThreads[i]->getSequence()->rotationMatters);
mTransitionTranslationNodes.overlap(mTransitionThreads[i]->transitionData.oldTranslationNodes);
mTransitionTranslationNodes.overlap(mTransitionThreads[i]->getSequence()->translationMatters);
mTransitionScaleNodes.overlap(mTransitionThreads[i]->transitionData.oldScaleNodes);
mTransitionScaleNodes.overlap(mTransitionThreads[i]->getSequence()->scaleMatters);
}
setDirty(ThreadDirty);
updateTransitions();
}
void TSShapeInstance::updateTransitions()
{
if (mTransitionThreads.empty())
return;
TSIntegerSet transitionNodes;
updateTransitionNodeTransforms(transitionNodes);
S32 i;
mNodeReferenceRotations.setSize(mShape->nodes.size());
mNodeReferenceTranslations.setSize(mShape->nodes.size());
for (i=0; i<mShape->nodes.size(); i++)
{
if (mTransitionRotationNodes.test(i))
mNodeReferenceRotations[i].set(smNodeCurrentRotations[i]);
if (mTransitionTranslationNodes.test(i))
mNodeReferenceTranslations[i] = smNodeCurrentTranslations[i];
}
if (animatesScale())
{
// Make sure smNodeXXXScale arrays have been resized
TSIntegerSet dummySet;
handleDefaultScale(0, 0, dummySet);
if (animatesUniformScale())
{
mNodeReferenceUniformScales.setSize(mShape->nodes.size());
for (i=0; i<mShape->nodes.size(); i++)
{
if (mTransitionScaleNodes.test(i))
mNodeReferenceUniformScales[i] = smNodeCurrentUniformScales[i];
}
}
else if (animatesAlignedScale())
{
mNodeReferenceScaleFactors.setSize(mShape->nodes.size());
for (i=0; i<mShape->nodes.size(); i++)
{
if (mTransitionScaleNodes.test(i))
mNodeReferenceScaleFactors[i] = smNodeCurrentAlignedScales[i];
}
}
else
{
mNodeReferenceScaleFactors.setSize(mShape->nodes.size());
mNodeReferenceArbitraryScaleRots.setSize(mShape->nodes.size());
for (i=0; i<mShape->nodes.size(); i++)
{
if (mTransitionScaleNodes.test(i))
{
mNodeReferenceScaleFactors[i] = smNodeCurrentArbitraryScales[i].mScale;
mNodeReferenceArbitraryScaleRots[i].set(smNodeCurrentArbitraryScales[i].mRotate);
}
}
}
}
// reset transition durations to account for new reference transforms
for (i=0; i<mTransitionThreads.size(); i++)
{
TSThread * th = mTransitionThreads[i];
if (th->transitionData.inTransition)
{
th->transitionData.duration *= 1.0f - th->transitionData.pos;
th->transitionData.pos = 0.0f;
}
}
}
void TSShapeInstance::checkScaleCurrentlyAnimated()
{
mScaleCurrentlyAnimated=true;
for (S32 i=0; i<mThreadList.size(); i++)
if (mThreadList[i]->getSequence()->animatesScale())
return;
mScaleCurrentlyAnimated=false;
}
void TSShapeInstance::setBlendEnabled(TSThread * thread, bool blendOn)
{
thread->blendDisabled = !blendOn;
}
bool TSShapeInstance::getBlendEnabled(TSThread * thread)
{
return !thread->blendDisabled;
}
void TSShapeInstance::setPriority(TSThread * thread, F32 priority)
{
thread->priority = priority;
}
F32 TSShapeInstance::getPriority(TSThread * thread)
{
return thread->priority;
}
F32 TSShapeInstance::getTime(TSThread * thread)
{
return thread->getTime();
}
F32 TSShapeInstance::getPos(TSThread * thread)
{
return thread->getPos();
}
void TSShapeInstance::setTime(TSThread * thread, F32 time)
{
thread->setTime(time);
}
void TSShapeInstance::setPos(TSThread * thread, F32 pos)
{
thread->setPos(pos);
}
bool TSShapeInstance::isInTransition(TSThread * thread)
{
return thread->isInTransition();
}
F32 TSShapeInstance::getTimeScale(TSThread * thread)
{
return thread->getTimeScale();
}
void TSShapeInstance::setTimeScale(TSThread * thread, F32 timeScale)
{
thread->setTimeScale(timeScale);
}
F32 TSShapeInstance::getDuration(TSThread * thread)
{
return thread->getDuration();
}
F32 TSShapeInstance::getScaledDuration(TSThread * thread)
{
return thread->getScaledDuration();
}
S32 TSShapeInstance::getKeyframeCount(TSThread * thread)
{
return thread->getKeyframeCount();
}
S32 TSShapeInstance::getKeyframeNumber(TSThread * thread)
{
return thread->getKeyframeNumber();
}
void TSShapeInstance::setKeyframeNumber(TSThread * thread, S32 kf)
{
thread->setKeyframeNumber(kf);
}
// advance time on a particular thread
void TSShapeInstance::advanceTime(F32 delta, TSThread * thread)
{
thread->advanceTime(delta);
}
// advance time on all threads
void TSShapeInstance::advanceTime(F32 delta)
{
for (S32 i=0; i<mThreadList.size(); i++)
mThreadList[i]->advanceTime(delta);
}
// advance pos on a particular thread
void TSShapeInstance::advancePos(F32 delta, TSThread * thread)
{
thread->advancePos(delta);
}
// advance pos on all threads
void TSShapeInstance::advancePos(F32 delta)
{
for (S32 i=0; i<mThreadList.size(); i++)
mThreadList[i]->advancePos(delta);
}