Plastic Gem #2: Animation Threads
by Paul Dana · 06/10/2008 (6:48 am) · 37 comments
Download Code File

Plastic Gem #2: Animation Threads.
Difficulty: Moderate
For a list of gems see the Gem A Day page.
Hello again from Plastic Games. It is time for gem number two in our Gem A Day effort. This gem adds lots of useful features to Torque's powerful script based Animation Thread system. You must install the files from Gem #1: Placeable Shapes before you can see the Grandfather Clock example explained at the end of this resource.
The Grandfather Clock

This resource was designed to give more functionality to the existing thread system. By the end of this resource you will be able to pause a thread, set a thread's position 0(start) -1(end), play a segment of a thread, replay a thread directly, as well as adjust a thread's speed.
All the code presented here was developed by Paul Dana, lead programmer at Plastic Games.
This code can be implemented in TGEA 1.7 and TGE 1.5.2. TGEA locations will be in parentheses
1)Provide new variables in the shapebase thread struct.
In game(T3D)/ShapeBase.h, in the Shapebase class
Struct Thread{
bool forward; // add new code AFTER this line
}
2) Declare new interfaces for thread manipulation.
void stopThreadSound(Thread& thread); // add new code AFTER this line
NOTE BESURE TO COMMENT OUT
void advanceThreads(F32 dt);
3) Initialize our new thread variables.
shapebase.cc
in ShapeBase::ShapeBase()
mScriptThread[i].forward = true;//add code after this line
bool ShapeBase::setThreadSequence(U32 slot,S32 seq,bool reset) // add code AFTER this function
BESURE TO REMOVE OLD
stopThread()
updateThread()
5) Define the interface functions
void ShapeBase::startSequenceSound(Thread& thread) // after this function
NOTE the last line in ShapeBase::onEndSequence is dependant on engine
REMOVE OLD
advanceThreads()
6) Now we need to add network support for our new thread variables.
Within ShapeBase::packUpdate{
stream->writeFlag(st.atEnd);// after this line in last if statement
}
AND
within ShapeBase::unpackUpdate{
st.atEnd = stream->readFlag(); // after this line
}
7) Finally lets finish our definitions and make some console functions.
Note we have rewritten the console functions playThread/ stopThread/ setThreadDir/pauseThread to check to see if the thread is locked so be sure to replace the existing ones.
ConsoleMethod( ShapeBase, stopAudio, bool, 3, 3, "(int slot)")// after this function
BESURE TO REMOVE OLD
Console Methods for
playThread()
stopThread()
pauseThread()
setThreadDir()
Once that is compiled in you will have these methods at you disposal.
Example:
Presume we have a stop light with an ifl for the various colors of the light. At the beginning of the animation the light is green, halfway thru the animation the light is yellow and at the end the light is red.
8) Now let's see an example of some of these methods.
You must install the files from Gem #1: Placeable Shapes before you can see the Grandfather Clock example explained here.
Unzip the pg02_thread.zip file provided with this resource. Copy the clock.cs file to your ~/server/scripts folder. There will already be a file of that name there from the previous Gem. Allow that file to be over-written - we wan't the new file.
Let's see what's changed in the new file:
First we show an exmaple of using data-inheritence to define a base class for analog clocks called AnalogClockBase:
Next our GrandfatherClock datablock now inherits the 'className' and the 'preload' fields from this base class:
Now in the on add method, in addition to running the 'pendulum' thread we also run the minutehand and hourhand threads. Note that we pause the threads. The thread frames are manipulated in our new method defined farther down called setAnalogClockTime()
Next we can do a better job of reversing pendulum direction after each swing, now that we have the %obj.getThreadDir() method...
Here is the new method we added to manipulate the 'hourhand' and 'minutehand' threads based on the desired input time...
9) Placing shape in Mission Editor
Ok now go place a grandfather clock into a mission using the same steps given in Gem #1: Placeable Shapes. You will notice that the time should now be set to 10:30 instead of 12:00.
That's it for now. Future gems might talk more about other features that were added in this resource, such as how to use script animation thread slots from the C++ side and more. The next gem will add the concept of ease to the Torque engine and show an example of use building on top of our Grandfather Clock example. We will use ease to make the pendulum swing more naturally.

Plastic Gem #2: Animation Threads.
Difficulty: Moderate
For a list of gems see the Gem A Day page.
Hello again from Plastic Games. It is time for gem number two in our Gem A Day effort. This gem adds lots of useful features to Torque's powerful script based Animation Thread system. You must install the files from Gem #1: Placeable Shapes before you can see the Grandfather Clock example explained at the end of this resource.
The Grandfather Clock

