Game Development Community

Particle Emitters and node rotation

by Gary Preston · in Torque Game Engine · 01/04/2006 (2:57 pm) · 4 replies

I've mounted 4 jets to a custom player class. The jets have a particle emitter state triggered whenever the player moves left/right/fwd/back or thrusts upwards.

I've modified the onNewDatablock code to mark the nodes that each jet is mounted to as hands off so that I can manually animate the jets as follows

for (S32 i = 0; i < TankBaseData::JET_COUNT; i++ )
      if (getDataBlock()->jetImageData[i])
         mShapeInstance->setNodeAnimationState( getJetMountNode(i), TSShapeInstance::MaskNodeHandsOff );


In the updateLookAnimation I animated the nodes as follows
for (S32 i=0; i < TankBaseData::JET_COUNT; i++)
   {  
      S32 node = getJetMountNode(i);
      MatrixF *mat = &mShapeInstance->mNodeTransforms[node];
      Point3F defaultPos = getDataBlock()->shapeResource->defaultTranslations[node];   
      // 45.0 could do with going in the datablock
      F32 yrot = delta.move.y > 0 ? mDegToRad(45.0) : delta.move.y < 0 ? mDegToRad(-45.0) : 0.0;
      F32 xrot = delta.move.x > 0 ? mDegToRad(45.0) : delta.move.x < 0 ? mDegToRad(-45.0) : 0.0;
      mat->set(EulerF(yrot, -xrot, 0));      
      mat->setColumn(3,defaultPos);      
   } 
   // now we must tell the animatino system we have been setting nodes by hand
   mShapeInstance->setDirty(TSShapeInstance::TransformDirty);
   mShapeInstance->animate();

This works great as far as the jets rotating in the opposite direction to travel (eg move forward the jets rotate such that the nozzel is facing behind the player as would be expected if it really was providing thrust)

When the player moves I activate the shape image state that emits particles as
void TankBase::updateJets(const Move* move)
{   
   for (S32 i=0; i < TankBaseData::JET_COUNT; i++)   
      setImageTriggerState( getDataBlock()->jetImageData[i]->mountPoint,
                           (move->x != 0 || move->y != 0 || move->trigger[2]) && mJetsActive );   
}

Heres where the problems begin. With the following particle emitter datablock

datablock ParticleEmitterData(JetEmitter)
{
   ejectionPeriodMS = 1;
   periodVarianceMS = 0;
   ejectionVelocity = 60;
   velocityVariance = 0.0;
   ejectionOffset   = 1.5; 
   thetaMin         = 90;
   thetaMax         = 90;
   phiReferenceVel  = 0;
   phiVariance      = 0;
   overrideAdvances = false;
   particles = "JetEmitterParticles";   
};


Particles orient perfectly with the jets in their unrotated (downward) facing configuration. When moving forward of backwards the particles again emit perfectly in alignment with the rotated jets.

However when moving left or right the particles emit downwards and thus arn't aligned with the left/right rotated jets.

Any ideas as to what the problem may be?

For those that are wondering where the original post went, I've deleted it and re-written the problem in with the more simplified attempt to acheive what I'm doing.

#1
01/05/2006 (3:06 pm)
I've got the jet particles working for both idle, forward/back and left/right motion. Although it feels like a bit of a hack to get the lefr/right emission working, I had to add a 90 degree rotation around the z axis.

for (S32 i=0; i < TankBaseData::JET_COUNT; i++)
   {  
      S32 node = getJetMountNode(i);
      MatrixF *mat = &mShapeInstance->mNodeTransforms[node];
      Point3F defaultPos = getDataBlock()->shapeResource->defaultTranslations[node];   
      
      F32 yrot = delta.move.x > 0 ? mDegToRad(-45.0) : delta.move.x < 0 ? mDegToRad(45.0) : 0.0;
      F32 xrot = delta.move.y > 0 ? mDegToRad(45.0) : delta.move.y < 0 ? mDegToRad(-45.0) : 0.0;  
      // this zrot feels like a hack.... I'm sure these rotations are not correct
      // and might be why diagonal rot fails
      F32 zrot = yrot > 0.0 ? mDegToRad(90.0) : yrot < 0.0 ? mDegToRad(90.0) : 0.0;

      mat->set(EulerF(xrot,yrot,zrot));
      mat->setColumn(3,defaultPos);             
   }

It wouldn't be too bad a hack, only the particle emission doesn't work for diagonal movement, eg when the x & y move are both active.

