Game Development Community

Plastic Gem #2: Animation Threads

by Paul Dana · 06/10/2008 (6:48 am) · 37 comments

Download Code File

i936.photobucket.com/albums/ad202/vincismurf/banner.jpg

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
www.plasticgames.com/dev/blog_images/gems/grandfatherclock_1030.jpg
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 threads

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
// > 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 threads
4) 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 thread

BESURE 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 thread

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
// > 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 thread

BESURE 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.
Page«First 1 2 Next»
#21
08/04/2008 (5:54 am)
I can confirm the problems Netwyrm and company are having: "use of undefined type 'TSShapeInstance'" everywhere on a pre-merged TGEA/AFX installation. Followed the instructions meticulously, and replacing

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.
#22
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
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
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
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
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
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
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
03/04/2009 (6:13 am)
FIX 3/4/09

I 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 threads

To 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
08/11/2009 (2:19 pm)
Konrad Kiss how did you fix the
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.
#37
08/14/2009 (7:25 am)
T3D beta 5 does not have a forward variable.
Page«First 1 2 Next»