Game Development Community

Bitstream custom variables.

by Lukas Joergensen · in Torque 3D Beginner · 05/12/2012 (7:23 pm) · 14 replies

So i went into a problem. I need to have several particle emitters using the same datablock but a change in one particle emitter shouldn't affect them all (pretty contradicting)
I couldn't find any native function so i decided to extend the engine. However, somethings wrong with it and i believe it is the packUpdate and unpackUpdate (my experience with this bitstream thing is 0) i have absolutely no idea if it is right or wrong so i guess it would be a good place to start.
But i can't really find any documentation on it, anyone of you guys care to help me?
You can have this cookie as a reward 1.bp.blogspot.com/-_cfIf12IV3g/TcDIwjbVeII/AAAAAAAAAIs/YNOaSmnEKhY/s1600/cookie.gif
Here is my packUpdate and unpackUpdate functions ( written into the ones at particleEmitterNode.cpp ).
//-----------------------------------------------------------------------------
// packUpdate
//-----------------------------------------------------------------------------
U32 ParticleEmitterNode::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
   U32 retMask = Parent::packUpdate(con, mask, stream);

   if ( stream->writeFlag( mask & InitialUpdateMask ) )
   {
      mathWrite(*stream, getTransform());
      mathWrite(*stream, getScale());
   }

   if ( stream->writeFlag( mask & EmitterDBMask ) )
   {
      if( stream->writeFlag(mEmitterDatablock != NULL) )
      {
         stream->writeRangedU32(mEmitterDatablock->getId(), DataBlockObjectIdFirst,
            DataBlockObjectIdLast);
      }
   }

   if ( stream->writeFlag( mask & StateMask ) )
   {
      stream->writeFlag( mActive );
      stream->write( mVelocity );
      stream->write( standAloneEmitter ); // This one is working afaik
   }
       //My additions
	stream->writeInt(sa_ejectionPeriodMS, 10);
	stream->writeInt(sa_periodVarianceMS, 10);
	stream->writeInt((S32)(sa_ejectionVelocity * 100), 16);
	stream->writeInt((S32)(sa_velocityVariance * 100), 14);
	if( stream->writeFlag( sa_ejectionOffset != 0.f ) )
		stream->writeInt((S32)(sa_ejectionOffset * 100), 16);
	stream->writeRangedU32((U32)sa_thetaMin, 0, 180);
	stream->writeRangedU32((U32)sa_thetaMax, 0, 180);
	if( stream->writeFlag( sa_phiReferenceVel != 0.f ) )
		stream->writeRangedU32((U32)sa_phiReferenceVel, 0, 360);
	if( stream->writeFlag( sa_phiVariance != 360.f ) )
		stream->writeRangedU32((U32)sa_phiVariance, 0, 360);
       //My additions -- end
   return retMask;
}
//-----------------------------------------------------------------------------
// unpackUpdate
//-----------------------------------------------------------------------------
void ParticleEmitterNode::unpackUpdate(NetConnection* con, BitStream* stream)
{
   Parent::unpackUpdate(con, stream);

	if( stream->readFlag() )
		sa_phiVariance = (F32)stream->readRangedU32(0, 360);
	else
		sa_phiVariance = 360.f;

   if ( stream->readFlag() )
   {
      MatrixF temp;
      Point3F tempScale;
      mathRead(*stream, &temp);
      mathRead(*stream, &tempScale);

      setScale(tempScale);
      setTransform(temp);
   }

   if ( stream->readFlag() )
   {
      mEmitterDatablockId = stream->readFlag() ?
         stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast) : 0;

      ParticleEmitterData *emitterDB = NULL;
      Sim::findObject( mEmitterDatablockId, emitterDB );
      if ( isProperlyAdded() )
         setEmitterDataBlock( emitterDB );
   }

   if ( stream->readFlag() )
   {
      mActive = stream->readFlag();
      stream->read( &mVelocity );

	  stream->read( &standAloneEmitter );
   }
