Mounted particles
by Peter Simard · 08/17/2006 (12:17 pm) · 30 comments
SHAPEBASE.H MODIFICATIONS
Add to the top of the file:
Above the ShapeBase class
Add this mask to your ShapeBaseMasks. Be sure to change the number to what you next avalible mask is
Add these lines inside of your ShapeBase private block
SHAPEBASE.CC MODIFICATIONS
Add this to the end of void ShapeBase::advanceTime(F32 dt)
Modify your packUpdate methods list of masks
Insert this block at the end of the packUpdate method
Insert this code block to the end of the unpackUpdate method
Insert the rest of the code at the end of the file
USAGE
Now you need to create a particle emitter node, and then mount it to your player or object. You will need to schedule the mounting of the particle for a second or two after you create it to give it time to be ghosted to the clients.
Add to the top of the file:
#include "game/fx/particleEmitter.h" class ParticleEmitter;
Above the ShapeBase class
class mountedParticle
{
public:
U32 nodeNumber;
ParticleEmitterNode* particleEmitter;
};Add this mask to your ShapeBaseMasks. Be sure to change the number to what you next avalible mask is
mountedParticleMask = Parent::NextFreeMask << 6,
Add these lines inside of your ShapeBase private block
Vector<mountedParticle*> mountedEmitters; void updateMountedParticles(F32 dt); void mountParticleEmitter(ParticleEmitterNode* particleEmitter, const char* slotName); void clearMountedEmitters(); void getRenderEquipmentTransform(U32 mountPoint,MatrixF* mat);
SHAPEBASE.CC MODIFICATIONS
Add this to the end of void ShapeBase::advanceTime(F32 dt)
updateMountedParticles(dt);
Modify your packUpdate methods list of masks
//Modify the mask list to add the particle mask
if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask |
ThreadMask | ImageMask | CloakMask | MountedMask | [b]mountedParticleMask[/b]
)))Insert this block at the end of the packUpdate method
if (stream->writeFlag(mask & mountedParticleMask)) {
stream->writeInt(mountedEmitters.size(), 8);
for(int q = 0; q < mountedEmitters.size(); q++)
{
mountedParticle* eqp = mountedEmitters[q];
U32 ghostIndex = con->getGhostIndex(eqp->particleEmitter);
if(ghostIndex > NetConnection::MaxGhostCount)
{
// Prevent a crash if its not ghosted yet
stream->writeRangedU32(-1, 0, NetConnection::MaxGhostCount);
stream->write(-1);
}
else
{
stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount);
stream->write(eqp->nodeNumber);
}
}
}Insert this code block to the end of the unpackUpdate method
if (stream->readFlag()) {
clearMountedEmitters();
U32 emitterCount = stream->readInt(8);
for(int i = 0; i < emitterCount; i++)
{
U32 emitterGhostId = stream->readRangedU32(0, NetConnection::MaxGhostCount);
S32 nodeNumber;
stream->read(&nodeNumber);
if(nodeNumber == -1)
continue;
NetObject* obj = con->resolveGhost(emitterGhostId);
if(obj)
{
ParticleEmitterNode* emitter = dynamic_cast<ParticleEmitterNode*>(obj);
if(emitter)
{
mountedParticle* mp = new mountedParticle;
mp->nodeNumber = nodeNumber;
mp->particleEmitter = emitter;
mountedEmitters.push_back(mp);
}
}
}
}Insert the rest of the code at the end of the file
void ShapeBase::clearMountedEmitters()
{
for(int x=0; x < mountedEmitters.size(); x++)
{
delete mountedEmitters[x];
}
mountedEmitters.clear();
}
void ShapeBase::mountParticleEmitter(ParticleEmitterNode* particleEmitter, const char* slotName)
{
if(!particleEmitter)
return;
U32 mountNode = mDataBlock->shape->findNode(slotName);
if(mountNode < 0)
{
// If no mountNode avalible
Con::errorf("Unable to locate node: \"%s\" while attempting to mount to %s", slotName, particleEmitter->getName());
return;
}
mountedParticle* mp = new mountedParticle;
mp->nodeNumber = mountNode;
mp->particleEmitter = particleEmitter;
mountedEmitters.push_back(mp);
setMaskBits(mountedParticleMask);
}
void ShapeBase::updateMountedParticles(F32 dt)
{
for(int x=0; x < mountedEmitters.size(); x++)
{
MatrixF mat;
mat.identity();
mountedParticle* mp = mountedEmitters[x];
ParticleEmitterNode* emitter = mp->particleEmitter;
getRenderEquipmentTransform(mp->nodeNumber, &mat);
emitter->setTransform(mat);
}
}
ConsoleMethod( ShapeBase, mountParticleEmitter, void, 4, 4, "(ParticleEmitterNode node, const char* slot)")
{
ParticleEmitterNode* particleNode;
if (!Sim::findObject(argv[2], particleNode)) {
Con::errorf("Unable to mount particle to node: \"%s\"", argv[2]);
return;
}
object->mountParticleEmitter(particleNode, argv[3]);
}
// Returns mount point to world space transform
void ShapeBase::getRenderEquipmentTransform(U32 mountPoint,MatrixF* mat)
{
MatrixF mountTransform = mShapeInstance->mNodeTransforms[mountPoint];
const Point3F& scale = getScale();
// The position of the mount point needs to be scaled.
Point3F position = mountTransform.getPosition();
position.convolve( scale );
mountTransform.setPosition( position );
// Also we would like the object to be scaled to the model.
mountTransform.scale( scale );
mat->mul(getRenderTransform(), mountTransform);
return;
}USAGE
Now you need to create a particle emitter node, and then mount it to your player or object. You will need to schedule the mounting of the particle for a second or two after you create it to give it time to be ghosted to the clients.
%emitter = new ParticleEmitterNode()
{
datablock = defaultParticleEmitterNode;
emitter = myEmitterData;
};
%player.schedule(1000, mountParticleEmitter, %emitter, "mount0");
#2
07/13/2006 (11:10 am)
Why do you have to schedule it with a delay based on ghosting? Will it not work if you don't put the delay in? Does it keep the emitter from being ghosted if you mount it first or something?
#3
@Paul - When an emitter is mounted, it sends out the GhostIndex of the emitter to all the clients. If you mount the emitter before it has been ghosted, the server wont know what the clients ghostIndex is. Im sure there is a better way to do this, and Id love if someone posted a better solution.
07/13/2006 (11:15 am)
@Stefan - Thanks!@Paul - When an emitter is mounted, it sends out the GhostIndex of the emitter to all the clients. If you mount the emitter before it has been ghosted, the server wont know what the clients ghostIndex is. Im sure there is a better way to do this, and Id love if someone posted a better solution.
#4
07/13/2006 (12:16 pm)
I just worry because ghosting time is subjective, so there's no reliable way to set it with a delay. At least you seem to have added a check to keep it from crashing. If you can check if the ghost is there or not, wouldn't it be possible to keep trying until it succeeds? It seems this doesn't bother to keep any syncrocity once it's been initially ghosted (which is totally optimal for a particle effect I'd say), but it probably wouldn't be to harmful to have it attempt to until it reaches an initial success then.
#5
07/13/2006 (7:25 pm)
Very impressive job.
#6
Can you tell exactly where you have put the code (with a clean 1.4 as example).
or maybe you have forgot to post some bits of code?
i have added "#include particleEmitter.h" too, but its still 1 error, "mountedParticle* eqp = mountedEmitters;" - cant be converted.
07/14/2006 (1:04 am)
I cant get it compiled with VS2005.Can you tell exactly where you have put the code (with a clean 1.4 as example).
or maybe you have forgot to post some bits of code?
i have added "#include particleEmitter.h" too, but its still 1 error, "mountedParticle* eqp = mountedEmitters;" - cant be converted.
#7
To: mountedParticle* eqp = mountedEmitters[i];
That gets rid of the "can't convert" error.
07/16/2006 (2:10 pm)
Change: mountedParticle* eqp = mountedEmitters;To: mountedParticle* eqp = mountedEmitters[i];
That gets rid of the "can't convert" error.
#8
07/16/2006 (2:24 pm)
Thanks for the fix Tim! It turns out the form ate the [ i ] tag thinking I wanted italics in the code. I changed the variable to q.
#9
08/20/2006 (9:38 pm)
hey, really good resource i am wondering how to get it to add a particle emitter on the spawn of an object, i have tried in ::onAdd but i dont think it is a shapebase member, the only way i can get them to add is on ::onFire and ::onCollision, any help would be appreciated.
#10
I fear im not knowing how to follow the directions on implementing the code. No other people mention similar error, perhaps it will not work with TLK?
08/22/2006 (2:28 am)
All I can get is '..\engine\game\shapeBase.cc(4269) : error C2248: 'ShapeBase::mountParticleEmitter' : cannot access private member declared in class 'ShapeBase'..'I fear im not knowing how to follow the directions on implementing the code. No other people mention similar error, perhaps it will not work with TLK?
#11
@Caylo: Move the mountParticleEmitter definition inside the .h file from the private block to the public block. That should fix that error.
08/22/2006 (10:30 am)
@Andrew: You should be able to add an emitter in the objects onAdd method. Make sure all the variables you are using are correct.@Caylo: Move the mountParticleEmitter definition inside the .h file from the private block to the public block. That should fix that error.
#12
08/22/2006 (3:52 pm)
After i move the 'mountParticleEmitter definition' to public, it indeed compile clean, but the EXE halts at the end of 'Loading Objects' message. I have started over multiple times, using clean new SHAPEBASE.h SHAPEBASE.c files, in the case chance that i may have done something wrong. Im very sure im following instructions as they state. This modification is the only one i have done to my SHAPEBASE.C/.H files- so i dont expect some other bit of code could be the blame.
#13
08/23/2006 (2:32 pm)
I enter some ' Con::errorf ' calls into the 'if (stream->readFlag())' and it seems to be stopping at the 'for(int i = 0; i < emitterCount; i++)' section. Im not very good with 'C' so i can not truly tell what the problem is. I have made 100% sure that the code i enter into my shapeBase files is exactly match the code in this article... I would be overjoyed to be able to mount particles using this system.....
#14
08/30/2006 (7:01 am)
@ Caylo - hmmm... maybe you placed the packupdate portion of the code after the 'return retMask' ? I mean if you return in the method before writting what needs to be written I can see where you would have a problem reading :)
#15
08/31/2006 (5:51 am)
I finally got it to work, yet i still dont know why it was NOT working. After many hours of fiddling with this, and actually LEARNING something, I ended up using Mountimage Replacement for Equipment first as a guide when editing IN the mount particle code. Thank you, Peter Simard for a brilliant solution to this common problem.
#16
09/03/2006 (10:34 am)
I can not figure out how to REMOVE the mounted Particle.
#17
ConsoleMethod( ShapeBase, unmountParticleEmitters, void, 2, 2, "")
{
object->clearMountedEmitters();
}
I have no compile errors, but it still not work -.-
And a simple delete() to the particle effect crash torcue.
Any idea about it?
10/11/2006 (1:38 am)
I have added a new console method:ConsoleMethod( ShapeBase, unmountParticleEmitters, void, 2, 2, "")
{
object->clearMountedEmitters();
}
I have no compile errors, but it still not work -.-
And a simple delete() to the particle effect crash torcue.
Any idea about it?
#18
EDIT:This time I changed my 'unhook' function so no longer do one need to NAME the node and the emitter they wish to unhook. I was getting a crash if i were to move the emitter mount node to a new mount node- then try and unhook it, this improved bit of code is the solution.
EDIT: Updated once more, now with 'Blue Crystals', and fewer bugs.
This ConsoleMethod will 'UNhookEmitters', dropping them into the world to be deleted by script.
Then in shapbase.h search for
To use it,
10/12/2006 (11:09 pm)
EDIT: This is a improved replacement for the old 'hack' i had post here.EDIT:This time I changed my 'unhook' function so no longer do one need to NAME the node and the emitter they wish to unhook. I was getting a crash if i were to move the emitter mount node to a new mount node- then try and unhook it, this improved bit of code is the solution.
EDIT: Updated once more, now with 'Blue Crystals', and fewer bugs.
This ConsoleMethod will 'UNhookEmitters', dropping them into the world to be deleted by script.
void ShapeBase::UNhookEmitters(ParticleEmitterNode* particleEmitter, const char* slotName)
{
for(int x=0; x < mountedEmitters.size(); x++)
{
mountedParticle* mp = mountedEmitters[x];
if(particleEmitter == mp->particleEmitter )
{
delete mountedEmitters[x];
mountedEmitters.erase_fast(x);
setMaskBits(mountedParticleMask);
break;
}
}
}
ConsoleMethod( ShapeBase, UNhookEmitter, void, 3, 3, "(ParticleEmitterNode Name)")
{
ParticleEmitterNode* particleNode;
if (!Sim::findObject(argv[2], particleNode))
{
Con::errorf("Unable to UNhookemitter: \"%s\". None hooked?", argv[2]);
return;
}
object->UNhookEmitters(particleNode, argv[3]);
}Paste that at the end of shapebase.ccThen in shapbase.h search for
void mountParticleEmitter(ParticleEmitterNode* particleEmitter, const char* slotName);and under it paste
void UNhookEmitter(ParticleEmitterNode* particleEmitter, const char* slotName);
To use it,
%ShapeBase.schedule(100, UNhookEmitter, %emittername );
#19
03/02/2007 (9:24 pm)
So how's this resource working? I'm thinking of giving it a shot, has it been updated/improved upon?
#20
08/31/2007 (12:39 pm)
I am a little confused, I am a noob so bare with me , on how the actual particles get onto the node and get emitted or projected. I know how to make the data blocks and everything but i am not sure how to actually plug the data into the code... I put the code to execute the object in the creteplayer function in game.cs, i am not sure if this is where it is supposed to be either. Any help on these issues would be very very helpful in tackling a very frustrating particle emitter system 
Torque Owner Stefan Lundmark