Mounting ParticleEmitterNodes
by Daniel Buckmaster · 05/27/2008 (9:45 am) · 59 comments
Developed with TGE 1.5.2.
I decided I'd like the ability to mount particle emitters to a node in a ShapeBase object after thinking about flamethrowers. I think you get the idea.
Anyway, I'm not going to waffle: here are the code changes.
In particleEmitter.h:
This is just infrastructure - making sure the particle code 'knows' about the ShapeBase code.
Find:
Next, we're going to declare some simple additions to the ParticleEmitterNode class. ParticleEmitterNodes are basically invisible point objects that emit particles. What we want to do is add two things: an object and a mount node. So why three fields? Script compatability. It's complicated.
And why the method? onDeleteNotify is called on objects when another object is deleted. Here, we use it to see whether the parent object of our ParticleEmitterNode is deleted. If so, we'll want to do something.
Find:
processTick and interpolateTick are methods that allow an object to do something over time. They are called periodically on all objects.
Find:
In particleEmitter.cc
Again, administration.
Find:
(Thanks to Pifford for pointing out that netConnection.h needs to be included.)
Now, those fields that we added to ParticleEmitterNode need to be initialised, so they don't unexpectedly contain junk, which could cause bad things to happen.
Find:
The initPersistFields method exposes a bunch of object fields to script. Note that only two of our three added fields are exposed to scripts.
Find:
This is where that third field comes in. We use the integer we were passed from script to find an actual object pointer. Then we just perform some common-sense checks to make sure our data makes sense.
Find:
This is the meat of the code. Here we declare the processTick, interpolateTick, and onDeleteNotify methods. Let's break them down:
processTick: every tick, if we're mounted to an object, we get the posiion and rotation of the node we're mounted to, and set our own position and rotation to be equal. I copied this code from StaticShape, which has simple mounting like this.
interpolateTick: this is called in the spaces between ticks. In this method, we do basically the same thing, but we use render transforms instead of regular ones. Don't ask me what the difference is.
onDeleteNotify: basically, if the object deleted was our mount object, we clear out the object from memory. Ideally, we should just delete the emitter. I'll add it to my to do list.
Find:
packUpdate and unPackUpdate deal with transferring data between client and server (if I'm not very much mistaken). In packUpdatek, we write all the relevant data to a stream. The stream is sent to the client, where it is unpacked to the client object.
Find:
Find:
That's it - it should build and compile. Here's an example of script usage - just put it in the console. I'm using sandard starter.fps art content here, just so you know.
Planned additions:
-Mounting using node names rather than mount numbers.
-Delete emitter when object is deleted.
I decided I'd like the ability to mount particle emitters to a node in a ShapeBase object after thinking about flamethrowers. I think you get the idea.
Anyway, I'm not going to waffle: here are the code changes.
In particleEmitter.h:
This is just infrastructure - making sure the particle code 'knows' about the ShapeBase code.
Find:
#ifndef _GAMEBASE_H_ #include "game/gameBase.h" #endifAnd add beneath it:
//Dan's mods (mounting particles) -> #ifndef _SHAPEBASE_H_ #include "game/shapeBase.h" #endif //<- Dan
Next, we're going to declare some simple additions to the ParticleEmitterNode class. ParticleEmitterNodes are basically invisible point objects that emit particles. What we want to do is add two things: an object and a mount node. So why three fields? Script compatability. It's complicated.
And why the method? onDeleteNotify is called on objects when another object is deleted. Here, we use it to see whether the parent object of our ParticleEmitterNode is deleted. If so, we'll want to do something.
Find:
ParticleEmitterNodeData* mDataBlock;And add beneath:
//Dan's mods (mounting particles) -> S32 mMountObject; ShapeBase* mMount; S32 mMountNode; void onDeleteNotify(SimObject*); //<- Dan
processTick and interpolateTick are methods that allow an object to do something over time. They are called periodically on all objects.
Find:
// Time/Move Management public:And add beneath:
//Dan's mods (mounting particles) -> void processTick(const Move* move); void interpolateTick(F32 dt); //<- Dan
In particleEmitter.cc
Again, administration.
Find:
#include "math/mathIO.h"And add beneath:
(Thanks to Pifford for pointing out that netConnection.h needs to be included.)
//Dan's mods (mounting particles) -> #include "game/shapeBase.h" #include "sim/netConnection.h" //<- Dan
Now, those fields that we added to ParticleEmitterNode need to be initialised, so they don't unexpectedly contain junk, which could cause bad things to happen.
Find:
mEmitterDatablock = NULL; mEmitterDatablockId = 0; mEmitter = NULL; mVelocity = 1.0;And add beneath:
//Dan's mods (mounting particles) -> mMountObject = -1; mMount = NULL; mMountNode = -1; //<- Dan
The initPersistFields method exposes a bunch of object fields to script. Note that only two of our three added fields are exposed to scripts.
Find:
addField("emitter", TypeParticleEmitterDataPtr, Offset(mEmitterDatablock, ParticleEmitterNode));
addField("velocity", TypeF32, Offset(mVelocity, ParticleEmitterNode));And add beneath://Dan's mods (particle mounting) ->
addField("mountObject",TypeS32,Offset(mMountObject,ParticleEmitterNode));
addField("mountNode",TypeS32,Offset(mMountNode,ParticleEmitterNode));
//<- DanThis is where that third field comes in. We use the integer we were passed from script to find an actual object pointer. Then we just perform some common-sense checks to make sure our data makes sense.
Find:
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;
}And add beneath://Dan's mods (mounting particles) ->
//If we're a server object, resolve mount object
else
{
ShapeBase* ptr;
mMount = NULL;
if(mMountObject != -1)
if(Sim::findObject(mMountObject,ptr))
{
mMount = ptr;
deleteNotify(ptr);
//And while we're at it, check mount
if(mMountNode != -1)
if(mMountNode > ShapeBaseData::NumMountPoints)
{
Con::errorf("ParticleNodeEmitter::onAdd: mountNode too big!");
mMountNode = -1;
}
}
}
//<- DanThis is the meat of the code. Here we declare the processTick, interpolateTick, and onDeleteNotify methods. Let's break them down:
processTick: every tick, if we're mounted to an object, we get the posiion and rotation of the node we're mounted to, and set our own position and rotation to be equal. I copied this code from StaticShape, which has simple mounting like this.
interpolateTick: this is called in the spaces between ticks. In this method, we do basically the same thing, but we use render transforms instead of regular ones. Don't ask me what the difference is.
onDeleteNotify: basically, if the object deleted was our mount object, we clear out the object from memory. Ideally, we should just delete the emitter. I'll add it to my to do list.
Find:
bool ParticleEmitterNode::onNewDataBlock(GameBaseData* dptr)
{
mDataBlock = dynamic_cast<ParticleEmitterNodeData*>(dptr);
if (!mDataBlock || !Parent::onNewDataBlock(dptr))
return false;
// Todo: Uncomment if this is a "leaf" class
scriptOnNewDataBlock();
return true;
}
//--------------------------------------------------------------------------And add beneath://Dan's mods (mounting particles) ->
void ParticleEmitterNode::processTick(const Move* move)
{
//If we have a mount object
if(mMount)
{
MatrixF mat;
//If we have a valid mount
if(mMountNode != -1)
mMount->getMountTransform(mMountNode,&mat);
//Otherwise, use centre
else
mat = mMount->getTransform();
//Set transforms
Parent::setTransform(mat);
Parent::setRenderTransform(mat);
}
}
void ParticleEmitterNode::interpolateTick(F32 dt)
{
//If we have a mount object
if(mMount)
{
MatrixF mat;
//If we have a valid mount
if(mMountNode != -1)
mMount->getRenderMountTransform(mMountNode,&mat);
//Otherwise, use centre
else
mat = mMount->getRenderTransform();
//Set render transform
Parent::setRenderTransform(mat);
}
}
void ParticleEmitterNode::onDeleteNotify(SimObject* obj)
{
Parent::onDeleteNotify(obj);
if(obj == (ShapeBase*)mMount)
{
mMount = NULL;
mMountObject = -1;
Con::executef(mDataBlock, 2, "onMountDeleted", scriptThis());
deleteObject();
}
}
//<- Dan(Thanks to Pifford and Guimo for suggesting I just delete the object ;) and to Guimo for suggesting a script calback.)packUpdate and unPackUpdate deal with transferring data between client and server (if I'm not very much mistaken). In packUpdatek, we write all the relevant data to a stream. The stream is sent to the client, where it is unpacked to the client object.
Find:
U32 retMask = Parent::packUpdate(con, mask, stream);And add beneath:
//Dan's mods (mounting particles) ->
if(stream->writeFlag(mask & InitialUpdateMask))
{
if(stream->writeFlag(mMountObject != -1))
{
S32 gIndex = con->getGhostIndex(mMount);
if(stream->writeFlag(gIndex != -1))
{
stream->writeInt(gIndex,NetConnection::GhostIdBitSize);
stream->writeInt(mMountNode,ShapeBaseData::NumMountPointBits);
}
}
}
//<- DanFind:
Parent::unpackUpdate(con, stream);And add beneath:
//Dan's mods (mounting particles) ->
if(stream->readFlag())
{
if(stream->readFlag())
{
if(stream->readFlag())
{
S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
mMount = dynamic_cast<ShapeBase*>(con->resolveGhost(gIndex));
mMountNode = stream->readInt(ShapeBaseData::NumMountPointBits);
}
}
}
//<- DanThat's it - it should build and compile. Here's an example of script usage - just put it in the console. I'm using sandard starter.fps art content here, just so you know.
new ParticleEmitterNode(){
dataBlock = SteamEmitterNode;
emitter = SteamEmitter;
mountObject = LocalClientConnection.player;
mountNode = 0;
};If you don't specify a node, or if the node is for some reason unavailable, the emitter is mounted at the object's position.Planned additions:
-Mounting using node names rather than mount numbers.
-Delete emitter when object is deleted.
About the author
Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!
#2
05/27/2008 (5:31 pm)
Cool! Gotta try this!
#3
06/07/2008 (12:26 pm)
Added some explanation to the code changes. Nothing too complicated.
#4
06/13/2008 (9:55 am)
Any luck with this and TGEA?
#5
06/17/2008 (11:34 am)
I have no idea how different TGEA is to TGE; I work with TGE. I should note that in the resource. However, the code is pretty high-level stuff, relatively, so TGE and TGEA shouldn't differ too much in this respect.
#6
and in the console you can use a small callback script:
Awesome resource.
Luck!
Guimo
07/27/2008 (5:43 am)
Just a suggestion. To delete this node when parent is deleted, just invoke a callback on the objecct like:void ParticleEmitterNode::onDeleteNotify(SimObject* obj) {
if(obj == (ShapeBase*)mMount) {
mMount = NULL;
mMountObject = -1;
Con::executef(this, 1, "onMountDeleted");
}
Parent::onDeleteNotify(obj);
}and in the console you can use a small callback script:
function ParticleEmitterNode::onMountDeleted(%this) {
%this.delete();
}Awesome resource.
Luck!
Guimo
#7
To delete the emitter on mounted object deletion just add:
deleteObject();
after:
mMountObject = -1;
in ParticleEmitterNode::onDeleteNotify()
-r
08/11/2008 (12:52 am)
I commented by I don't think it went through...To delete the emitter on mounted object deletion just add:
deleteObject();
after:
mMountObject = -1;
in ParticleEmitterNode::onDeleteNotify()
-r
#8
#include "sim/netConnection.h"
to particleEmitter.cc
08/11/2008 (2:10 am)
Also, to compile with TGE 1.5.2 and GCC4 it's necessary to add:#include "sim/netConnection.h"
to particleEmitter.cc
#9
Guimo - a script callback is an interesting idea, but I reckon the C++ should handle object deleting. I'll add in a callback, though, just for completeness ;P. On the datablock, of course :)
I'm not sure about the best practice for deleting objects - the parent function should be called last, but also the object should be deleted last! I've decided to play it a bit safe (and rely on the fact that nothing further up the line defines anything useful in onDeleteNotified :P).
08/20/2008 (9:31 am)
Thanks for that guys! I don't really know why I couldn't be bothered adding the deleteObject();... :P. I'll add that now.Guimo - a script callback is an interesting idea, but I reckon the C++ should handle object deleting. I'll add in a callback, though, just for completeness ;P. On the datablock, of course :)
I'm not sure about the best practice for deleting objects - the parent function should be called last, but also the object should be deleted last! I've decided to play it a bit safe (and rely on the fact that nothing further up the line defines anything useful in onDeleteNotified :P).
#10
One problem I have however, is that when running the flames from my flamethrower blow back into my face. I thought inheritedVelFactor would correct for this, but it doesn't appear to be working. If the emitter is mounted to a player, do I need to use the player's velocity in the calculation instead of the emitter? Any idea how I might do that? Thanks!
09/11/2008 (12:22 am)
I have made good use of this resource. Thank you for posting it. One problem I have however, is that when running the flames from my flamethrower blow back into my face. I thought inheritedVelFactor would correct for this, but it doesn't appear to be working. If the emitter is mounted to a player, do I need to use the player's velocity in the calculation instead of the emitter? Any idea how I might do that? Thanks!
#11
09/11/2008 (8:17 am)
What I would do (I would do it now if I didn't have homework :P) is find all references to the velocity of a particle emitter. I would guess that when I set the transform of an emitter, the velocity is not altered as it should be. You can verify that, but it seems to be the case. So you'd simply have to change the particle emitter's velocity in processTick.
#12
The change I made was :
09/11/2008 (9:00 am)
Yeah that seemed to do the trick. The particles now correctly inherit the emitter's velocity. This almost seems like a bug with the engine because it didn't technically have anything to do with your code :PThe change I made was :
void ParticleEmitterNode::advanceTime(F32 dt)
{
Parent::advanceTime(dt);
Point3F emitPoint, emitVelocity;
Point3F emitAxis(0, 0, 1);
getTransform().mulV(emitAxis);
getTransform().getColumn(3, &emitPoint);
if(mMount != 0) // added this check to support inheriting the velocity of the mounted object
{
emitVelocity = ((emitAxis + mMount->getVelocity()) * mVelocity );
}
else
{
emitVelocity = emitAxis * mVelocity;
}
mEmitter->emitParticles(emitPoint, emitPoint,
emitAxis,
emitVelocity, (U32)(dt * mDataBlock->timeMultiple * 1000.0f));
}
#13
09/11/2008 (10:27 am)
Thanks for posting that up :) This is quite an important change - do I have your permission to add it to the resource? With credit, of course ;)
#14
works perfectly in 1.7.1 (with minor changes)
funny seeing the player running around with smoke pouring out of him
12/20/2008 (12:46 pm)
nice resource!!works perfectly in 1.7.1 (with minor changes)
funny seeing the player running around with smoke pouring out of him
#15
1. Instead of using 'particleEmitter.h' and 'particleEmitter.cc', I had to use 'particleEmitterNode.h' and 'particleEmitterNode.cpp', otherwise I was unable to find most of the code mentioned in the guide.
2. This line in both files: #include "game/shapeBase.h"
Needs to be: #include "T3D/shapeBase.h"
I tried to run a build and am getting one error causing a couple other errors. I am not too savvy with C++/C# yet so I don't know how to fix them. Here's the errors:
06/24/2009 (9:55 am)
I am trying to port this over to TGEA 1.8.1. I had to make a couple changes.1. Instead of using 'particleEmitter.h' and 'particleEmitter.cc', I had to use 'particleEmitterNode.h' and 'particleEmitterNode.cpp', otherwise I was unable to find most of the code mentioned in the guide.
2. This line in both files: #include "game/shapeBase.h"
Needs to be: #include "T3D/shapeBase.h"
I tried to run a build and am getting one error causing a couple other errors. I am not too savvy with C++/C# yet so I don't know how to fix them. Here's the errors:
particleEmitterNode.cpp
..\..\..\..\..\engine\source\T3D\fx\particleEmitterNode.cpp(259) : error C2665: 'Con::executef' : none of the 20 overloads could convert all the argument types
c:\Torque\TGEA_1_8_1\engine\source\console/console.h(536): could be 'const char *Con::executef(const char *,const char *,const char *,const char *)'
c:\Torque\TGEA_1_8_1\engine\source\console/console.h(564): or 'const char *Con::executef(SimObject *,const char *,const char *,const char *)'
while trying to match the argument list '(ParticleEmitterNodeData *, int, const char [15], const char *)'
#16
07/03/2009 (9:03 am)
BUMP!!! REALLY need help with this!
#17
Picasso's solution: www.garagegames.com/community/forums/viewthread/96147/1#comment-646301
However, now my game is crashing when I use this...
"TP1" is the name of the object I want my emitter mounted to. It is a static shape and I made sure to export it with a 'mount0' node.
07/03/2009 (10:53 am)
Ok, I got help from Picasso in the TGEA Engine boards.Picasso's solution: www.garagegames.com/community/forums/viewthread/96147/1#comment-646301
However, now my game is crashing when I use this...
new ParticleEmitterNode(TelePiece1) {
canSaveDynamicFields = "1";
Enabled = "1";
position = "453.774 1019.04 2.19296";
rotation = "0.498257 -0.278039 0.821239 1.19352";
scale = "1 1 1";
dataBlock = "EmberNode";
emitter = "TelePieceEmitter";
velocity = "1";
mountObject = "TP1";
mountNode = 0;
};"TP1" is the name of the object I want my emitter mounted to. It is a static shape and I made sure to export it with a 'mount0' node.
#18
07/04/2009 (4:54 am)
I think the problem you're seeing now is that you're passing a name to mountObject, but I designed it for use with an ID number. Try getting the object's ID number and using that instead. That, or I could add another field for a mountObjectName.
#19
07/04/2009 (4:05 pm)
This looks like a great resource. Can this also be used to attach emitters to nodes defined in a weapon image as well? It seems like just looking at the code it should work but I came across this post, which references your resource. Maybe there is a problem with Sim::findObject when dealing with images? I thought I would ask before trying to implement it.
#20
But either way, the method the poster used was incorrect. See my reply for details ;P.
I should really update this resource, I've just realised :P.
07/04/2009 (5:20 pm)
Hm, I never saw that thread. I've replied to it now. Basically, I didn't design the resource with that function in mind, since I believe images already have built-in particle effects (do they?).But either way, the method the poster used was incorrect. See my reply for details ;P.
I should really update this resource, I've just realised :P.

Torque 3D Owner Rubes