//My additions
   	sa_ejectionPeriodMS = stream->readInt(10);
	sa_periodVarianceMS = stream->readInt(10);
	sa_ejectionVelocity = stream->readInt(16) / 100.0f;
	sa_velocityVariance = stream->readInt(14) / 100.0f;
	if( stream->readFlag() )
		sa_ejectionOffset = stream->readInt(16) / 100.0f;
	else
		sa_ejectionOffset = 0.f;

	sa_thetaMin = (F32)stream->readRangedU32(0, 180);
	sa_thetaMax = (F32)stream->readRangedU32(0, 180);
	if( stream->readFlag() )
		sa_phiReferenceVel = (F32)stream->readRangedU32(0, 360);
	else
		sa_phiReferenceVel = 0.f;
//My additions -- end
}

#1
05/12/2012 (9:15 pm)
I am not very familiar with this, but as a cursory view, it looks like the logic here is incorrect in the code used to read from the stream.

91. if( stream->readFlag() )
92. sa_ejectionOffset = stream->readInt(16) / 100.0f;
93. else
94. sa_ejectionOffset = 0.f;


Unless writeflag returns the value that you actually wrote, you will need to read an int either way here.

There are two blocks of code like this that are suspect.

The documentation I found does not indicate whether this is the case or not, and I do not have time to dig into the code at the moment (prepping for mothers day).

John

John
#2
05/13/2012 (3:36 am)
Oh well, actually i just took the read/write code from the particleEmitterData (Should have mentioned that) Therefore i don't know why it is setup like that (Prolly something to do with boolean settings that have an influence on what data is set in the particleemitterdata)
#3
05/13/2012 (3:50 am)
So i should probably specify what i am trying to do and what goes wrong:
Over the course of the particleEmitterNodes life, i run setFieldValue several times to change the behaviour of the emitter.

But setFieldValue on a datablock will change all the emitters with said datablock, and i haven't found another way to change the behaviour of a single emitter besides changing the datablock.

So i made changes to the source so i would be able to change some settings on a per node basis.
However when i run setFieldValue now the emitter doesn't update and stays at the initial value.

Thats why i think the read/write bits is the problem.

Now i removed the if settings, i believe they are there to prevent errors with the emitter getting set to 360 like the phiReferenceVel.

I did fix sa_phiVariance too and moved it to the end rather than the beginning of the code but it's still not working :/
#4
05/13/2012 (11:02 am)
I think we'd have to see your code to provide better insight as to what is wrong. If you post it, then we can see what the problem could be.
#5
05/13/2012 (11:11 am)
I will try to find everything i edited then, i edited it from stock source code so it is pretty much the same.
To avoid spamming this comment section i will post it as a winMerge report just have to install T3D another time to get the source
(Backup? Never heard of it doh >.<)
Do you want to see the declaration of the particle emitter object in script as well?
#6
05/13/2012 (11:46 am)
I take that back, looks like WinMerge doesn't interpret the files properly so i will be posting it here instead :)
particleEmitterNode.h
//------------------------- Stand alone variables
   bool standAloneEmitter;
   S32   sa_ejectionPeriodMS;                   ///< Time, in Milliseconds, between particle ejection
   S32   sa_periodVarianceMS;                   ///< Varience in ejection peroid between 0 and n

   F32   sa_ejectionVelocity;                   ///< Ejection velocity
   F32   sa_velocityVariance;                   ///< Variance for velocity between 0 and n
   F32   sa_ejectionOffset;                     ///< Z offset from emitter point to eject from

   F32   sa_thetaMin;                           ///< Minimum angle, from the horizontal plane, to eject from
   F32   sa_thetaMax;                           ///< Maximum angle, from the horizontal plane, to eject from

   F32   sa_phiReferenceVel;                    ///< Reference angle, from the verticle plane, to eject from
   F32   sa_phiVariance;                        ///< Varience from the reference angle, from 0 to n
Right before
ParticleEmitterNode(); 
~ParticleEmitterNode();