I beleive my problem is a combination of having the 90deg min/max theta setting in the particle data block and the rotations used on the node. What I'm struggling with is that the above rotates the node so that the jet nozzle points correctly in all 8 compass directions, yet the particles only emit correctly in 4 compass directions.
#2
01/08/2006 (1:48 am)
Why not calculate the vector you want them to face, take an up vector, and use a cross product to generate a rotation matrix for them? That seems much more robust than the hackish way you're doing it now...
#3
01/08/2006 (11:14 am)
Short Answer:
It was a model orientation issue. Model needed to be aligned such that the nozel exit was down +Y as particles emit out of +Y by default.

Long Explanation:

The tank model is setup such that forward is +Y and up is +Z, the mount3 node that the jets are mounted to is setup with the same orientation. The jet model is a vertical cylinder aligned again with +Y forward and +Z up. Thus mounting the jet to the tank with no node rotation gives a tank facing along +Y with the jets facing down -Z (the nozzel where the flames will come out points downwards into the ground).

In this configuration I would expect particles with a negative velocity to emit out the base of the jet. However they come out the front of the jet +Y. Setting thetaMin/Max to 90 fixed this and the particles now emitted down -Z (that was my big mistake).

forward movement

If we move forwards or backwards we rotate the jets by +-45 degrees around the X axis and get a rotation matrix as follows:

1.0, 0.0, 0.0, 0.0
0.0, 0.52, 0.85, 0.0
0.0, -0.85, 0.52, 0.0
0.0, 0.0, 0.0, 1.0

The way the particle emit code works is that it uses column 1 as the axis of emission. (0, 0.52, -0.85) it then calculates off this an X axis around which we will rotate each particle emitted by thetaMin/Max by using the cross product with one of two vectors.

If axis.z < 0.9 we cross the axis with 0,0,1 otherwise we cross it with 0,1,0. So with the above matrix we will have a z of -0.85 which means xaxis (normalized) = 1,0,0.

Thus the particles are emitted down (0,0.52,-0.85) and rotated around 1,0,0 by 90degrees which means they'll come out of the base of the jet as needed.


left/right movement

However if we rotate around the Y axis by 45 degrees during horizontal movement the model orientation problem rears its ugly head.

The transform matrix for the mount3 node with 45 degree rotation about X is

0.52, 0.0, -0.8, 0.0
0.0, 1.0, 0.0, 0.0,
0.85, 0.0, 0.52, 0.0
0, 0, 0, 1

In this case Column 1 is 0,1,0,0 and thus axis.z is 0. So the particle engine obtains xaxis by cross product with 0,0,1 giving an axis (normalixed) again of 1,0,0.

Clearly this is not what we want (at least it isn't when we're relying on the 90deg rotation around X to fix the model problem :P)

Fix

So the problem was caused by the model been setup such that it is stood vertically with the node used for particle emission oriented such that +Y extened out the sides/front of the jet rather than down through the base.

My use of thetaMin/Max to compensate for this was flawed even though it appeared to work in the default case and during forward/backwards motion, i was using in a way thetaMin/Max wasn't intended to work.

If the particle engine is changed to use getColumn(2,&axis) thus emitting down the Z axis by default, everything works. Obviously that isn't a real fix, its the model itself that is setup wrong. So the solution is to get the model re-exported.

With the fixed model the following code works fine in all 8 orientations :)

for (S32 i=0; i < TankBaseData::JET_COUNT; i++)
   {  
      S32 node = getJetMountNode(i);
      MatrixF *mat = &mShapeInstance->mNodeTransforms[node];
      Point3F defaultPos = getDataBlock()->shapeResource->defaultTranslations[node];   
      QuatF defaultRot;
      getDataBlock()->shapeResource->defaultRotations[node].getQuatF(&defaultRot);   
      
   	   F32 yrot = delta.move.x > 0 ? mDegToRad(-45.0) : delta.move.x < 0 ? mDegToRad(45.0) : 0.0;
      F32 xrot = delta.move.y > 0 ? mDegToRad(45.0) : delta.move.y < 0 ? mDegToRad(-45.0) : 0.0;  
      mat->set(EulerF(xrot,yrot,0));
      mat->setColumn(3,defaultPos);             
   }

So in short, I've gone all around the houses and now know how the particle engine does the dirty work due a bad assumption on my part that lead me to beleive my code was wrong rather than the model orientation.

Long post I know, but I thought I'd explain why the model orientation had this side effect just in case anyone else comes a cropper with it.
#4
01/08/2006 (4:48 pm)
Awesome, thank you for the thorough writeup!