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.
#2
06/10/2008 (5:51 pm)
Excellent Paul, keep em coming!
#3
06/10/2008 (9:05 pm)
Fantastic resource.
#4
06/11/2008 (7:19 pm)
I've implemented a small fraction of these functions in the past, but this does everything I've needed. Awesome, can't wait to get this installed.
#5
06/13/2008 (6:01 am)
Good work guys thanks!
#6
After commenting out these final lines I am down to 112 errors.
This code in game(T3D)/ShapeBase.h, in the Shapebase class, should be "compile as you go" compatible - right?
06/13/2008 (8:06 am)
Step 2 is tossing tons of errors (322), most of which are functions already defined.After commenting out these final lines I am down to 112 errors.
//F32 getThreadPos(U32 slot); //F32 getThreadSpeed(U32 slot); //F32 getThreadStartPos(U32 slot); //F32 getThreadStopPos(U32 slot); //bool getThreadLocked(U32 slot);
This code in game(T3D)/ShapeBase.h, in the Shapebase class, should be "compile as you go" compatible - right?
#7
06/16/2008 (6:52 am)
Be sure to read carefully, you need to repalce the old versions of certain methods for our new improved methods
#8
06/17/2008 (10:02 am)
Kyle Cook - are you still getting errors after checking the instructions again? We have tested through these resources ourselves at least once and we have an intern who is testing through them at a slower pace, but this one worked for him. If you think you have found an actual error in the instructions - something we left out - let us know and we will update the gem. Good luck!
#9
06/17/2008 (10:05 am)
I have not looked at it in a few days but one problem might be me using TGEA and I think I remember reading these gems are for TGE.
#10
ust be sure to follow the instructions carefully as they will indicate what to do for TGE or TGEA on a section by section basis, when that section has instructions that differ between the two engines.
06/17/2008 (5:09 pm)
Kyle - The great thing is all the Plastic Gems are for both TGE and TGEA. How is that for awesome? We hit the no-messing-around button on these gems!ust be sure to follow the instructions carefully as they will indicate what to do for TGE or TGEA on a section by section basis, when that section has instructions that differ between the two engines.
#11
Could you give an approximate line number? Doing a search with vs2005 exp. and a manual search thru the code, I could not find this.
06/28/2008 (8:56 am)
In game(T3D)/ShapeBase.h, in the Shapebase class
Struct Thread{
boolforward; // add new code AFTER this lineCould you give an approximate line number? Doing a search with vs2005 exp. and a manual search thru the code, I could not find this.
#12
The same problem as Mike:
Could you give an approximate line number? Doing a search with vs2005 exp. and a manual search thru the code, I could not find this.
07/09/2008 (2:28 pm)
"In game(T3D)/ShapeBase.h, in the Shapebase class Struct Thread{ boolforward; // add new code AFTER this line....."The same problem as Mike:
Could you give an approximate line number? Doing a search with vs2005 exp. and a manual search thru the code, I could not find this.
#13
are defined twice in the code in the steps above, which causes Xcode on the Mac to hiccup. The same function definitions are repeated in shapeBase.h (in Step #2) and in shapeBase.cc (in Step #4). I just deleted the definitions from shapeBase.h (the declarations are still there at the end of Step #2) and it compiled fine.
07/09/2008 (9:58 pm)
Related to Kyle's problem above, I had one issue compiling this. The following functions:getThreadPos(U32 slot); getThreadSpeed(U32 slot); getThreadStartPos(U32 slot); getThreadStopPos(U32 slot); getThreadLocked(U32 slot);
are defined twice in the code in the steps above, which causes Xcode on the Mac to hiccup. The same function definitions are repeated in shapeBase.h (in Step #2) and in shapeBase.cc (in Step #4). I just deleted the definitions from shapeBase.h (the declarations are still there at the end of Step #2) and it compiled fine.
#14
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;
}
In both shapebase.h and shapebase.cc gives you some errors like this.
1>..\engine\game\shapeBase.cc(2108) : error C2084: function 'F32 ShapeBase::getThreadPos(U32)' already has a body
1> ../engine\game/shapeBase.h(1193) : see previous definition of 'getThreadPos'
1>..\engine\game\shapeBase.cc(2117) : error C2084: function 'F32 ShapeBase::getThreadSpeed(U32)' already has a body
1> ../engine\game/shapeBase.h(1202) : see previous definition of 'getThreadSpeed'
1>..\engine\game\shapeBase.cc(2126) : error C2084: function 'F32 ShapeBase::getThreadStartPos(U32)' already has a body
1> ../engine\game/shapeBase.h(1211) : see previous definition of 'getThreadStartPos'
1>..\engine\game\shapeBase.cc(2135) : error C2084: function 'F32 ShapeBase::getThreadStopPos(U32)' already has a body
1> ../engine\game/shapeBase.h(1220) : see previous definition of 'getThreadStopPos'
1>..\engine\game\shapeBase.cc(2144) : error C2084: function 'bool ShapeBase::getThreadLocked(U32)' already has a body
1> ../engine\game/shapeBase.h(1229) : see previous definition of 'getThreadLocked'
1>..\engine\game\shapeBase.cc(3867) : error C2264: 'ShapeBase::getThreadSpeed' : error in function definition or declaration; function not called
1>..\engine\game\shapeBase.cc(3878) : error C2264: 'ShapeBase::getThreadPos' : error in function definition or declaration; function not called
1>..\engine\game\shapeBase.cc(3900) : error C2264: 'ShapeBase::getThreadStartPos' : error in function definition or declaration; function not called
1>..\engine\game\shapeBase.cc(3911) : error C2264: 'ShapeBase::getThreadStopPos' : error in function definition or declaration; function not called
So I removed them from shapebase.cc and that seemed to fix the errors, haven't tested it yet though.
EDIT: Seems to work just fine.
07/25/2008 (2:35 pm)
I was having the same problem as Kyle and followed Rubes advice, which seemed to fix some of my errors, but I was still getting a lot of errors. Turns out that definingF32 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;
}
In both shapebase.h and shapebase.cc gives you some errors like this.
1>..\engine\game\shapeBase.cc(2108) : error C2084: function 'F32 ShapeBase::getThreadPos(U32)' already has a body
1> ../engine\game/shapeBase.h(1193) : see previous definition of 'getThreadPos'
1>..\engine\game\shapeBase.cc(2117) : error C2084: function 'F32 ShapeBase::getThreadSpeed(U32)' already has a body
1> ../engine\game/shapeBase.h(1202) : see previous definition of 'getThreadSpeed'
1>..\engine\game\shapeBase.cc(2126) : error C2084: function 'F32 ShapeBase::getThreadStartPos(U32)' already has a body
1> ../engine\game/shapeBase.h(1211) : see previous definition of 'getThreadStartPos'
1>..\engine\game\shapeBase.cc(2135) : error C2084: function 'F32 ShapeBase::getThreadStopPos(U32)' already has a body
1> ../engine\game/shapeBase.h(1220) : see previous definition of 'getThreadStopPos'
1>..\engine\game\shapeBase.cc(2144) : error C2084: function 'bool ShapeBase::getThreadLocked(U32)' already has a body
1> ../engine\game/shapeBase.h(1229) : see previous definition of 'getThreadLocked'
1>..\engine\game\shapeBase.cc(3867) : error C2264: 'ShapeBase::getThreadSpeed' : error in function definition or declaration; function not called
1>..\engine\game\shapeBase.cc(3878) : error C2264: 'ShapeBase::getThreadPos' : error in function definition or declaration; function not called
1>..\engine\game\shapeBase.cc(3900) : error C2264: 'ShapeBase::getThreadStartPos' : error in function definition or declaration; function not called
1>..\engine\game\shapeBase.cc(3911) : error C2264: 'ShapeBase::getThreadStopPos' : error in function definition or declaration; function not called
So I removed them from shapebase.cc and that seemed to fix the errors, haven't tested it yet though.
EDIT: Seems to work just fine.
#15
07/27/2008 (12:20 pm)
Rubes - thanks for the fix! I will ask Anthony to remove the functions from the .cc file so this is fixed in the .zip file people download.
#16
My errors, after eliminating those Sgb Gem was seeing, seem to touch everything using a TSShapeBase and look like this:
9>c:\torque\afx112_combo_tgea171_sdk\engine\source\t3d\shapebase.h(1209) : error C2027: use of undefined type 'TSShapeInstance'
9> c:\torque\afx112_combo_tgea171_sdk\engine\source\t3d\shapebase.h(42) : see declaration of 'TSShapeInstance'
9>c:\torque\afx112_combo_tgea171_sdk\engine\source\t3d\shapebase.h(1209) : error C2227: left of '->getPos' must point to class/struct/union/generic type
I've reinstalled a clean afx engine codebase twice now and made the modifications--I might still be making an error in the inclusion process, and I did work at it late into the night yesterday--but I can't seem to solve it.
At this point, the easiest question to ask is are you working off the "stock" TGEA 1.7.1 or an AFX build version, please? If it's the former, I can try working off that tree and see if I can pull off the inclusion to see if I'm just making an error somewhere along the way.
Thanks!
07/27/2008 (3:30 pm)
Hmmm... looks like I better wait for the updated zip file, then. I have been trying now for several days to get these inclusions to compile in the AFX/TGEA 1.7.1 Beta version, but errors as described above have had me starting over several times now. Just commenting out the lines above, which I had tried earlier, doesn't do it.My errors, after eliminating those Sgb Gem was seeing, seem to touch everything using a TSShapeBase and look like this:
9>c:\torque\afx112_combo_tgea171_sdk\engine\source\t3d\shapebase.h(1209) : error C2027: use of undefined type 'TSShapeInstance'
9> c:\torque\afx112_combo_tgea171_sdk\engine\source\t3d\shapebase.h(42) : see declaration of 'TSShapeInstance'
9>c:\torque\afx112_combo_tgea171_sdk\engine\source\t3d\shapebase.h(1209) : error C2227: left of '->getPos' must point to class/struct/union/generic type
I've reinstalled a clean afx engine codebase twice now and made the modifications--I might still be making an error in the inclusion process, and I did work at it late into the night yesterday--but I can't seem to solve it.
At this point, the easiest question to ask is are you working off the "stock" TGEA 1.7.1 or an AFX build version, please? If it's the former, I can try working off that tree and see if I can pull off the inclusion to see if I'm just making an error somewhere along the way.
Thanks!
#17
boolforward is a typo look for bool forward
@ Rubes Sgb Gem
Good catch I will remove the extra defintions
@Netwyrn
We are using stock 1.7.1 without AFX
Resource and Zip updated with fixes
07/28/2008 (10:25 am)
@ Mike and Giovanniboolforward is a typo look for bool forward
@ Rubes Sgb Gem
Good catch I will remove the extra defintions
@Netwyrn
We are using stock 1.7.1 without AFX
Resource and Zip updated with fixes
#18
If I comment out the return line, or substitute something innocuous like "return 0", it compiles just fine.
(The compiler I am using is Visual C++ 2008 Express Edition. I suspect you are using a slightly different byte-grinder, as you refer in the text to a "cc" extension, and VC uses "cpp" as its convention.)
07/28/2008 (8:42 pm)
Hmmmm... alright, I downloaded the updated zip file, and loaded up a copy of TGEA 1.7.1 without AFX. I made the changes as indicated, and after working through Rubes' commenting out of the single line definitions in ShapeBase.h, I am down to just this bit causing compile errors:// With the return line after the test in this commented out, and the F32s from the end
// this will compile and link. Otherwise, every linked module will throw up:
//error C2027: "left of '->getPos' must point to class/struct/union/generic type"
//and
//error C2227: left of '->getPos' must point to class/struct/union/generic type
// all pointing to this line (1188 in this copy of "shapeBase.h") during the compilation
F32 ShapeBase::getThreadPos(U32 slot)
{
Thread& st = mScriptThread[slot];
if (st.sequence != -1) {
return mShapeInstance->getPos(st.thread);
}
return 0;
}If I comment out the return line, or substitute something innocuous like "return 0", it compiles just fine.
(The compiler I am using is Visual C++ 2008 Express Edition. I suspect you are using a slightly different byte-grinder, as you refer in the text to a "cc" extension, and VC uses "cpp" as its convention.)
#19
07/30/2008 (6:31 am)
Netwyrm - did you get past this yet? The compiler is acting as if the class TSShapeInstance is not defined. See if you can figure out why that class is not defined.
#20
I've just taken to reading the text of the next several gems in the series, even though I can't implement them at the moment, in hopes of being able to solve this when I absorb enough I can understand what is happening.
Updated: I spent much of the evening trying everything I could think of... I even downloaded VC++ 2005 and set it up. The project still won't compile, but one additional line turns up in the error log:
If that "illegal indirection" rings any bells for anyone, I'd like to hear of it.
07/30/2008 (1:05 pm)
No, thank you, I'm afraid I haven't. The compiler just reports that the mShapeInstance is not defined, and I am afraid it is beyond my skill to take further. I've just taken to reading the text of the next several gems in the series, even though I can't implement them at the moment, in hopes of being able to solve this when I absorb enough I can understand what is happening.
Updated: I spent much of the evening trying everything I could think of... I even downloaded VC++ 2005 and set it up. The project still won't compile, but one additional line turns up in the error log:
9>c:\Torque\TGEA_1_7_1\engine\source\T3D/shapeBase.h(1188) : error C2100: illegal indirection 9>sphere.cpp 9>showTSShape.cpp 9>shapeImage.cpp 9>c:\Torque\TGEA_1_7_1\engine\source\T3D/shapeBase.h(1188) : error C2100: illegal indirection 9>shapeCollision.cpp 9>c:\Torque\TGEA_1_7_1\engine\source\T3D/shapeBase.h(1188) : error C2027: use of undefined type 'TSShapeInstance' 9> c:\Torque\TGEA_1_7_1\engine\source\T3D/shapeBase.h(32) : see declaration of 'TSShapeInstance' 9>c:\Torque\TGEA_1_7_1\engine\source\T3D/shapeBase.h(1188) : error C2227: left of '->getPos' must point to class/struct/union/generic type
If that "illegal indirection" rings any bells for anyone, I'd like to hear of it.
Torque Owner Kory Imaginism
team innovative imaginations