particleEmitterNode.cpp
In ParticleEmitterNode::ParticleEmitterNode();
Added this right at the end of thefunction:
standAloneEmitter = false;

   sa_ejectionPeriodMS = 100;    // 10 Particles Per second
   sa_periodVarianceMS = 0;      // exactly

   sa_ejectionVelocity = 2.0f;   // From 1.0 - 3.0 meters per sec
   sa_velocityVariance = 1.0f;
   sa_ejectionOffset   = 0.0f;   // ejection from the emitter point

   sa_thetaMin         = 0.0f;   // All heights
   sa_thetaMax         = 90.0f;

   sa_phiReferenceVel  = 0.0f;   // All directions
   sa_phiVariance      = 180.0f;
In initpersistfields:
addField( "standAloneEmitter", TYPEID< bool >(), Offset(standAloneEmitter, ParticleEmitterNode),
         "@brief If true, this datablock is not connected other datablocks of the same type .n"
         "Useful for animated datablocks." );

   addField("sa_ejectionPeriodMS", TYPEID< S32 >(), Offset(sa_ejectionPeriodMS,   ParticleEmitterNode),
         "Time (in milliseconds) between each particle ejection." );

    addField("sa_periodVarianceMS", TYPEID< S32 >(), Offset(sa_periodVarianceMS,   ParticleEmitterNode),
        "Variance in ejection period, from 1 - ejectionPeriodMS." );

    addField( "sa_ejectionVelocity", TYPEID< F32 >(), Offset(sa_ejectionVelocity, ParticleEmitterNode),
        "Particle ejection velocity." );

    addField( "sa_velocityVariance", TYPEID< F32 >(), Offset(sa_velocityVariance, ParticleEmitterNode),
        "Variance for ejection velocity, from 0 - ejectionVelocity." );

    addField( "sa_ejectionOffset", TYPEID< F32 >(), Offset(sa_ejectionOffset, ParticleEmitterNode),
        "Distance along ejection Z axis from which to eject particles." );

    addField( "sa_thetaMin", TYPEID< F32 >(), Offset(sa_thetaMin, ParticleEmitterNode),
        "Minimum angle, from the horizontal plane, to eject from." );

    addField( "sa_thetaMax", TYPEID< F32 >(), Offset(sa_thetaMax, ParticleEmitterNode),
        "Maximum angle, from the horizontal plane, to eject particles from." );

    addField( "sa_phiReferenceVel", TYPEID< F32 >(), Offset(sa_phiReferenceVel, ParticleEmitterNode),
        "Reference angle, from the vertical plane, to eject particles from." );

    addField( "sa_phiVariance", TYPEID< F32 >(), Offset(sa_phiVariance, ParticleEmitterNode),
        "Variance from the reference angle, from 0 - 360." );
#7
05/13/2012 (11:47 am)
Continued:

In ParticleEmitterNode::advanceTime(F32)
//changed this
mEmitter->emitParticles(emitPoint, emitPoint,
                           emitAxis,
                           emitVelocity, (U32)(dt * mDataBlock->timeMultiple * 1000.0f));
//To this
mEmitter->emitParticles(emitPoint, emitPoint,
                           emitAxis,
                           emitVelocity, (U32)(dt * mDataBlock->timeMultiple * 1000.0f), this);

