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!
#42
BTW - The error message I'm getting on the console is:
"Error: shape starter.fps/data/shapes/crossbow/ammo.dts-collision detail 1 (Collision-3) bounds box invalid!"
I should also note that I've gone in and manually deleted the player.cs.dso file to make sure I was getting a fresh compile and nothing. I'm totally lost as to what to try next.
08/24/2009 (3:08 am)
What am I doing wrong? I copy and pasted the lines of code above so I know that they're correct and the SDK compiles without any problems at all. I've also gone into the FPS folder and found the player.cs file and added the code above to actually add the emitter in the Armor::OnAdd function and I've had no luck. I'm not getting any errors in the console except for one regarding the collision box for the crossbow. (I'm not exactly sure how that happened since I haven't touched anything in that file). I even thought that perhaps I have to add the particle data info directly into the player.cs file but that didn't seem to work either. I get no errors, everything runs super smooth but no particles are generated. Has anyone come across this problem that can give this lowly noob some tips? They'd be greatly appreciated. Like... you can have my first born! haha!BTW - The error message I'm getting on the console is:
"Error: shape starter.fps/data/shapes/crossbow/ammo.dts-collision detail 1 (Collision-3) bounds box invalid!"
I should also note that I've gone in and manually deleted the player.cs.dso file to make sure I was getting a fresh compile and nothing. I'm totally lost as to what to try next.
#43
08/24/2009 (5:16 am)
Try putting the example script at the bottom straight into the console to see if it works that way. I honestly don't know what could be wrong if you're getting no errors (the ammo.dts error has always been there, no fault of yours :P).
#44
Maybe I'm just missing a step. Let me go through all the steps I went to to see if I'm just missing something.
First I opened up the Torque SDK using Visual C++ 2008 express edition. I found particleEmitter.h and particleEmitter.cc and made all the changes as described in your blog. To make sure I did this part right, I went through it again and copied directly from the blog into the .h and .cc files. I used the "view Plain" link to make sure that I didn't make any formatting errors. (I originally figured that the compiler would catch any such mistakes but decided to copy and paste just to be safe).
Once I made all the changes to the .h and .cc files I compiled the code. I did so by using both the Build Solution and rebuild solution in the Build menu.
From this point I then opened up player.cs and added the code to the Armor::OnAdd function. In the process of figuring out what I'm doing wrong I've done several things to the player.cs file. First I put in the code as described except for replacing Local.Connection.player with %obj. When this did not work I tried the original code. It didn't work so then I thought, "hrm, maybe I need to define the particle datablocks in player.cs I couldn't find exactly where in code SteamEmitter was located so I instead copied all the datablocks associated with torchfire from Chimney.cs directly into player.cs. No dice. I tried a couple of other just to keep testing. When neither of these solutions helped I removed the extra particle datablocks from player.cs.
To test each attempt I navigated to my torque folder and started the FPS game using the starter.fps.bat file as well as the shortcut via the start menue.
Inside the game I also tried both copy and pasting both versions of the console code (both the %obj and .player variants) directly into the console. No error occured when I hit enter but nothing happend. I then thought, "Maybe the carriage returns are throwing it off or something" so I tried manually typing the code in seperating using commas.
There's gotta be something I'm missing. I'm using Torque 1.5.2.
I'm not sure how to add links to the blog otherwise I'd post my particleEmitter.h, particleEmitter.cc and player.cs to this post.
Any insight you could provide would be greatly appreciated :)
08/25/2009 (12:48 am)
Thank you for your quick response! :DMaybe I'm just missing a step. Let me go through all the steps I went to to see if I'm just missing something.
First I opened up the Torque SDK using Visual C++ 2008 express edition. I found particleEmitter.h and particleEmitter.cc and made all the changes as described in your blog. To make sure I did this part right, I went through it again and copied directly from the blog into the .h and .cc files. I used the "view Plain" link to make sure that I didn't make any formatting errors. (I originally figured that the compiler would catch any such mistakes but decided to copy and paste just to be safe).
Once I made all the changes to the .h and .cc files I compiled the code. I did so by using both the Build Solution and rebuild solution in the Build menu.
From this point I then opened up player.cs and added the code to the Armor::OnAdd function. In the process of figuring out what I'm doing wrong I've done several things to the player.cs file. First I put in the code as described except for replacing Local.Connection.player with %obj. When this did not work I tried the original code. It didn't work so then I thought, "hrm, maybe I need to define the particle datablocks in player.cs I couldn't find exactly where in code SteamEmitter was located so I instead copied all the datablocks associated with torchfire from Chimney.cs directly into player.cs. No dice. I tried a couple of other just to keep testing. When neither of these solutions helped I removed the extra particle datablocks from player.cs.
To test each attempt I navigated to my torque folder and started the FPS game using the starter.fps.bat file as well as the shortcut via the start menue.
Inside the game I also tried both copy and pasting both versions of the console code (both the %obj and .player variants) directly into the console. No error occured when I hit enter but nothing happend. I then thought, "Maybe the carriage returns are throwing it off or something" so I tried manually typing the code in seperating using commas.
There's gotta be something I'm missing. I'm using Torque 1.5.2.
I'm not sure how to add links to the blog otherwise I'd post my particleEmitter.h, particleEmitter.cc and player.cs to this post.
Any insight you could provide would be greatly appreciated :)
#45
I think I'll implement this on my own clean build and send you the files. I'd reccommend using WinMerge (or something similar) to check for differences between mine and yours, if mine work for you.
08/25/2009 (9:12 pm)
Sounds like you're doing everything right. Did you compile in release mode, or one of the debug modes?Quote:Inside the game I also tried both copy and pasting both versions of the console code (both the %obj and .player variants) directly into the console. No error occured when I hit enter but nothing happend.That strikes me as a little odd - if you used %obj in the console, it wouldn't have been defined as anything, so you should have gotten some sort of error, I think.
I think I'll implement this on my own clean build and send you the files. I'd reccommend using WinMerge (or something similar) to check for differences between mine and yours, if mine work for you.
#46
Thank you so much for your help. This really is an excellent mod. Thanks again for all your help! :)
08/26/2009 (2:05 am)
WoW thanks! I knew it had to be some silly newbie mistake I was making! You got it exactly right. I had been compiling in debug mode. Once I fixed that it worked like a charm!Thank you so much for your help. This really is an excellent mod. Thanks again for all your help! :)
#47
I'd reccommend building in debug most of the time - just remember to run the torqueDemo_DEBUG instead of the usual torqueDemo. If you've made code changes that give you a crash, the debug build will allow you to catch it more easily (only if you're running it within VC++, I think) and reduce the severity of that happening.
08/26/2009 (3:56 am)
No worries, I'm just glad that was the problem! :DI'd reccommend building in debug most of the time - just remember to run the torqueDemo_DEBUG instead of the usual torqueDemo. If you've made code changes that give you a crash, the debug build will allow you to catch it more easily (only if you're running it within VC++, I think) and reduce the severity of that happening.
#48
I've implemented this into T3D,
and all is good, but,
I would like to attach the emitter to a regular shapeBase object, not a player, in the onAdd function.
when spawned, the object displays the effect, but on moving the object, the emitter does not move with it.
any ideas?
09/20/2009 (6:48 pm)
hi Daniel,I've implemented this into T3D,
and all is good, but,
I would like to attach the emitter to a regular shapeBase object, not a player, in the onAdd function.
when spawned, the object displays the effect, but on moving the object, the emitter does not move with it.
any ideas?
#49
I don't have the resource in my code base at the moment, but I'll go ahead and add it to see whether I can repeat the bug in 1.5.2.
09/21/2009 (12:16 am)
Hmm. You mean moving the object in the editor, correct? I'm not sure what could be causing that. Try hitting 'apply' in the editor or something similar. Maybe this is a T3D issue.I don't have the resource in my code base at the moment, but I'll go ahead and add it to see whether I can repeat the bug in 1.5.2.
#50
09/21/2009 (5:35 am)
could be a T3D issue, yes.
#51
09/22/2009 (4:00 am)
Okay, I've implemented it in TGE 1.5.2 and I can mount a particle emitter to a static shape (note: not a TSStatic), move it around in the editor, and the particles will follow.
#52
Just to make sure it can do what we want, we have this AI creature that we wanted to be sheathed in flames, the idea was to mount emitters at key points around the creature. Will this resource allow us to do this?
Thanks
11/09/2009 (4:09 pm)
Wow, this looks awesome! This is something my buddies and I would love to use in our project right now.Just to make sure it can do what we want, we have this AI creature that we wanted to be sheathed in flames, the idea was to mount emitters at key points around the creature. Will this resource allow us to do this?
Thanks
#53
Disclaimer: I'm not using this code in my current build, so I'm not sure what issues there might be ;P. The changes are pretty simple, though, so there shouldn't be any problems.
11/09/2009 (10:56 pm)
That was the intent of the resource, and as far as I know you should be good to go. Just make sure that all those 'key points' have nodes named 'mountX' (where X is an integer, of course) for the particle emitters to attach to.Disclaimer: I'm not using this code in my current build, so I'm not sure what issues there might be ;P. The changes are pretty simple, though, so there shouldn't be any problems.
#54
One thing I would like to ask is about changing code in the engine. I have never done anything within the engine before, I've only done scripting with Torque. What is the process of doing something with the engine? Should I back up the original before doing anything? Will something weird happen should it mess up and I delete the changed version and bring back the original? Is there anything I should expect and not be freaked out by?
Thanks,
Andy Johnson
11/09/2009 (11:40 pm)
Sweet thanks.One thing I would like to ask is about changing code in the engine. I have never done anything within the engine before, I've only done scripting with Torque. What is the process of doing something with the engine? Should I back up the original before doing anything? Will something weird happen should it mess up and I delete the changed version and bring back the original? Is there anything I should expect and not be freaked out by?
Thanks,
Andy Johnson
#55
You can also compile in a debug mode, which will create a torqueDemo_DEBUG.exe, which is just useful for finding out what the problem is with code that doesn't work.
You should definitely back up you files - or even better, see if you can get version control up and running. It will allow you to, in effect, store 'snapshots' of your files and roll back to the previous versions if you mess something up. I'm using TortoiseSVN very happily for this.
Aside from that, if you're used to TS, then stepping up to C++ shouldn't be too difficult. Just be aware that it's a lot less forgiving - C++ is case-sensitive, unlike TS, and if you do something a little wrong you can make your game crash hard. But you get used to debugging your code and seeing what's going wrong. It can be frustrating, but the reward is well worth it.
11/16/2009 (3:47 am)
There are probably tutorials out there better than what I can tell you, but you'll need an IDE like the free Microsoft Visual C++ express. Then it's a matter of changing the code files, then recompiling the engine so your changes are rolled into the torqueDemo.exe.You can also compile in a debug mode, which will create a torqueDemo_DEBUG.exe, which is just useful for finding out what the problem is with code that doesn't work.
You should definitely back up you files - or even better, see if you can get version control up and running. It will allow you to, in effect, store 'snapshots' of your files and roll back to the previous versions if you mess something up. I'm using TortoiseSVN very happily for this.
Aside from that, if you're used to TS, then stepping up to C++ shouldn't be too difficult. Just be aware that it's a lot less forgiving - C++ is case-sensitive, unlike TS, and if you do something a little wrong you can make your game crash hard. But you get used to debugging your code and seeing what's going wrong. It can be frustrating, but the reward is well worth it.
#56
I have this implemented and fully working in T3D with no issues at all. Completely awesome!
03/08/2010 (2:39 pm)
EDIT:I have this implemented and fully working in T3D with no issues at all. Completely awesome!
#57
But it sounds like you figured that out ;).
03/08/2010 (7:52 pm)
To respond to the question that I received in my inbox - this code refers to ShapeBase, so it works for Players, Items, Vehicles, RigidShapes, and Cameras, oddly enough :P. Maybe some other classes I've forgotten.But it sounds like you figured that out ;).
#58
09/01/2014 (10:54 pm)
Anyone able to get this to work in T3D MIT release ? 
Torque Owner Daniel Buckmaster
T3D Steering Committee
Glad you like the resource!