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
Here is my packUpdate and unpackUpdate functions ( written into the ones at particleEmitterNode.cpp ).
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

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
}About the author
IPS Bundle available at: https://www.winterleafentertainment.com/Products/IPS.aspx
#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
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 :/
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
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?
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
particleEmitterNode.h
particleEmitterNode.cpp
In ParticleEmitterNode::ParticleEmitterNode();
Added this right at the end of thefunction:
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 nRight 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
In ParticleEmitterNode::advanceTime(F32)
In ParticleEmitterNode::setEmitterDatablock(ParticleEmitterData*)
Added this right after all the #ifndef
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 functionparticleEmitter.hAdded 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
particleEmitter.cpp
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
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.
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
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?
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
i tested this code:
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
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
05/14/2012 (5:52 am)
sa_ejectionOffset != 0.fcould 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
In the onAdd function of ParticleEmitterNode it says:
To fix it i wrote this:
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 );
}
Torque Owner John Bergman
Default Studio Name
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