In ParticleEmitterNode::setEmitterDatablock(ParticleEmitterData*)
void ParticleEmitterNode::setEmitterDataBlock(ParticleEmitterData* data)
{
   if ( isServerObject() )
   {
      setMaskBits( EmitterDBMask );
   }
   else
   {
	  if(standAloneEmitter)
	  {
		  sa_thetaMax = data->thetaMax;
		  sa_thetaMin = data->thetaMin;

		  sa_phiReferenceVel = data->phiReferenceVel;
		  sa_phiVariance = data->phiVariance;

		  sa_ejectionVelocity = data->ejectionVelocity;
		  sa_velocityVariance = data->velocityVariance;
		  sa_ejectionOffset = data->ejectionOffset;

		  sa_ejectionPeriodMS = data->ejectionPeriodMS;
		  sa_periodVarianceMS = data->periodVarianceMS;
	  }
//Rest of function
particleEmitter.h
Added this right after all the #ifndef
#include "T3D/fx/particleEmitterNode.h"
Changed emitParticles:
//From
void emitParticles(const Point3F& start,
                      const Point3F& end,
                      const Point3F& axis,
                      const Point3F& velocity,
                      const U32      numMilliseconds);
//To
void emitParticles(const Point3F& start,
                      const Point3F& end,
                      const Point3F& axis,
                      const Point3F& velocity,
                      const U32      numMilliseconds,
					  ParticleEmitterNode* node);
Added an addParticle overload:
void addParticle(const Point3F &pos, const Point3F &axis, const Point3F &vel, const Point3F &axisx,
					  ParticleEmitterNode* node);
#8
05/13/2012 (11:48 am)
Continued:

particleEmitter.cpp
//From
void ParticleEmitter::emitParticles(const Point3F& start,
                                    const Point3F& end,
                                    const Point3F& axis,
                                    const Point3F& velocity,
                                    const U32      numMilliseconds)
//To
ParticleEmitter::emitParticles(const Point3F& start,
                                    const Point3F& end,
                                    const Point3F& axis,
                                    const Point3F& velocity,
                                    const U32      numMilliseconds,
									ParticleEmitterNode* node)
Inside the emitParticles function:
//From
// Create particle at the correct position
         Point3F pos;
         pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
         addParticle(pos, axis, velocity, axisx);
         particlesAdded = true;
         mNextParticleTime = 0;
//To
// Create particle at the correct position
         Point3F pos;
         pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));
         addParticle(pos, axis, velocity, axisx, node);
         particlesAdded = true;
         mNextParticleTime = 0;
//...
//From
S32 nextTime = mDataBlock->ejectionPeriodMS;
      if( mDataBlock->periodVarianceMS != 0 )
      {
         nextTime += S32(gRandGen.randI() % (2 * mDataBlock->periodVarianceMS + 1)) -
                     S32(mDataBlock->periodVarianceMS);
      }
//To
S32 nextTime;
	   if(node->standAloneEmitter)
	   {
		   nextTime = node->sa_ejectionPeriodMS;
		   if( node->sa_periodVarianceMS != 0 )
			{
				nextTime += S32(gRandGen.randI() % (2 * node->sa_periodVarianceMS + 1)) -
						S32(node->sa_periodVarianceMS);
			}
	   }
	   else
	   {
			nextTime = mDataBlock->ejectionPeriodMS;
			if( mDataBlock->periodVarianceMS != 0 )
			{
				nextTime += S32(gRandGen.randI() % (2 * mDataBlock->periodVarianceMS + 1)) -
						S32(mDataBlock->periodVarianceMS);
			}
	   }
