Improved Particle Emitters
by Sebastiaan Keek · 04/08/2003 (1:42 pm) · 48 comments
Download Code File
First off this is my first attempt at torque coding besides coppy and pasting other code. The (not all that nicely) documented added and expanded fields have been tested to a good extend and "if" you get what the heck it is they actually do.. you'l find them very usefull for creating certain special effects and whatnot.
This code is meant for the latest head of approx early april 2003.
It seems to be incompatible with 1.1.1.
I've included a readme for reference an screenshot and a modified scorchedPlanet.mis to showcase some of the posibilities. And ofcourse the code files.
The code files overwrite your current particleEmitter and ParticleEngine source files. If you made any changes to them, you could try merging, thought I recommend testing out my version first to see if it's worth it ;).
But it's worth it.. trust me. The additions allow for creations of complex rotating fields of particles, cubic fields of particles and particles relative to objects..
Also it removes the need for a model node by introducing a virtual node in the emitter system.
I also enabled the ability for emitters to scale, either by datablock or by linked object wich allows for player jetpacks to dynamically scale along with the rest of the model.
However with the introduction of new fields also comes added complexity. I'l try to update my documentation and hopefully shed light on questions anyone might have.
Finally, please feel free to make comments, ask questions or make sugestions either by email or on this page.
*Updated to version 0.3
First off this is my first attempt at torque coding besides coppy and pasting other code. The (not all that nicely) documented added and expanded fields have been tested to a good extend and "if" you get what the heck it is they actually do.. you'l find them very usefull for creating certain special effects and whatnot.
This code is meant for the latest head of approx early april 2003.
It seems to be incompatible with 1.1.1.
I've included a readme for reference an screenshot and a modified scorchedPlanet.mis to showcase some of the posibilities. And ofcourse the code files.
The code files overwrite your current particleEmitter and ParticleEngine source files. If you made any changes to them, you could try merging, thought I recommend testing out my version first to see if it's worth it ;).
But it's worth it.. trust me. The additions allow for creations of complex rotating fields of particles, cubic fields of particles and particles relative to objects..
Also it removes the need for a model node by introducing a virtual node in the emitter system.
I also enabled the ability for emitters to scale, either by datablock or by linked object wich allows for player jetpacks to dynamically scale along with the rest of the model.
However with the introduction of new fields also comes added complexity. I'l try to update my documentation and hopefully shed light on questions anyone might have.
Finally, please feel free to make comments, ask questions or make sugestions either by email or on this page.
*Updated to version 0.3
#42
TorqueScript to attach a beam to an object (I'll leave the actual ParticleEmitterData and ParticleEmitterNodeData out since any will work in this example)
The part that requires API modification is the linkObject() call. That's what actually watches the position of the first object and moves the second (the emitternode) to follow when it moves. To do that, patch your files between the ellipsis:
in particleEmitter.h:
in particleEmitter.cc:
in particleEngine.cc
That's the basic gist of it. I added 3 booleans to particleEngine.h called linkPosition, linkRotation and linkScale that control the linking of the 3 elements as described. Well, at least I think those were added by me? To be totally honest, I've made so many small changes to the 1.3 engine that I'd seriously need to do a full diff against a matching checkout that I was building against to find everything. This code was just me digging through looking for keywords.
This code also only controls linking particleEmitterNodes to gamebase objects, which was enough for what I wanted to do. I'm guessing you could use a similar method of control to link gamebase objects to other gamebase objects if that's what you need.
I don't know how well this will fit in with 1.4, either. I haven't tried, but you should be able to find all the appropriate spots in the 1.3 code base if you pretend there is code where I put an ellipsis :)
For other people just looking to link things like this: I may have pasted some of Keek's code in with mine, so don't be confused if you just want to mount stuff like this and don't have his whole patch in. Just take a look at what it does, do some figuring, make it make sense and I think you'll be all good.
This all probably won't compile for some reason or another if you just paste it in. I can try to help figure out what I'm missing if you ask, otherwise, I trust you all to fill in the gaps :)
09/14/2006 (8:23 pm)
Allright here's what I got so far:TorqueScript to attach a beam to an object (I'll leave the actual ParticleEmitterData and ParticleEmitterNodeData out since any will work in this example)
function attachBeam(%obj) {
if (%obj.beam $= "") {
%obj.beam = new ParticleEmitterNode() {
position = %obj.position;
rotation = "1 0.5 0 0";
scale = "1 1 1";
dataBlock = "BeamUpNode";
emitter = "BeamEmitter";
velocity = "1";
};
%obj.beam.linkObject(%obj);
}
}
function detachBeam(%obj) {
if (%obj.beam) {
%obj.beam.delete();
%obj.beam = "";
}
}The part that requires API modification is the linkObject() call. That's what actually watches the position of the first object and moves the second (the emitternode) to follow when it moves. To do that, patch your files between the ellipsis:
in particleEmitter.h:
...
ParticleEmitter* mEmitter;
F32 mVelocity;
GameBase* mLinkObject;
public:
ParticleEmitterNode();
~ParticleEmitterNode();
enum ParticleEmitterMasks {
LinkObjectMask = Parent::NextFreeMask,
NextFreeMask = LinkObjectMask << 1
};
// Time/Move Management
public:
void advanceTime(F32 dt);
void setEmitterDataBlock(ParticleEmitterData* data);
void setLinkObject(GameBase* obj);
...in particleEmitter.cc:
ParticleEmitterNode::ParticleEmitterNode()
{
...
mLinkObject = NULL;
}
bool ParticleEmitterNode::onAdd()
{
...
if (isClientObject()) {
ParticleEmitter* pEmitter = new ParticleEmitter;
pEmitter->onNewDataBlock(mEmitterDatablock);
if (pEmitter->registerObject() == false) {
Con::warnf(ConsoleLogEntry::General, "Could not register base emitter for particle of class: %s", mDataBlock->getName());
delete pEmitter;
return false;
}
mEmitter = pEmitter;
mEmitter->mLinkObject = mLinkObject;
}
...
}
U32 ParticleEmitterNode::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
mathWrite(*stream, getTransform());
mathWrite(*stream, getScale());
if (stream->writeFlag(mEmitterDatablock != NULL)) {
stream->writeRangedU32(mEmitterDatablock->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
}
if (mask & LinkObjectMask) {
S32 gIndex = -1;
if (mLinkObject) {
gIndex = con->getGhostIndex(mLinkObject);
printf("Server obj %d -> Client obj %d\n", mLinkObject->getId(), gIndex);
}
if (stream->writeFlag(true)) {
stream->writeInt(gIndex,NetConnection::GhostIdBitSize);
}
} else {
stream->writeFlag(false);
}
return retMask;
}
void ParticleEmitterNode::unpackUpdate(NetConnection* con, BitStream* stream)
{
Parent::unpackUpdate(con, stream);
MatrixF temp;
Point3F tempScale;
mathRead(*stream, &temp);
mathRead(*stream, &tempScale);
if (stream->readFlag()) {
mEmitterDatablockId = stream->readRangedU32(DataBlockObjectIdFirst,
DataBlockObjectIdLast);
} else {
mEmitterDatablockId = 0;
}
if (stream->readFlag()) {
S32 linkObjIndex = stream->readInt(NetConnection::GhostIdBitSize);
printf("Client obj %d\n", linkObjIndex);
if (linkObjIndex != -1) {
NetObject* obj = con->resolveGhost(linkObjIndex);
setLinkObject((GameBase*)obj);
} else {
setLinkObject(NULL);
}
}
setScale(tempScale);
setTransform(temp);
}
void ParticleEmitterNode::setEmitterDataBlock(ParticleEmitterData* data)
{
...
if (pEmitter)
{
mEmitter->deleteWhenEmpty();
mEmitter = pEmitter;
mEmitter->mLinkObject = mLinkObject;
}
}
}
void ParticleEmitterNode::setLinkObject(GameBase *obj) {
//keep a local reference to preserve between datablocks
mLinkObject = obj;
if (isClientObject()) {
if (mEmitter != NULL) {
mEmitter->mLinkObject = obj;
}
}
if (isServerObject()) {
setMaskBits(LinkObjectMask);
}
}
//Links the emitter to an object
ConsoleMethod(ParticleEmitterNode, linkObject, void, 3, 3, "(object)")
{
GameBase* obj = dynamic_cast<GameBase*>(Sim::findObject(dAtoi(argv[2])));
if (!obj)
obj = dynamic_cast<GameBase*>(Sim::findObject(argv[2]));
if (obj) {
object->setLinkObject(obj);
}
}in particleEngine.cc
ParticleEmitter::ParticleEmitter()
{
...
mLifetimeMS = 0;
mElapsedTimeMS = 0;
mLinkObject = NULL;
mPrintCount = 0;
}
inline void ParticleEmitter::renderBillboardParticle( Particle &part, Point3F *basePnts, MatrixF &camView, F32 spinFactor )
{
glBegin(GL_QUADS);
glColor4f(part.color.red,
part.color.green,
part.color.blue,
part.color.alpha);
//[most]
F32 scale = part.currentOwner->getDataBlock()->internalScale.len()/1.7;
if (part.currentOwner->mLinkObject && part.currentOwner->getDataBlock()->linkScale && !part.currentOwner->mLinkObject->getScale().isZero())
scale *= part.currentOwner->mLinkObject->getScale().len()/1.7;
F32 width = part.size * 0.5 * scale;
F32 spinAngle = part.spinSpeed * part.currentAge * spinFactor + part.currentOwner->getDataBlock()->orientBase;
//[most]
...
inline void ParticleEmitter::renderOrientedParticle( Particle &part, const Point3F &camPos )
{
...
F32 scale = part.currentOwner->getDataBlock()->internalScale.len()/1.7;
if (part.currentOwner->mLinkObject && part.currentOwner->getDataBlock()->linkScale && !part.currentOwner->mLinkObject->getScale().isZero())
scale *= part.currentOwner->mLinkObject->getScale().len()/1.7;
...
void ParticleEmitter::addParticle(const Point3F& pos,
const Point3F& axis,
const Point3F& vel,
const Point3F& axisx)
{
...
if (mDataBlock->linkPosition && this->mLinkObject && !this->mLinkObject->getPosition().isZero()) {
//use node offset
Point3F loPos = this->mLinkObject->getPosition();
if (mPrintCount < 2) {
printf("newParticle Pos=(%f %f %f) LinkObject=%s (%f %f %f)\n", pos.x, pos.y, pos.z, this->mLinkObject->getIdString(), loPos.x, loPos.y, loPos.z);
printf("LinkObject=%s (%f %f %f)\n", this->mLinkObject->getIdString(), loPos.x, loPos.y, loPos.z);
mPrintCount++;
}
//pos is the position of the emitter node, doesn't ever change
pNew->pos = this->mLinkObject->getPosition() + (ejectionAxis * mDataBlock->ejectionOffset);
MatrixF lastLinkObjTransform = MatrixF();
lastLinkObjTransform.setPosition(this->mLinkObject->getRenderTransform().getPosition());
pNew->lastLinkObjTransform = lastLinkObjTransform;
} else {
pNew->pos = pos + (ejectionAxis * mDataBlock->ejectionOffset);
}
...
void PEngine::updateSingleParticle(Particle* particle, ParticleEmitter &emitter, const U32 ms)
{
AssertFatal(particle != NULL, "PEngine::updateSingleParticle: Error, must have a particle to process in this function");
AssertFatal(ms != 0, "PEngine::updateSingleParticle: error, no time to update?");
F32 t = F32(ms) / 1000.0;
Point3F a = particle->acc;
a -= particle->vel * particle->dataBlock->dragCoefficient;
a -= ParticleEngine::windVelocity * particle->dataBlock->windCoefficient;
a += Point3F(0, 0, -9.81) * particle->dataBlock->gravityCoefficient;
if (emitter.mLinkObject && emitter.getDataBlock()->linkPosition) {
//the linkobj transform when we last updated
Point3F prev = particle->lastLinkObjTransform.getPosition();
//the current linkobj transform
Point3F cur = emitter.mLinkObject->getPosition();
if (prev != cur) {
//printf("p(%.5f %.5f %.5f) c(%.5f %.5f %.5f)\n", prev.x, prev.y, prev.z, cur.x, cur.y, cur.z);
//the difference between where it was and where it is
Point3F linkObjOffsetUpdate = cur - prev;
//add the difference to the current position
particle->pos += linkObjOffsetUpdate;
//set the last pos to cur
particle->lastLinkObjTransform.setPosition(cur);
}
}
...That's the basic gist of it. I added 3 booleans to particleEngine.h called linkPosition, linkRotation and linkScale that control the linking of the 3 elements as described. Well, at least I think those were added by me? To be totally honest, I've made so many small changes to the 1.3 engine that I'd seriously need to do a full diff against a matching checkout that I was building against to find everything. This code was just me digging through looking for keywords.
This code also only controls linking particleEmitterNodes to gamebase objects, which was enough for what I wanted to do. I'm guessing you could use a similar method of control to link gamebase objects to other gamebase objects if that's what you need.
I don't know how well this will fit in with 1.4, either. I haven't tried, but you should be able to find all the appropriate spots in the 1.3 code base if you pretend there is code where I put an ellipsis :)
For other people just looking to link things like this: I may have pasted some of Keek's code in with mine, so don't be confused if you just want to mount stuff like this and don't have his whole patch in. Just take a look at what it does, do some figuring, make it make sense and I think you'll be all good.
This all probably won't compile for some reason or another if you just paste it in. I can try to help figure out what I'm missing if you ask, otherwise, I trust you all to fill in the gaps :)
#43
09/15/2006 (7:32 am)
Thanks a lot, I will try to implement these this morning and post if anything comes up.
#44
also, It seems that one of my post was lost... hmmmmzing!
I did ask about something though. I was wondering if you just mount to the origin of the object? I dont see anywhere in there about the node to attach to and getting that node data.
09/15/2006 (11:33 am)
Worked on it some and i added #include "sim/netConnection.h" and that seemed to help the network connections now i just need to figure out what your lastLinkObjTransform is really. Could you put that up so i know what kind of struct that is or information on it. also, It seems that one of my post was lost... hmmmmzing!
I did ask about something though. I was wondering if you just mount to the origin of the object? I dont see anywhere in there about the node to attach to and getting that node data.
#45
11/28/2006 (8:19 pm)
Just wondering if anyone has got this to work in TGE 1.5 and would like to share?
#46
I was following your example, compiled it and had many errors. Then I realized that you didn't have anything for particleEngine.h to add. So obviously there's a pile of bits and pieces that need to be added in the header file.
here's are a couple errors for example:
error C2039: 'internalScale' : is not a member of 'ParticleEmitterData'
/particleEngine.h(164) : see declaration of 'ParticleEmitterData'
error C2228: left of '.len' must have class/struct/union
particleEngine.cc(1179) : error C2039: 'linkScale' : is not a member of 'ParticleEmitterData'
particleEngine.h(164) : see declaration of 'ParticleEmitterData'
particleEngine.cc(1184) : error C2039: 'orientBase' : is not a member of 'ParticleEmitterData'
** mose errors come from this piece of code that you have:
F32 scale = part.currentOwner->getDataBlock()->internalScale.len()/1.7;
if (part.currentOwner->mLinkObject && part.currentOwner->getDataBlock()->linkScale && !part.currentOwner->mLinkObject->getScale().isZero())
scale *= part.currentOwner->mLinkObject->getScale().len()/1.7;
**
internalScale.len()
linkScale
If you could post them that would be fantastic. You'd help me out big time.
Thank you
03/16/2007 (1:15 pm)
Hey Rob Green,I was following your example, compiled it and had many errors. Then I realized that you didn't have anything for particleEngine.h to add. So obviously there's a pile of bits and pieces that need to be added in the header file.
here's are a couple errors for example:
error C2039: 'internalScale' : is not a member of 'ParticleEmitterData'
/particleEngine.h(164) : see declaration of 'ParticleEmitterData'
error C2228: left of '.len' must have class/struct/union
particleEngine.cc(1179) : error C2039: 'linkScale' : is not a member of 'ParticleEmitterData'
particleEngine.h(164) : see declaration of 'ParticleEmitterData'
particleEngine.cc(1184) : error C2039: 'orientBase' : is not a member of 'ParticleEmitterData'
** mose errors come from this piece of code that you have:
F32 scale = part.currentOwner->getDataBlock()->internalScale.len()/1.7;
if (part.currentOwner->mLinkObject && part.currentOwner->getDataBlock()->linkScale && !part.currentOwner->mLinkObject->getScale().isZero())
scale *= part.currentOwner->mLinkObject->getScale().len()/1.7;
**
internalScale.len()
linkScale
If you could post them that would be fantastic. You'd help me out big time.
Thank you
#47
//////////////////////////////////////////////////////////////
class ParticleEmitter : public GameBase{
...
public:
...
GameBase* mLinkObject;
S32 mPrintCount;
//////////////////////////////////////////////////////////////
class ParticleEmitterData : public GameBaseData
public
...
MatrixF internalTransform;
Point3F internalScale;
F32 orientBase;
bool linkPosition;
bool linkRotation;
bool linkScale;
bool isChain;
F32 VarianceSteps;
//////////////////////////////////////////////////////////////
struct particle{
....
MatrixF lastLinkObjTransform;
...
As soon as the game comes on (after loading objects and datablocks) the game crashes and there is nothing in the console for errors.......any ideas?
Any help would be greatly appreciated.
Thx
03/16/2007 (2:05 pm)
So I've managed to put this in to the particleEngine.h header and it compiled with no errors TGE 1.5 btw//////////////////////////////////////////////////////////////
class ParticleEmitter : public GameBase{
...
public:
...
GameBase* mLinkObject;
S32 mPrintCount;
//////////////////////////////////////////////////////////////
class ParticleEmitterData : public GameBaseData
public
...
MatrixF internalTransform;
Point3F internalScale;
F32 orientBase;
bool linkPosition;
bool linkRotation;
bool linkScale;
bool isChain;
F32 VarianceSteps;
//////////////////////////////////////////////////////////////
struct particle{
....
MatrixF lastLinkObjTransform;
...
As soon as the game comes on (after loading objects and datablocks) the game crashes and there is nothing in the console for errors.......any ideas?
Any help would be greatly appreciated.
Thx
#48
04/11/2008 (12:21 am)
Has anyone got this working with TGEA ? 
Torque Owner Kip
the main problem is i dont get any orientations from the mount point and if the object is animated or moves the particles dont follow it. Any help would be great!
Thanks!
Well im off to bed.. Ill check this tomorrow morning. Thanks again for being able to post this!