This resource was designed to give more functionality to the existing thread system. By the end of this resource you will be able to pause a thread, set a thread's position 0(start) -1(end), play a segment of a thread, replay a thread directly, as well as adjust a thread's speed.
All the code presented here was developed by Paul Dana, lead programmer at Plastic Games.
This code can be implemented in TGEA 1.7 and TGE 1.5.2. TGEA locations will be in parentheses
1)Provide new variables in the shapebase thread struct.
In game(T3D)/ShapeBase.h, in the Shapebase class
Struct Thread{
bool forward; // add new code AFTER this line
// > pg threads
F32 startPos; ///< if not zero we have atypical start position...
F32 stopPos; ///< if not one we have atypical stop position...
F32 forcePos; ///< if non negative true we are forcing the position to a given value...
F32 speed;
//EaseF ease;
// this is SERVER only...never sent to the client because it involves only SERVER logic...
bool locked; ///< if true then C++ uses this slot...it is not available to the script...
// < pg threads}
2) Declare new interfaces for thread manipulation.
void stopThreadSound(Thread& thread); // add new code AFTER this line
// > pg threads
/// Advance all animation threads attached to this shapebase
/// @param dt Change in time from last call to this function
virtual void advanceThreads(F32 dt);
/// @}
virtual void onEndSequence(U32 slot);
bool playThread(U32 slot, const char *name);
bool setClientThreadPos(U32 slot, F32 speed);
bool playClientThread(U32 slot, const char *name);
bool playClientThread(U32 slot, S32 seq);
bool ShapeBase::isThreadPlaying(U32 slot)
{
return getThreadState(slot) == Thread::Play;
}
bool ShapeBase::isThreadPaused(U32 slot)
{
return getThreadState(slot) == Thread::Pause;
}
bool ShapeBase::isThreadStopped(U32 slot)
{
return getThreadState(slot) == Thread::Stop;
}
S32 ShapeBase::getThreadState(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return st.state;
}
return -1;
}
bool ShapeBase::isThreadAtEnd(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return st.atEnd;
}
return false;
}
bool ShapeBase::getThreadDir(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return st.forward;
}
return 0;
}
F32 ShapeBase::getThreadPos(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return mShapeInstance->getPos(st.thread);
}
return 0;
}
F32 ShapeBase::getThreadSpeed(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return st.speed;
}
return 0;
}
F32 ShapeBase::getThreadStartPos(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return st.startPos;
}
return 0.0f;
}
F32 ShapeBase::getThreadStopPos(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return st.stopPos;
}
return 1.0f;
}
bool ShapeBase::getThreadLocked(U32 slot)
{
Thread& st = mScriptThread[slot];
return st.locked;
}
bool setThreadSpeed(U32 slot, F32 speed);
bool setThreadPos(U32 slot, F32 pos);
// bool setThreadEase(U32 slot, EaseF ease);
bool replayThread(U32 slot, F32 forcePos);
bool replayThread(U32 slot);
bool setThreadStartPos(U32 slot, F32 startPos);
bool setThreadStopPos(U32 slot, F32 stopPos);
// this is SERVER side only...no net logic needed...
bool isLockedError(const char *name, U32 slot);
bool setThreadLocked(U32 slot, bool locked)
{
if (slot <= 0 && slot < MaxScriptThreads)
{
Thread& st = mScriptThread[slot];
st.locked = locked;
return true;
}
return false;
}
F32 getThreadPos(U32 slot);
F32 getThreadSpeed(U32 slot);
F32 getThreadStartPos(U32 slot);
F32 getThreadStopPos(U32 slot);
bool getThreadLocked(U32 slot);
// < pg threadsNOTE BESURE TO COMMENT OUT
void advanceThreads(F32 dt);
3) Initialize our new thread variables.
shapebase.cc
in ShapeBase::ShapeBase()
mScriptThread[i].forward = true;//add code after this line
// > pg threads
mScriptThread[i].startPos = 0.0f;
mScriptThread[i].stopPos = 1.0f;
mScriptThread[i].forcePos = -1.0f;
mScriptThread[i].speed = 1;
//mScriptThread[i].ease.set(0,0); //
// server side only stuff...
mScriptThread[i].locked = false;
// < pg threads4) Now we need to rewrite some existing functions bool ShapeBase::setThreadSequence(U32 slot,S32 seq,bool reset) // add code AFTER this function
// > pg threads
//note, this is mostly implimented in jugg
void ShapeBase::updateThread(Thread& st)
{
switch (st.state) {
case Thread::Stop:
mShapeInstance->setTimeScale(st.thread,1);
// NOTE: this used to ONLY set to 0.0 (ie startpos) so if you stoped
// a thread it would go to the BEGINING whether the direction was forward or not
// for Plastic Gems we have CHANGED this default behavior of torque ....we are not
// aware of any script codes that dependend upon the old behavior that are now
// broken, but there sure could be! - phdana
mShapeInstance->setPos(st.thread,st.forward?st.startPos:st.stopPos);
// Drop through to pause state
case Thread::Pause:
mShapeInstance->setTimeScale(st.thread,0);
stopThreadSound(st);
break;
case Thread::Play:
if (st.atEnd) {
mShapeInstance->setTimeScale(st.thread,1);
mShapeInstance->setPos(st.thread,st.forward? st.stopPos: st.startPos);
mShapeInstance->setTimeScale(st.thread,0);
stopThreadSound(st);
}
else {
mShapeInstance->setTimeScale(st.thread,st.forward? st.speed: -st.speed);
//mShapeInstance->setEase(st.thread,st.ease);
if (st.forcePos >= 0)
mShapeInstance->setPos(st.thread,st.forcePos);
if (!st.sound)
startSequenceSound(st);
}
break;
}
}
//implimented in jugg
bool ShapeBase::stopThread(U32 slot)
{
Thread& st = mScriptThread[slot];
// NOTE:
// THIS FUNCTION IS A BIT STRANGE...
// when a thread stops playing the "pos" is put back at the BEGINING, even though the shape is still left
// on the last frame (it does not animate after setting "pos" back to begining). HOWEVER...if you call this
// fuction while a thread is actaully PLAYING...it does put it back at begining, For example, assuming a forward thread:
//
// ACTION RESULT
// a) call while thread playing -> Set Pos to 0.0
// b) call thread that finished on own -> DO NOTHING...Anim remains at last frame
//
if (st.sequence != -1 && st.state != Thread::Stop)
{
setMaskBits(ThreadMaskN << slot);
st.state = Thread::Stop;
updateThread(st);
return true;
}
return false;
}
// < pg threadBESURE TO REMOVE OLD
stopThread()
updateThread()
5) Define the interface functions
void ShapeBase::startSequenceSound(Thread& thread) // after this function
// > pg thread
bool ShapeBase::playThread(U32 slot, const char *name)
{
Thread& st = mScriptThread[slot];
if (getShape()) {
st.forcePos = -1;
S32 seq = getShape()->findSequence(name);
if (seq != -1 && setThreadSequence(slot,seq))
return true;
}
return false;
}
bool ShapeBase::setClientThreadPos(U32 slot, F32 pos)
{
pos = mClampF(pos, 0.f, 1.f);
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
if (mShapeInstance->getPos(st.thread) != pos)
mShapeInstance->setPos(st.thread,pos);
return true;
}
return false;
}
bool ShapeBase::playClientThread(U32 slot, S32 seq)
{
if (seq < 0 || seq >= MaxSequenceIndex)
return false;
Thread& st = mScriptThread[slot];
st.forcePos = -1;
if (st.sequence != seq)
return setThreadSequence(slot,seq,false);
return true;
}
bool ShapeBase::playClientThread(U32 slot, const char *name)
{
S32 seq = getShape()->findSequence(name);
if (seq < 0)
return false;
return playClientThread(slot,seq);
}
bool ShapeBase::setThreadSpeed(U32 slot, F32 speed)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
if (st.speed != speed)
{
setMaskBits(ThreadMaskN << slot);
st.speed = speed;
updateThread(st);
}
return true;
}
return false;
}
bool ShapeBase::setThreadPos(U32 slot, F32 pos)
{
pos = mClampF(pos, 0.f, 1.f);
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
if (mShapeInstance->getPos(st.thread) != pos)
{
setMaskBits(ThreadMaskN << slot);
mShapeInstance->setPos(st.thread,pos);
updateThread(st);
}
return true;
}
return false;
}
/* bool ShapeBase::setThreadEase(U32 slot, EaseF ease)
{
Thread& st = mScriptThread[slot];
bool diff = ease.type != st.ease.type || ease.dir != st.ease.dir ||
ease.param[0] != st.ease.param[0] || ease.param[1] != st.ease.param[1];
if (st.sequence != -1) {
if (diff)
{
setMaskBits(ThreadMaskN << slot);
st.ease = ease;
updateThread(st);
}
return true;
}
return false;
}
*/
bool ShapeBase::replayThread(U32 slot)
{
return replayThread(slot,-1.0f);
}
bool ShapeBase::replayThread(U32 slot, F32 forcePos)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
setMaskBits(ThreadMaskN << slot);
st.state = Thread::Play;
st.atEnd = false;
if (forcePos < 0)
forcePos = st.forward ? st.startPos : st.stopPos;
st.forcePos = forcePos;
mShapeInstance->setSequence(st.thread,st.sequence,st.forcePos);
updateThread(st);
return true;
}
return false;
}
bool ShapeBase::setThreadStartPos(U32 slot, F32 pos)
{
pos = mClampF(pos, 0.f, 1.f);
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
if (st.startPos != pos)
{
setMaskBits(ThreadMaskN << slot);
st.startPos = pos;
updateThread(st);
}
return true;
}
return false;
}
bool ShapeBase::setThreadStopPos(U32 slot, F32 pos)
{
pos = mClampF(pos, 0.f, 1.f);
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
if (st.stopPos != pos)
{
setMaskBits(ThreadMaskN << slot);
st.stopPos = pos;
updateThread(st);
}
return true;
}
return false;
}
void ShapeBase::advanceThreads(F32 dt)
{
for (U32 i = 0; i < MaxScriptThreads; i++) {
Thread& st = mScriptThread[i];
if (st.thread) {
if (!mShapeInstance->getShape()->sequences[st.sequence].isCyclic() && !st.atEnd &&
(st.forward? mShapeInstance->getPos(st.thread) >= st.stopPos:
mShapeInstance->getPos(st.thread) <= st.startPos)) {
st.atEnd = true;
updateThread(st);
if (!isGhost()) {
onEndSequence(i);
}
}
mShapeInstance->advanceTime(dt,st.thread);
}
}
}
void ShapeBase::onEndSequence(U32 i)
{
char slot[16];
dSprintf(slot,sizeof(slot),"%d",i);
Con::executef(mDataBlock,3,"onEndSequence",scriptThis(),slot);//TGE
Con::executef(mDataBlock,"onEndSequence",scriptThis(),slot);//TGEA
}
// < pg threadNOTE the last line in ShapeBase::onEndSequence is dependant on engine
REMOVE OLD
advanceThreads()
6) Now we need to add network support for our new thread variables.
Within ShapeBase::packUpdate{
stream->writeFlag(st.atEnd);// after this line in last if statement
// > pg thread
if (stream->writeFlag(st.forcePos >= 0))
stream->write(st.forcePos);
if (stream->writeFlag(st.startPos != 0.0f))
stream->write(st.startPos);
if (stream->writeFlag(st.stopPos != 1.0f))
stream->write(st.stopPos);
/* if (stream->writeFlag(st.ease.type != 0))
{
stream->write(st.ease.type);
stream->write(st.ease.dir);
stream->write(st.ease.param[0]);
stream->write(st.ease.param[1]);
}
*/
stream->write(st.speed);
if (st.state == Thread::Pause)
stream->write(mShapeInstance->getPos(st.thread));
// < pg thread}
AND
within ShapeBase::unpackUpdate{
st.atEnd = stream->readFlag(); // after this line
// > pg thread
if (stream->readFlag())
stream->read(&st.forcePos);
else
st.forcePos = -1.0f;
if (stream->readFlag())
stream->read(&st.startPos);
else
st.startPos = 0.0f;
if (stream->readFlag())
stream->read(&st.stopPos);
else
st.stopPos = 1.0f;
/* if (stream->readFlag())
{
stream->read(&st.ease.type);
stream->read(&st.ease.dir);
stream->read(&st.ease.param[0]);
stream->read(&st.ease.param[1]);
}
else
{
st.ease.type = 0;
// the other types should be set already but in any case are not USEd when type == 0
//st.ease.dir = 0;
//st.ease.param[0] = -1;
//st.ease.param[1] = -1;
}
*/
stream->read(&st.speed);
F32 pos = -1.0;
if (st.state == Thread::Pause)
stream->read(&pos);
if (st.sequence != seq)
{
setThreadSequence(i,seq,false);
}
else if (st.forcePos >= 0.0f)
{
st.sequence = -1;
st.atEnd = false;
setThreadSequence(i,seq,false);
}
else
{
updateThread(st);
}
if (pos != -1.0)
{
// at this point we should setup the interpolation
// value so the client can do client side interpolation
// TO this value rather than setting it directly...
mShapeInstance->setPos(st.thread,pos);
}
// < pg thread}
7) Finally lets finish our definitions and make some console functions.
Note we have rewritten the console functions playThread/ stopThread/ setThreadDir/pauseThread to check to see if the thread is locked so be sure to replace the existing ones.
ConsoleMethod( ShapeBase, stopAudio, bool, 3, 3, "(int slot)")// after this function
// > pg thread
bool ShapeBase::isLockedError(const char *name, U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.locked)
{
char obj[512];
if (this->getName() != NULL)
dStrncpy(obj,this->getName(),511);
else
dSprintf(obj,511,"%d",this->getId());
Con::errorf(ConsoleLogEntry::Script,"Cannot call %s.%s() on thread slot %d, it is LOCKED for use by the C++ on this class...",obj,name,slot);
return true;
}
return false;
}
ConsoleMethod( ShapeBase, playThread, bool, 3, 4, "(int slot, string sequenceName)")
{
U32 slot = dAtoi(argv[2]);
if (object->isLockedError("playThread",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (argc == 4) {
if (object->playThread(slot,argv[3]))
return true;
}
else
if (object->playThread(slot))
return true;
}
return false;
}
ConsoleMethod( ShapeBase, setThreadDir, bool, 4, 4, "(int slot, bool isForward)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("setThreadDir",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (object->setThreadDir(slot,dAtob(argv[3])))
return true;
}
return false;
}
ConsoleMethod( ShapeBase, stopThread, bool, 3, 3, "(int slot)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("stopThread",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (object->stopThread(slot))
return true;
}
return false;
}
ConsoleMethod( ShapeBase, pauseThread, bool, 3, 3, "(int slot)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("pauseThread",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (object->pauseThread(slot))
return true;
}
return false;
}
ConsoleMethod( ShapeBase, getThreadSpeed, F32, 3, 3, "(int slot)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("getThreadSpeed",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
return object->getThreadSpeed(slot);
}
return 0.0f;
}
ConsoleMethod( ShapeBase, getThreadPos, F32, 3, 3, "(int slot)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("getThreadPos",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
return object->getThreadPos(slot);
}
return 0.0f;
}
ConsoleMethod( ShapeBase, getThreadDir, bool, 3, 3, "(int slot)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("getThreadDir",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
return object->getThreadDir(slot);
}
return 0;
}
ConsoleMethod( ShapeBase, getThreadStartPos, F32, 3, 3, "(int slot)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("getThreadStartPos",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
return object->getThreadStartPos(slot);
}
return 0.0f;
}
ConsoleMethod( ShapeBase, getStopThreadPos, F32, 3, 3, "(int slot)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("getStopThreadPos",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
return object->getThreadStopPos(slot);
}
return 1.0f;
}
ConsoleMethod( ShapeBase, setThreadPos, bool, 4, 4, "(int slot, float Position)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("setThreadPos",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (object->setThreadPos(slot,dAtof(argv[3])))
return true;
}
return false;
}
ConsoleMethod( ShapeBase, setThreadSpeed, bool, 4, 4, "(int slot, int Speed)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("setThreadSpeed",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (object->setThreadSpeed(slot,dAtof(argv[3])))
return true;
}
return false;
}
/* ConsoleMethod( ShapeBase, setEase, bool, 4, 4, "(int slot, easef ease)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("setEase",slot))
return false;
EaseF ease;
ease.set(argv[3]);
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (object->setThreadEase(slot,ease))
return true;
}
return false;
}
*/
ConsoleMethod( ShapeBase, replayThread, bool, 3, 4, "(int slot, [float forcePos] )")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("replayThread",slot))
return false;
F32 forcePos = -1.0f;
if (argc > 3)
forcePos = dAtof(argv[3]);
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (object->replayThread(slot,forcePos))
return true;
}
return false;
}
ConsoleMethod( ShapeBase, setThreadStartPos, bool, 4, 4, "(int slot, float startPos)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("setThreadStartPos",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (object->setThreadStartPos(slot,dAtof(argv[3])))
return true;
}
return false;
}
ConsoleMethod( ShapeBase, setThreadStopPos, bool, 4, 4, "(int slot, float stopPos)")
{
int slot = dAtoi(argv[2]);
if (object->isLockedError("setThreadStopPos",slot))
return false;
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
if (object->setThreadStopPos(slot,dAtof(argv[3])))
return true;
}
return false;
}
// < pg threadBESURE TO REMOVE OLD
Console Methods for
playThread()
stopThread()
pauseThread()
setThreadDir()
Once that is compiled in you will have these methods at you disposal.
%shapebase.getThreadSpeed(int slot); // get current speed of thread %shapebase.getThreadPos(int slot);//get current location of thread 0-1 %shapebase.getThreadDir(int slot);//determines if thread is playing forward or backward as set by parameter in setThreadDir(slot, isFoward); %shapebase.setThreadPos(int slot, float Position);//take a value 0-1 to set thread postion, must call playThread() AND pauseThread() first. %shapebase.setThreadSpeed(int slot, int Speed);// provides a value to scale the animation speed %shapebase.setEase(int slot, easef ease);// allows ease on existing threads %shapebase.replayThread(int slot, [float forcePos] );// replays thread automatically %shapebase.getThreadStartPos(int slot); %shapebase.setThreadStartPos(int slot, float startPos);//sets start postition to play a segment of a thread %shapebase.getStopThreadPos(int slot); %shapebase.setThreadStopPos(int slot, float stopPos); );//sets stop postition to play a segment of a thread
Example:
Presume we have a stop light with an ifl for the various colors of the light. At the beginning of the animation the light is green, halfway thru the animation the light is yellow and at the end the light is red.
function stoplight::onAdd(%this, %obj){
%obj.playThread(0, "Color_change");//assign sequence to slot
%obj.pauseThread(0); //pause the thread so we can manipulate the position
%obj.setThreadPos(0,1); //set stoplight to red
}
function stoplight::turnGreen(%this, %obj){
%obj.setThreadPos(0,0); //set stoplight to green
}
function stoplight:: turnYellow(%this, %obj){
%obj.setThreadPos(0,0.5); //set stoplight to yellow
}
function stoplight:: turnRed(%this, %obj){
%obj.setThreadPos(0,1); //set stoplight to red
}8) Now let's see an example of some of these methods.
You must install the files from Gem #1: Placeable Shapes before you can see the Grandfather Clock example explained here.
Unzip the pg02_thread.zip file provided with this resource. Copy the clock.cs file to your ~/server/scripts folder. There will already be a file of that name there from the previous Gem. Allow that file to be over-written - we wan't the new file.
Let's see what's changed in the new file:
First we show an exmaple of using data-inheritence to define a base class for analog clocks called AnalogClockBase:
// this shows how we can use Torque script data-inheritence ...
datablock StaticShapeData(AnalogClockBase)
{
// functions for this datablock will be in the AnalogClock namespace...
className = "AnalogClock";
// load this asset at mission load time...
preload = true;
};Next our GrandfatherClock datablock now inherits the 'className' and the 'preload' fields from this base class:
// since we derive from the analog clock base our className is AnalogClock
// and therefore this datablock's methods are in the AnalogClock namespace...
datablock StaticShapeData(GrandfatherClock : AnalogClockBase)
{
// shape file containing the clock...
shapeFile = "~/data/shapes/clock/clock.dts";
// what category in mission editor when placing this magnet...
// we can't put this on the base class because then that abstract
// class would be placeable (which we don't want)
category = "Plastic";
};Now in the on add method, in addition to running the 'pendulum' thread we also run the minutehand and hourhand threads. Note that we pause the threads. The thread frames are manipulated in our new method defined farther down called setAnalogClockTime()
// ANALOG clock namespace...
// this is called when an object with a clock datablock is created...
function AnalogClock::onAdd(%this,%obj)
{
// model is constructed 2x what we want...we can scale down here
// but we ONLY scale down if the scale is not already set...
if (%obj.getScale() $= "1 1 1")
%obj.setScale("0.5 0.5 0.5");
// run the 'pendulum' blend animation on thread 0...
%obj.playThread(0, "pendulum");
// run the 'hourhand' blend anim on thread 1 and pause it...
%obj.playThread(1, "hourhand");
%obj.pauseThread(1);
// run the 'minutehand' blend anim on thread 2 and pause it...
%obj.playThread(2, "minutehand");
%obj.pauseThread(2);
// set the default hour to 10:30
%this.setAnalogClockTime(%obj, "10", "30");
}Next we can do a better job of reversing pendulum direction after each swing, now that we have the %obj.getThreadDir() method...
function AnalogClock::onEndSequence(%this, %obj, %slot)
{
// if this is the pendulum swing on slot 0...reverse
// the thread direction so it will keep going in the other direction
if (%slot == 0)
{
%dir = !%obj.getThreadDir(0);
%obj.setThreadDir(0, %dir);
}
}Here is the new method we added to manipulate the 'hourhand' and 'minutehand' threads based on the desired input time...
// this function manipulates the threads to show the given hour and minutes...
function AnalogClock::setAnalogClockTime(%this, %obj, %hour, %min)
{
// translate the range "0..12 to the range 0..1
%obj.setThreadPos(1, %hour / 12.0);
// translate the range "0..60 to the range 0..1
%obj.setThreadPos(2, %min / 60.0);
}9) Placing shape in Mission Editor
Ok now go place a grandfather clock into a mission using the same steps given in Gem #1: Placeable Shapes. You will notice that the time should now be set to 10:30 instead of 12:00.
That's it for now. Future gems might talk more about other features that were added in this resource, such as how to use script animation thread slots from the C++ side and more. The next gem will add the concept of ease to the Torque engine and show an example of use building on top of our Grandfather Clock example. We will use ease to make the pendulum swing more naturally.
#22
If anyone else out there has AFX and can see what has gone wrong here please pipe up!
08/05/2008 (8:31 pm)
Netwyrm and John - I am not sure what is going on. I wonder if AFX redefines that type? I do not own AFX (yet - I have been meaning to pick it up for a long while now) so I am not able to figure out your problem exactly yet.If anyone else out there has AFX and can see what has gone wrong here please pipe up!
#23
08/05/2008 (8:44 pm)
I have TGE/AFX...it sounds like it's probably a TGEA thing.
#24
(dont ask why that works, no idea, i only did it beacuse i dont like those functions being in a header)
Compiled but ive yet to test it.
08/13/2008 (2:41 am)
Im using TGEA/AFX 1.7.1, to fix all the issues ive been getting I simply moved all the functions in step two, to the .cpp and then defined then in the .h.(dont ask why that works, no idea, i only did it beacuse i dont like those functions being in a header)
Compiled but ive yet to test it.
#25
08/17/2008 (2:35 am)
I also had a problem compiling with TGE 1.5.2, to fix I completely separated the prototypes from functions so that all the prototypes are in the .h file and all the functions are in the .cc file. I can post it if someone is interested.
#26
I only moved the F32 ShapeBase::getThreadPos function to the shapebase.cpp and created a declaration in the shapebase.h file. This compliled ok.
I love these tutorials good work..
08/18/2008 (9:17 am)
I did the same thing with the 1.7.1 tgea and it works. There is an example of this error on the microsoft web page. http://msdn.microsoft.com/en-us/library/6c2dk0ah(VS.80).aspx I only moved the F32 ShapeBase::getThreadPos function to the shapebase.cpp and created a declaration in the shapebase.h file. This compliled ok.
I love these tutorials good work..
#27
No disrespect intended, I'm just a little intimidated.
I think I'll come back to this one. It looks like a well written tutorial, very straightforward, just a lot of work for a non-programmer.
The end result looks very encouraging, though!
Tony
I3D
09/30/2008 (11:21 am)
Wow! Only the 2nd Gem and already jumping into Source Code changes...No disrespect intended, I'm just a little intimidated.
I think I'll come back to this one. It looks like a well written tutorial, very straightforward, just a lot of work for a non-programmer.
The end result looks very encouraging, though!
Tony
I3D
#28
Is there a reason why I can not stop, pause, set the position, nor change the direction of thread 0?
Get pos, speed, change speed work fine on it, so my guess it is something outside of this gem causing the problem.
10/16/2008 (3:11 am)
Compiled fine for me for TGE 1.5.2 after commenting out the duplicated declarations and commenting out the TGEA specific line in one of the console functions.Is there a reason why I can not stop, pause, set the position, nor change the direction of thread 0?
Get pos, speed, change speed work fine on it, so my guess it is something outside of this gem causing the problem.
#29
10/24/2008 (6:45 pm)
to set a position you must playThead() then pauseThread() then setThreadPos()
#30
In step 2, replace the getThreadPos function with its declaration:
Also, comment out (or delete) those last 5 lines of step 2:
Paste the "getThreadPos" function you cut out of step 2 at the top of the code of step 7 (after "// > pg thread"):
Seems to work for me here. Excellent gems, Plastic people! The best way to dig into TGEA, at least for me.
Cheers!
11/01/2008 (12:23 pm)
Quick changes to get it compiling with Visual C++ (2008) to get rid of the C2027 errors:In step 2, replace the getThreadPos function with its declaration:
F32 getThreadPos(U32 slot);
Also, comment out (or delete) those last 5 lines of step 2:
// F32 getThreadPos(U32 slot);
// F32 getThreadSpeed(U32 slot);
// F32 getThreadStartPos(U32 slot);
// F32 getThreadStopPos(U32 slot);
// bool getThreadLocked(U32 slot);
Paste the "getThreadPos" function you cut out of step 2 at the top of the code of step 7 (after "// > pg thread"):
F32 ShapeBase::getThreadPos(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return mShapeInstance->getPos(st.thread);
}
return 0;
}
Seems to work for me here. Excellent gems, Plastic people! The best way to dig into TGEA, at least for me.
Cheers!
#31
1> ../engine\game/shapeBase.h(542) : see declaration of 'ShapeBase'
1>..\engine\game\shapeBase.cc(3844) : error C2039: 'getThreadPos' : is not a member of 'ShapeBase'
1> ../engine\game/shapeBase.h(542) : see declaration of 'ShapeBase'
1>..\engine\game\shapeBase.cc(3866) : error C2039: 'getThreadStartPos' : is not a member of 'ShapeBase'
1> ../engine\game/shapeBase.h(542) : see declaration of 'ShapeBase'
1>..\engine\game\shapeBase.cc(3877) : error C2039: 'getThreadStopPos' : is not a member of 'ShapeBase'
1> ../engine\game/shapeBase.h(542) : see declaration of 'ShapeBase'
11/09/2008 (11:22 am)
.\engine\game\shapeBase.cc(3833) : error C2039: 'getThreadSpeed' : is not a member of 'ShapeBase'1> ../engine\game/shapeBase.h(542) : see declaration of 'ShapeBase'
1>..\engine\game\shapeBase.cc(3844) : error C2039: 'getThreadPos' : is not a member of 'ShapeBase'
1> ../engine\game/shapeBase.h(542) : see declaration of 'ShapeBase'
1>..\engine\game\shapeBase.cc(3866) : error C2039: 'getThreadStartPos' : is not a member of 'ShapeBase'
1> ../engine\game/shapeBase.h(542) : see declaration of 'ShapeBase'
1>..\engine\game\shapeBase.cc(3877) : error C2039: 'getThreadStopPos' : is not a member of 'ShapeBase'
1> ../engine\game/shapeBase.h(542) : see declaration of 'ShapeBase'
#32
01/07/2009 (7:31 am)
XD - looks like you ported the .cc parts of the code over but not the declarations in the .h file
#33
I ran into some errors about same functions already declared
to resolve the issue, move these function
To the end of shapebase.cc
It should compile fine.
This makes resource is 1.8.1 compatible
03/04/2009 (6:13 am)
FIX 3/4/09I ran into some errors about same functions already declared
to resolve the issue, move these function
//> pg threads
bool ShapeBase::getThreadLocked(U32 slot)
{
Thread& st = mScriptThread[slot];
return st.locked;
}
F32 ShapeBase::getThreadPos(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return mShapeInstance->getPos(st.thread);
}
return 0;
}
F32 ShapeBase::getThreadSpeed(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return st.speed;
}
return 0;
}
F32 ShapeBase::getThreadStartPos(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return st.startPos;
}
return 0.0f;
}
F32 ShapeBase::getThreadStopPos(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return st.stopPos;
}
return 1.0f;
}
//< pg threadsTo the end of shapebase.cc
It should compile fine.
This makes resource is 1.8.1 compatible
#34
04/16/2009 (5:32 pm)
Epic, thanks once again!
#35
05/29/2009 (8:43 am)
@Anthony: Thanks again - went straight into Torque3D without a problem.
#36
Error 2 error C2227: left of '->getPos' must point to class/struct/union/generic type f:torquetgea_1_8_1enginesourcet3dshapebase.h 1775 Stronghold
Error 1 error C2027: use of undefined type 'TSShapeInstance' f:torquetgea_1_8_1enginesourcet3dshapebase.h 1775 Stronghold
errors?
Opps acidently put them at the end of ShapeBased.h instead of ShapeBased.cpp silly me. All better.
08/11/2009 (2:19 pm)
Konrad Kiss how did you fix theError 2 error C2227: left of '->getPos' must point to class/struct/union/generic type f:torquetgea_1_8_1enginesourcet3dshapebase.h 1775 Stronghold
Error 1 error C2027: use of undefined type 'TSShapeInstance' f:torquetgea_1_8_1enginesourcet3dshapebase.h 1775 Stronghold
errors?
Opps acidently put them at the end of ShapeBased.h instead of ShapeBased.cpp silly me. All better.
#37
08/14/2009 (7:25 am)
T3D beta 5 does not have a forward variable.
Torque Owner John Doppler Schiff
return mShapeInstance->getPos(st.thread);
with
return 0;
temporarily circumvents the compile hiccup.
Is it a forward declaration issue? My meager C++ skills are nowhere near tackling this problem, I fear.