And added the new addParticle overload based on the old one:
void ParticleEmitter::addParticle(const Point3F& pos,
                                  const Point3F& axis,
                                  const Point3F& vel,
                                  const Point3F& axisx,
				ParticleEmitterNode* nodeDat)
{
   //......
   F32 ref;
   F32 phi;
   F32 theta;

   if(nodeDat->standAloneEmitter)
   {
		theta = (nodeDat->sa_thetaMax - nodeDat->sa_thetaMin) * gRandGen.randF() +
               nodeDat->sa_thetaMin;
		ref  = (F32(mInternalClock) / 1000.0) * nodeDat->sa_phiReferenceVel;
		phi  = ref + gRandGen.randF() * nodeDat->sa_phiVariance;
   }
   else{
	   theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() +
				   mDataBlock->thetaMin;

	   ref  = (F32(mInternalClock) / 1000.0) * mDataBlock->phiReferenceVel;
	   phi  = ref + gRandGen.randF() * mDataBlock->phiVariance;
   }
//...
   F32 initialVel;
   if(nodeDat->standAloneEmitter)
   {
	   initialVel = nodeDat->sa_ejectionVelocity;
	   initialVel    += (nodeDat->sa_velocityVariance * 2.0f * gRandGen.randF()) - nodeDat->sa_velocityVariance;
   }
   else
   {
	   initialVel = mDataBlock->ejectionVelocity;
	   initialVel    += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
   }
   if(nodeDat->standAloneEmitter)
		pNew->pos = pos + (ejectionAxis * nodeDat->sa_ejectionOffset);
   else
		pNew->pos = pos + (ejectionAxis * mDataBlock->ejectionOffset);
   pNew->vel = ejectionAxis * initialVel;
   pNew->orientDir = ejectionAxis;
   pNew->acc.set(0, 0, 0);
   pNew->currentAge = 0;

   //...

}
#9
05/13/2012 (11:51 am)
I hope that is enough information :)
I think the most interesting places is the packupdate and unpackupdate.
And wherever it says this:
if(nodeDat->standAloneEmitter)
Edit:
I checked every single particle in the particle emitter and it is calling the new overloaded function. So i'm pretty contetn that the particleEmitter.cpp is working as it should.
And all the newly added variables ( the sa_ ) is also found by the world editor and if you dump the particle emitter.
#10
05/13/2012 (1:49 pm)
Hmm i might have bothered you too much, it seems that changing the values in the world editor is working properly but if i do it scriptwise with setFieldValue it acts weird here is how i would call it from script:
emitter.setFieldValue("sa_ejectionOffset", 4.84);
#11
05/13/2012 (1:55 pm)
I am terribly sorry for the inconvenience!
It seems as it is simply because the emitter is not tagged as dirty when you edit variables on the particleEmitterNode as it is not built for having an effect on the particleEmitter only the position of this. Thats also why it worked fine when editing the datablocks!
Thank you very much for your time!
Edit:
Oh yeah if anyone knows the best way to mark it dirty, or to get a kind of onValueChanged callback it would save me alot of time! :)
Found out how to reload the particle emitter ( the reload function doh ), but would like to know if there were a way from the source to detect when a variable has changed?
#12
05/13/2012 (3:18 pm)
My only problem right now is it doesn't read the intial values like it's supposed to.
i tested this code:
if(standAloneEmitter)
	  {
		  sa_thetaMax = data->thetaMax;
		  sa_thetaMin = data->thetaMin;

		  sa_phiReferenceVel = data->phiReferenceVel;
		  sa_phiVariance = data->phiVariance;

		  sa_ejectionVelocity = data->ejectionVelocity;
		  sa_velocityVariance = data->velocityVariance;
		  sa_ejectionOffset = data->ejectionOffset;

		  sa_ejectionPeriodMS = data->ejectionPeriodMS;
		  sa_periodVarianceMS = data->periodVarianceMS;
	  }
And sa_ejectionOffset is 0 before the datablock is applied and it is 5 after. I tested it as well as i could but i can't locate where it is reset or if it is because it doesn't get set properly.
#13
05/14/2012 (5:52 am)
sa_ejectionOffset != 0.f

could be replaced with:
sa_ejectionOffset != 0

Non-transitive compares would lead to problems,I mean that a=b,b=c, but sometimes c!=a
#14
05/14/2012 (8:22 am)
Oh i got it! It is my logic that is wrong. I wasn't taking the client/server structure into account!
In the onAdd function of ParticleEmitterNode it says:
if( isClientObject() )
   {
      setEmitterDataBlock( mEmitterDatablock );
   }
   else
   {
      setMaskBits( StateMask | EmitterDBMask );
   }
So if the particle emitter is created serverside, it never goes to setEmitterDataBlock and therefore the variables isn't set on the serverside and because of that it will get set to defaults on the client side.
To fix it i wrote this:
if( isClientObject() )
   {
      setEmitterDataBlock( mEmitterDatablock );
   }
   else
   {
	   	sa_thetaMax = mEmitterDatablock->thetaMax;
		sa_thetaMin = mEmitterDatablock->thetaMin;

		sa_phiReferenceVel = mEmitterDatablock->phiReferenceVel;
		sa_phiVariance = mEmitterDatablock->phiVariance;

		sa_ejectionVelocity = mEmitterDatablock->ejectionVelocity;
		sa_velocityVariance = mEmitterDatablock->velocityVariance;
		sa_ejectionOffset = mEmitterDatablock->ejectionOffset;

		sa_ejectionPeriodMS = mEmitterDatablock->ejectionPeriodMS;
		sa_periodVarianceMS = mEmitterDatablock->periodVarianceMS;
      setMaskBits( StateMask | EmitterDBMask | emitterEdited );
   }