[RESOLVED] How to enable a particle emitter with collisions?
by Chris Hoopes · in Torque Game Builder · 01/02/2013 (6:34 pm) · 19 replies
I'm trying to set up an emitter for sparks and I'd like them to collide with platforms and sort of bounce a few times before disappearing. I thought that I could achieve this by:
- setting the emitter and platform's Collisions Layers and Collision Groups
- set the emitter to Send Collisions
- set the platform to Receive Collisions
- set the emitter's Collisions Response to Bounce
This apparently didn't work. I then read about the Use Effect Collisions checkbox which says:
So I thought that maybe this needed to be checked just to get visual particle collision to work as well but apparently that's not the case. So how can I get my little particles to bounce and interact with the environment?
- setting the emitter and platform's Collisions Layers and Collision Groups
- set the emitter to Send Collisions
- set the platform to Receive Collisions
- set the emitter's Collisions Response to Bounce
This apparently didn't work. I then read about the Use Effect Collisions checkbox which says:
Quote:
Use Effect Collisions - When enabled, individual particle collision will be turned on. Whenever a particle node collides with another object in the same collision group or mask, the function onParticleCollision will be called for the emitter.
So I thought that maybe this needed to be checked just to get visual particle collision to work as well but apparently that's not the case. So how can I get my little particles to bounce and interact with the environment?
About the author
#2
01/03/2013 (7:10 am)
I've actually seen your particle system and it does look nice but isn't it only for Torque3D? I wasn't aware that any of that could also work for TGB.
#3
Then I'm not sure, the idea would be the same but I don't have access to T2D source so I can't be helpful with that, perhaps when T2D OS is released but not before :)
But can you do raycasts in T2D? I believe the general process would be the same, find the place where the particles positions is updated, where the position is updated do a raycast from it's current position to the new position, if it collides with something, reflect or deflect the particles accordingly.
As in the T3D code snippet above, where I do a raycast from the particle to where the particle will end up and and if it collides, I find the deflected vector and place the particle there instead (if the particles moves really fast, you would want a more sophisticated algorithm)
01/03/2013 (10:15 am)
Ah! I'm sorry thought this was a T3D forum >.>Then I'm not sure, the idea would be the same but I don't have access to T2D source so I can't be helpful with that, perhaps when T2D OS is released but not before :)
But can you do raycasts in T2D? I believe the general process would be the same, find the place where the particles positions is updated, where the position is updated do a raycast from it's current position to the new position, if it collides with something, reflect or deflect the particles accordingly.
As in the T3D code snippet above, where I do a raycast from the particle to where the particle will end up and and if it collides, I find the deflected vector and place the particle there instead (if the particles moves really fast, you would want a more sophisticated algorithm)
#4
Mostly I've been finding a lot of dead ends, people asking similar questions but not really coming to any resolution. The closest answer I've found is this thread where someone made a change to a much older version of the engine to support particle collision but it's not really applicable to me at this time. It's a little frustrating.
01/03/2013 (10:37 am)
Ha, no problem. But I'm not sure, I haven't dug into the source myself to see what's up as I have plenty of other, bigger tasks for the game. I was just hoping that someone has attempted something similar and could steer me in the right direction.Mostly I've been finding a lot of dead ends, people asking similar questions but not really coming to any resolution. The closest answer I've found is this thread where someone made a change to a much older version of the engine to support particle collision but it's not really applicable to me at this time. It's a little frustrating.
#5
The only thing I have been able to find is a TGB documentation that talks about particles maybe that is still applicable for your version of T2D?
01/03/2013 (10:49 am)
Ah yes that seems to be for TorqueX2D (the xbox version of T2D)The only thing I have been able to find is a TGB documentation that talks about particles maybe that is still applicable for your version of T2D?
#6
[edit]
First step - make this change in t2dParticleEffect.cc at about line 1796 (inside of t2dParticleEffect::integrateObject()):
This gets the particles colliding, but the selected collision response is being ignored and is sticking with the CLAMP default. Still working....
01/03/2013 (2:15 pm)
Ok, looking at things in t2dParticleEmitter and t2dParticleEffect it appears that particle collisions are just not being checked. I'll see if I can't sort this out.[edit]
First step - make this change in t2dParticleEffect.cc at about line 1796 (inside of t2dParticleEffect::integrateObject()):
// Update all emitters.
for ( U32 n = 0; n < mParticleEmitterList.size(); n++ )
{
// Integrate Emitter (if visible).
t2dParticleEmitter *pEmitter = dynamic_cast<t2dParticleEmitter*>( mParticleEmitterList[n].mpSceneObject );
if ( pEmitter && pEmitter->getVisible() )
{
// Integrate Emitter.
pEmitter->integrateObject( sceneTime, elapsedTime, pDebugStats );
// Total Particles.
activeParticles += pEmitter->getActiveParticles();
// vvvvv Added this section vvvvv
if (mUseEffectCollisions)
{
t2dPhysics::cCollisionStatus status;
pEmitter->checkParticleCollisions(this, elapsedTime, status, pDebugStats);
}
// ^^^^^ Added this section ^^^^^
}
}I included the for loop for reference - see comments for the added part.This gets the particles colliding, but the selected collision response is being ignored and is sticking with the CLAMP default. Still working....
#7
So, more progress, but still not perfect.
01/03/2013 (3:08 pm)
Mixed success - particles now collide with scene objects. Sort of. Using CIRCLE for detection mode on a scene object causes particles to fail to collide, but at least I have corrected the BOUNCE response by tweaking checkParticleCollisions() in t2dParticleEmitter.cc :// Set new Linear Velocity. t2dVector velocity = linearVelocity - (collisionNormal*(2.0f*dotVelocityNormal) * restitution); linearVelocity.set(velocity.mX, velocity.mY);
So, more progress, but still not perfect.
#8
Gah, much more digging to be done here I suppose.
01/03/2013 (3:18 pm)
Oddly, this seems to have caused the particle effect object itself to begin to move unless you set it to "immovable". And now, while particles collide with scene objects, scene objects don't seem to be reliably colliding with each other....Gah, much more digging to be done here I suppose.
#9
01/03/2013 (8:54 pm)
Richard, thanks so much for looking into this. And yeah when you feel like you've come to a stable solution please let me know and I'll update the engine on my side to check it out. I'm not nearly as familiar with everything as you and I'm afraid I'll just end up breaking more if I try to get too deep at this point.
#10
Oh, and while the particle collision is mostly working, the emitter is now colliding too.
And it looks like certain tangential collisions cause the bounce normal to be reversed.
01/04/2013 (7:21 am)
I'm breaking plenty it seems - I'll have to look at this for a minute. Right now I'm noticing that approximately 5% of the time the particles don't collide and somehow it's messing with the way other scene objects collide. I don't know if it's just too many collisions to process and it's trying to "early out" on stuff or what.Oh, and while the particle collision is mostly working, the emitter is now colliding too.
And it looks like certain tangential collisions cause the bounce normal to be reversed.
#11
I'd say it's close but I'm not sure why the outstanding issues are happening at all. The collision is resolved in a fairly straightforward manner and it doesn't really look like it should be misbehaving. Maybe I'll think of something in my sleep....
01/04/2013 (5:12 pm)
State of the system: Particles still don't like the "CIRCLE" detection mode but seems to do fine using "FULL" or "POLYGON". They also don't collide with anything on a different scene layer - but they do seem to respect collision layers as long as the object is on the same scene layer.I'd say it's close but I'm not sure why the outstanding issues are happening at all. The collision is resolved in a fairly straightforward manner and it doesn't really look like it should be misbehaving. Maybe I'll think of something in my sleep....
#12
01/04/2013 (5:15 pm)
That generally works wonders for me. I've stayed up until three in the morning banging my head against something only to pop awake and know immediately how to fix it. Feels great when that happens.
#13
and T2D/t2dParticleEffect.cc t2dParticleEffect::integrateObject():
This works most of the time and should be functional for "sparks" and other one-shot particle effects that you want to do some bouncing. Just remember to set anything you want them to collide with to use FULL or POLYGON collision detection.
01/07/2013 (9:49 am)
Ok, T2D/t2dParticleEmitter.cc, t2dParticleEmitter::checkParticleCollisions()://-----------------------------------------------------------------------------
// Check Particle Collisions.
//-----------------------------------------------------------------------------
bool t2dParticleEmitter::checkParticleCollisions( const t2dParticleEffect* pParentEffect, const F32 elapsedTime, t2dPhysics::cCollisionStatus& sendCollisionStatus, CDebugStats* pDebugStats )
{
// Reset Initial Collision Status.
bool collisionStatus = false;
// Fetch SceneGraph.
t2dSceneGraph* pSceneGraph = pParentEffect->getSceneGraph();
// Get Parent Collision Masks.
const U32 groupMask = pParentEffect->getCollisionGroupMask();
const U32 layerMask = pParentEffect->getCollisionLayerMask();
// Get Parent Collision Response.
const t2dPhysics::eCollisionResponse collisionResponse = pParentEffect->getCollisionResponseMode();
// Get Parent Restitution.
const F32 restitution = pParentEffect->getRestitution();
// Fetch First Particle Node.
tParticleNode* pParticleNode = mParticleNodeHead.mNextNode;
// Next Particle Node.
tParticleNode* pNextParticleNode;
// Process All particle nodes.
while ( pParticleNode != &mParticleNodeHead )
{
t2dVector newPosition;
// Default to not suppressing movement.
pParticleNode->mSuppressMovement = false;
// Fetch Start Position.
t2dVector& startPosition = pParticleNode->mPosition;
// Calculate Projected Position.
const t2dVector endPosition = pParticleNode->mPosition + (pParticleNode->mVelocity * pParticleNode->mRenderSpeed * elapsedTime);
// Pick Objects along our velocity path.
const U32 pickedObjects = pSceneGraph->pickLine( startPosition, endPosition, groupMask, layerMask, false, pParentEffect );
// Reference next Particle Node.
// NOTE:- We do this here because we may destroy the particle if the collision-response is in "KILL" mode.
pNextParticleNode = pParticleNode->mNextNode;
// Did we collide?
if ( pickedObjects > 0 )
{
// Flag Collision Occurred.
collisionStatus = true;
// Fetch Colliding Object.
typeSceneObjectVectorConstRef pickVector = pSceneGraph->getPickList();
// Fetch Scene Object.
t2dSceneObject* pSceneObject;
int index = 0;
while ( index < pickedObjects )
{
pSceneObject = pickVector[index];
if ( pSceneObject->getCollisionActiveReceive() || pSceneObject->getCollisionActiveSend() ||
pSceneObject->getCollisionPhysicsReceive() || pSceneObject->getCollisionPhysicsSend() )
break;
index++;
}
// Fetch Collision Time.
const F32& collisionTime = pSceneObject->mSortKeyCollisionTime;
// Lerp to collision position.
startPosition.lerp( endPosition, collisionTime, startPosition );
// Reference Velocity.
t2dVector& linearVelocity = pParticleNode->mVelocity;
// Reference Collision Normal.
t2dVector& collisionNormal = pSceneObject->mSortKeyCollisionNormal;
sendCollisionStatus.mCollisionNormal = collisionNormal;
switch( collisionResponse )
{
// Bounce.
case t2dPhysics::T2D_RESPONSE_BOUNCE:
{
// Calculate Dot Velocity Normal.
const F32 dotVelocityNormal = collisionNormal * linearVelocity;
// Any velocity to clamp?
if ( mNotZero(dotVelocityNormal) )
{
// Set new Linear Velocity.
t2dVector velocity = linearVelocity - (collisionNormal*(2.0f*dotVelocityNormal) * restitution);
linearVelocity.set(velocity.mX, velocity.mY);
}
} break;
// Clamp.
case t2dPhysics::T2D_RESPONSE_CLAMP:
{
// Calculate Dot Velocity Normal.
const F32 dotVelocityNormal = collisionNormal * linearVelocity;
// Any velocity to clamp?
if ( mNotZero(dotVelocityNormal) )
{
// Set new Linear Velocity.
linearVelocity -= collisionNormal*dotVelocityNormal;
}
} break;
// Bounce.
case t2dPhysics::T2D_RESPONSE_STICKY:
{
// Set at rest.
linearVelocity.set(0.0f,0.0f);
} break;
// Kill.
case t2dPhysics::T2D_RESPONSE_KILL:
{
// Free Particle.
freeParticle( pParticleNode );
} break;
};
// Suppress Movment.
//pParticleNode->mSuppressMovement = true;
}
// Move to next Particle Node.
pParticleNode = pNextParticleNode;
};
// Return Collision Status.
sendCollisionStatus.mValidCollision = collisionStatus;
return collisionStatus;
}and T2D/t2dParticleEffect.cc t2dParticleEffect::integrateObject():
//-----------------------------------------------------------------------------
// Integrate Object.
//-----------------------------------------------------------------------------
void t2dParticleEffect::integrateObject( const F32 sceneTime, const F32 elapsedTime, CDebugStats* pDebugStats )
{
// Call Parent.
Parent::integrateObject( sceneTime, elapsedTime, pDebugStats );
#ifdef TORQUE_ENABLE_PROFILER
PROFILE_START(T2D_t2dParticleEffect_integrateObject);
#endif
// Is the Effect Playing?
if ( mEffectPlaying )
{
// Yes, so update Effect Age.
mEffectAge += elapsedTime;
// Reset Active Particle Count.
U32 activeParticles = 0;
// Update all emitters.
for ( U32 n = 0; n < mParticleEmitterList.size(); n++ )
{
// Integrate Emitter (if visible).
t2dParticleEmitter *pEmitter = dynamic_cast<t2dParticleEmitter*>( mParticleEmitterList[n].mpSceneObject );
if ( pEmitter && pEmitter->getVisible() )
{
// vvvvv Added this section vvvvv
if ( mUseEffectCollisions )
{
t2dPhysics::cCollisionStatus status;
checkCollisionSend(elapsedTime, status, pDebugStats);
}
// ^^^^^ Added this section ^^^^^
// Integrate Emitter.
pEmitter->integrateObject( sceneTime, elapsedTime, pDebugStats );
// Total Particles.
activeParticles += pEmitter->getActiveParticles();
}
}
// Only handle modes if we're not waiting for particles.
if ( !mWaitingForParticles )
{
// Handle Effect-Life Mode Appropriately.
switch( mEffectLifeMode )
{
// Cycle-Life Mode.
case t2dParticleEffect::CYCLE:
{
// Have we expired?
if ( mEffectAge >= mEffectLifetime )
// Yes, so restart effect (reset existing particles).
playEffect(true);
} break;
// Kill-Life Mode.
case t2dParticleEffect::KILL:
{
// Have we expired?
if ( mEffectAge >= mEffectLifetime )
{
if( getSceneGraph() && getSceneGraph()->getIsEditorScene() )
stopEffect( true, false );
else
// Yes, so stop Effect and Kill.
stopEffect( true, true );
}
} break;
// Stop-Life Mode.
case t2dParticleEffect::STOP:
{
// Have we expired?
if ( mEffectAge >= mEffectLifetime )
// Yes, so stop Effect.
stopEffect( true, false );
} break;
}
}
// Are we waiting for particles and none are active?
if ( mWaitingForParticles && activeParticles == 0 )
{
// Yes, so stop effect ( take note of 'killEffect' flag ).
stopEffect( false, mWaitingForDelete );
}
}
#ifdef TORQUE_ENABLE_PROFILER
PROFILE_END(); // T2D_t2dParticleEffect_integrateObject
#endif
}This works most of the time and should be functional for "sparks" and other one-shot particle effects that you want to do some bouncing. Just remember to set anything you want them to collide with to use FULL or POLYGON collision detection.
#14
One weird issue though is that the particles seem to be colliding with ALL scene objects regardless of scene layer, collision layer, collision group or what the object has marked off for send/receive collision and send/receive physics. Is this what this section of the code should be checking?
01/14/2013 (4:48 pm)
Hey sorry for the delay in getting back to you, I've been cranking away at other parts of the project and got sidetracked. I just implemented the code above and the colliding and bouncing seems to be working! It's really cool to send the player through a shower of sparks and see them all bounce off in different directions.One weird issue though is that the particles seem to be colliding with ALL scene objects regardless of scene layer, collision layer, collision group or what the object has marked off for send/receive collision and send/receive physics. Is this what this section of the code should be checking?
while ( index < pickedObjects )
{
pSceneObject = pickVector[index];
if ( pSceneObject->getCollisionActiveReceive() || pSceneObject->getCollisionActiveSend() ||
pSceneObject->getCollisionPhysicsReceive() || pSceneObject->getCollisionPhysicsSend() )
break;
index++;
}
#15
01/14/2013 (7:07 pm)
That looks to be exactly what that's checking. I didn't see this behavior on my end - have to double-check.
#16
Here is the platform its particles are colliding with's collision settings on scene layer 1: i.imgur.com/mBOL4.png
And here is the result, particles from the emitter colliding with the platform even though that object has all collision disabled and is not part of any collision layer or group: i.imgur.com/46z1j.png
So basically it seems that the particles are colliding with everything as long as it has Send Collision checked and Use Effect Collisions unchecked.
01/14/2013 (7:21 pm)
If it helps to see what I'm seeing, here is the emitter with its collision settings on scene layer 0: i.imgur.com/RCNFE.pngHere is the platform its particles are colliding with's collision settings on scene layer 1: i.imgur.com/mBOL4.png
And here is the result, particles from the emitter colliding with the platform even though that object has all collision disabled and is not part of any collision layer or group: i.imgur.com/46z1j.png
So basically it seems that the particles are colliding with everything as long as it has Send Collision checked and Use Effect Collisions unchecked.
#17
01/16/2013 (1:11 pm)
Ok, you can try including or excluding some of those filters and see how it goes....
#18
- a list of objects in its path was being selected based on the vector, collision mask, etc
- a check was being done on every object in this list to see if it has any collision or physics properties set
- a switch statement was done to see what type of collision it should be
- the collision response was returned
The problem here is a few things. The first is here:
pickedObjects represents a list of objects along the particle's path that the particle has the opportunity to collide with. However depending on the parent emitter and colliding object's settings it may or may not. The issue is that for every object it was possible to collide with the collisionStatus was always being set to "true." This shouldn't be the case. The second problem is here:
Now I'm not entirely sure what's going on here or what the pickVector is but shouldn't this be checking against different rules? Objects that Send Collisions collide with objects that Receive Collisions and vice/versa? Here we're just checking if they have any of those properties set in general, not how the interact with each other. I changed it to this:
So now it's setting collisionStatus to "false" by default and if the emitter is sending collisions and the colliding object is receiving or vice/versa then it's setting collisionStatus to "true" as per rules of this engine that's when a collision is supposed to occur. Now you'll see that I left out the check for Send/Receive Physics because quite frankly I don't know how they fit in there. If this all seems wrong please let me know.
But we're not quite finished because even with that code in place particles are still colliding with all objects regardless of settings. Why? Because the switch status on collisionResponse is always being executed regardless of collisionStatus so the particles are reacting accordingly. I simply wrapped all that in an if statement to check on collision like so:
So now it checks to see if an object is a valid collision and whether or not to use the assigned response. Please if you could take a look at what I've done and see if you can find anything majorly busted, especially about how the physics settings factor in to these checks. I may be way off base about this but particles now see to be acting in a manner more consistent with how the engine is meant to work.
Thanks!
01/16/2013 (7:58 pm)
OK so I think I have a clearer understanding of what's going on here and have come up with a solution. Basically the problem was that, for every particle, the following was happening:- a list of objects in its path was being selected based on the vector, collision mask, etc
- a check was being done on every object in this list to see if it has any collision or physics properties set
- a switch statement was done to see what type of collision it should be
- the collision response was returned
The problem here is a few things. The first is here:
// Pick Objects along our velocity path.
const U32 pickedObjects = pSceneGraph->pickLine( startPosition, endPosition, groupMask, layerMask, false, pParentEffect );
// Reference next Particle Node.
// NOTE:- We do this here because we may destroy the particle if the collision-response is in "KILL" mode.
pNextParticleNode = pParticleNode->mNextNode;
// Did we collide?
if ( pickedObjects > 0 )
{
// Flag Collision Occurred.
collisionStatus = true;pickedObjects represents a list of objects along the particle's path that the particle has the opportunity to collide with. However depending on the parent emitter and colliding object's settings it may or may not. The issue is that for every object it was possible to collide with the collisionStatus was always being set to "true." This shouldn't be the case. The second problem is here:
while ( index < pickedObjects )
{
pSceneObject = pickVector[index];
if ( pSceneObject->getCollisionActiveReceive() || pSceneObject->getCollisionActiveSend() ||
pSceneObject->getCollisionPhysicsReceive() || pSceneObject->getCollisionPhysicsSend() )
break;
index++;
}Now I'm not entirely sure what's going on here or what the pickVector is but shouldn't this be checking against different rules? Objects that Send Collisions collide with objects that Receive Collisions and vice/versa? Here we're just checking if they have any of those properties set in general, not how the interact with each other. I changed it to this:
if ( pickedObjects > 0 )
{
// Flag Collision Occurred.
collisionStatus = false;
// Fetch Colliding Object.
typeSceneObjectVectorConstRef pickVector = pSceneGraph->getPickList();
// Fetch Scene Object.
t2dSceneObject* pSceneObject;
int index = 0;
while ( index < pickedObjects )
{
pSceneObject = pickVector[index];
//if ( pSceneObject->getCollisionActiveReceive() || pSceneObject->getCollisionActiveSend() ||
// pSceneObject->getCollisionPhysicsReceive() || pSceneObject->getCollisionPhysicsSend() )
if( (pParentEffect->getCollisionActiveReceive() && pSceneObject->getCollisionActiveSend()) ||
(pParentEffect->getCollisionActiveSend() && pSceneObject->getCollisionActiveReceive()) )
{
collisionStatus = true;
break;
}
index++;
}
...So now it's setting collisionStatus to "false" by default and if the emitter is sending collisions and the colliding object is receiving or vice/versa then it's setting collisionStatus to "true" as per rules of this engine that's when a collision is supposed to occur. Now you'll see that I left out the check for Send/Receive Physics because quite frankly I don't know how they fit in there. If this all seems wrong please let me know.
But we're not quite finished because even with that code in place particles are still colliding with all objects regardless of settings. Why? Because the switch status on collisionResponse is always being executed regardless of collisionStatus so the particles are reacting accordingly. I simply wrapped all that in an if statement to check on collision like so:
if(collisionStatus)
{
switch( collisionResponse )
{
...
}
}So now it checks to see if an object is a valid collision and whether or not to use the assigned response. Please if you could take a look at what I've done and see if you can find anything majorly busted, especially about how the physics settings factor in to these checks. I may be way off base about this but particles now see to be acting in a manner more consistent with how the engine is meant to work.
Thanks!
#19
01/17/2013 (6:19 am)
I'll try to take a look at this today and let you know what I find.
Torque Owner Lukas Joergensen
WinterLeaf Entertainment
If so I have this snippet that I use for particle collision in my IPS resource and upcoming product.
Add this inside of the foreach loop of the Update function of the ParticleEmitter
RayInfo rInfo; if(gClientContainer.castRay(part->pos, part->pos + part->vel * time, TerrainObjectType | InteriorObjectType | VehicleObjectType | PlayerObjectType | StaticShapeObjectType, &rInfo)) { Point3F proj = mDot(part->vel,rInfo.normal)/(rInfo.normal.len()*rInfo.normal.len())*rInfo.normal; Point3F between = (part->vel - proj); part->vel = -(part->vel-(between*2)*0.8); }(Shameless selfplug)
I will recommend that you have a look at my IPS Lite resource and when it comes out my IPS Pro product if you are interested in having some cool particle effects and want to be able to easily customize your particle system
(Shameless selfplug end)