Particle problems

I've just been adding particle effects for flames and smoke coming from damaged planes in my game, and while it looks rather nice :), it has a couple of issues:


  1. I need to control the particle release rate, so that I can switch the flames/smoke off and on, and alter intensity. However, sometimes when I use controlFlow, and set the releaseRate to 0, my game slows down and stops over a few frames. It seems like some delay builds up exponentially until the game pretty much freezes. If I have a high or default release rate, or don't use controlFlow, this never seems to happen. I did think that it only happened with releaseRate at 0, but releaseRate 1 will sometimes do it as well. Using warmUp sometimes seems to help a little, but I really need a complete fix so it won't kill my game :slight_smile:


  2. I'm using subtractive blending for smoke:


         smokeAlphaState.setSrcFunction(AlphaState.SB_ZERO);
         smokeAlphaState.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_COLOR);


When the particles are a long distance away, they appear as squares rather than the roughly circular blob texture I'm using. At extreme distance, they no longer fade out according to the endColor, they stay pretty much opaque until they disappear. Things look fine up close, then "blend" to the opaque squares as distance increases.
The "turning into squares" thing looks a lot like mipmapping, but I have tried to disable this on the TextureState applied to the ParticleMesh:


         texture.setMipmapState(Texture.MM_NONE);
         textureState.setTexture(texture);



This doesn't seem to make any difference, and mipmapping doesn't explain the alpha problem either.

You can see the effect in this screenshot (just a small region of 1024x768 screen):

The particles at the end of the trails should be nearly transparent (when you see them in motion, they very obviously just "blink" out rather than fading) and should still look like little fluffy blobs.

The particles should look like this: https://aircarrier.dev.java.net/nonav/images/flamesScreenShot.jpg at all ranges (screenshot from closer to the particles)
  1. Hmm, I'd log a possible bug about release rate of 0…  For now, instead of using the release rate to kill particles, try setting the repeat mode to CLAMP.  Switch it back to WRAP and respawn the particles with a modest release rate to bring them back.


  2. Is there a zstate on the particles?  I'm just wondering if the difference is that the particles are being drawn on top or behind the blue sky.




  1. Thanks, I'll try tweaking the clamp thing, but it would be nice to vary effect as well as switching on and off. My first attempt with clamping gave my particles that emit and then stop emitting regularly, even when I'm not changing anything. I tried setActive on the controller as well - that works fine when switched on, but leaves particles hanging when I set non-active. I think I'll have a quick look at the controller code, doesn't seem too complicated.


  2. The particles have a zState, slightly odd but based on one I came up with for my glowing bullets, which seems to work fine. I'm pretty sure the particles aren't going behind anything, it's not so much that they disappear in the wrong place/time, more that they don't fade out first. Z state is:



         zState = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
         zState.setEnabled(true);
         zState.setFunction(ZBufferState.CF_LEQUAL);
         zState.setWritable(false);

Can you try eliminating your skybox, setting the gl clear color to blue, and seeing if you have the same blend issue?

I tried that, and the same issue is there, I took some more shots of it:







But that started me thinking, so I tried disabling other stuff as well - and if I disable fog it is fine! (tiny image, but you can see the smoke still appears as textured blobs fading out nicely):





Unfortunately my game then looks terrible :slight_smile:



The fog state is as follows:



      FogState fs = display.getRenderer().createFogState();
      fs.setDensity(0.5f);
      fs.setEnabled(true);
      fs.setColor(skyColor);
      fs.setEnd(1000);
      fs.setStart(50);
      fs.setDensityFunction(FogState.DF_LINEAR);
      fs.setApplyFunction(FogState.AF_PER_VERTEX);
      worldNode.setRenderState(fs);      



I'm going to try changing fog parameters to see if it makes any difference.

I've also been playing with the setReleaseRate thing, I went through the ParticleController update method, and I couldn't find anything that seemed wrong. I thought for a while the problem had stopped, since I ran a few times without slowdown, but it's back now.

I ran in debug mode, and it seems like when the slowdown happens, if I pause I am always somewhere like:


   BoundingSphere.recurseMini(FloatBuffer, int, int, int) line: 215   
   BoundingSphere.recurseMini(FloatBuffer, int, int, int) line: 226   
   BoundingSphere.recurseMini(FloatBuffer, int, int, int) line: 226   
   BoundingSphere.recurseMini(FloatBuffer, int, int, int) line: 226   
   BoundingSphere.recurseMini(FloatBuffer, int, int, int) line: 226   
   BoundingSphere.calcWelzl(FloatBuffer) line: 170   
   BoundingSphere.computeFromPoints(FloatBuffer) line: 126   
   TriangleBatch(GeomBatch).updateModelBound() line: 563   
   ParticleMesh(Geometry).updateModelBound() line: 456   
   ParticleController.update(float) line: 233   
   ParticleMesh(Spatial).updateWorldData(float) line: 335   
   ParticleMesh(Geometry).updateWorldData(float) line: 488   
   ParticleMesh(Spatial).updateGeometricState(float, boolean) line: 311   
   ParticleMesh(ParticleGeometry).updateGeometricState(float, boolean) line: 940   
   Plane(Node).updateWorldData(float) line: 402   



Is it possible that the bounding sphere calculation goes wrong if all the particles are dead because of no releases? The code path will be different for this case than for any other because the particles will all die, but the particleController update will never get "dead == true", so won't deactivate itself. AFAICT this can only happen when we have control flow on, and release rate zero, since the update routine then just becomes "update all particles, don't recreate any, set dead = false". In any other mode, we will be either recreating particles, or we will eventually die (e.g. with RT_CLAMP, we will eventually get dead==true). Seems like this could be at least worked around by noticing the case where all particles returned "true" from updateAndCheck, and we didn't want to recreate any, in this case can we just not update the model bounds? I'll try this, but I don't understand the code enough to really know if this is fixing the problem properly. In any case, I just wanted to draw your attention to the fact that the ParticleController update method runs slightly oddly in the single case where we have controlFlow true and never have particlesToCreate positive.

Thanks for the help :)

Following up on my idea in last post, I altered the ParticleController.update() method as follows (changes marked SHINGOKI), and don't seem to get the slowdown any more, although since it has always been intermittent I can't be sure yet. I'll be running the code in my game all the time though, so if there is a problem I'll be quite likely to notice it :slight_smile:



I'm not sure the alteration I made has no side effects - I assume that if only the "particles.updateRotationMatrix();" is called, and all the particles are Particle.DEAD, the model bound can't need updating. It seems ok from running so far. Do I get those fancy points and a candy bar if this fixes it? :slight_smile:



    /**
     * Update the particles managed by this manager. If any particles are "dead"
     * recreate them at the origin position (which may be a point, line or
     * rectangle.) See com.jme.scene.Controller.update(float)
     *
     * @param secondsPassed
     *            float
     */
    public void update(float secondsPassed) {
       
       //Only update if active
        if (isActive()) {
           
           //Add time and unless we have more than precision time passed
           //since last real update, do nothing
            currentTime += secondsPassed * getSpeed();
            timePassed = currentTime - prevTime;
            if (timePassed < precision * getSpeed()) {
                return;
            }
           
            //We are actually going to do a real update,
            //so this is our new previous time
            prevTime = currentTime;

            // Update the current rotation matrix if needed.
            particles.updateRotationMatrix();
           
            //If we are in the time window where this controller is active
            //(defaults to 0 to Float.MAX_VALUE for ParticleController)
            if (currentTime >= getMinTime() && currentTime <= getMaxTime()) {

               //If we are controlling flow
                if (controlFlow) {
                   //Release a number of particles based on release rate,
                   //timePassed (already scaled for speed), variance
                   //This is added to any current value
                   //Note this is a float value, so we will keep adding up partial particles
                    releaseParticles += (particles.getReleaseRate() *
                        timePassed * (1.0f + releaseVariance *
                            (FastMath.nextRandomFloat() - 0.5f)));
                   
                    //Try to create all "whole" particles we have added up
                    particlesToCreate = (int) releaseParticles;
                   
                    //If we have any whole particles, then subtract them
                    //from releaseParticles
                    if (particlesToCreate > 0)
                        releaseParticles -= particlesToCreate;
                    //Make sure particlesToCreate is not negative
                    else
                        particlesToCreate = 0;
                }

                particles.updateInvScale();

                //If we have any influences, prepare them all
                if (influences != null) {
                    for (ParticleInfluence influence : influences) {
                        influence.prepare(particles);
                    }
                }
               
                //Track particle index
                int i = 0;
               
                //Track whether the whole set of particles is "dead" - if any particles are still
                //alive, this will be set to false
                boolean dead = true;
               
                //SHINGOKI ADDED               
                boolean anyAlive = false;
               
                //i is index through all particles
                while (i < particles.getNumParticles()) {
                   //Current particle
                    Particle p = particles.getParticle(i);
                   
                    //If we have influences and particle is alive
                    if (influences != null && p.getStatus() == Particle.ALIVE) {
                       //Apply each enabled influence to the current particle
                        for (int x = 0; x < influences.size(); x++) {
                            ParticleInfluence inf = influences.get(x);
                            if (inf.isEnabled())
                                inf.apply(timePassed, p, i);
                        }
                    }
                   
                    //Update and check the particle.
                    //If this returns true, indicating particle is ready to be reused,
                    //we may reuse it. Do so if we are not using control flow, OR
                    //we intend to create particles based on control flow count calculated above
                    if (p.updateAndCheck(timePassed)
                            && (!controlFlow || particlesToCreate > 0)) {
                       
                       //Don't recreate the particle if it is dead, and we are clamped
                        if (p.getStatus() == Particle.DEAD
                                && getRepeatType() == RT_CLAMP) {
                            ;
                        //We plan to reuse the particle
                        } else {
                           //Not all particles are dead (this one will be reused)
                            dead = false;
                           
                            //If we are doing control flow, decrement particlesToCreate, since we
                            //are about to create one
                            if (controlFlow) {
                                particlesToCreate--;
                            }
                           
                            //Recreate the particle
                            p.recreateParticle(particles.getRandomLifeSpan());
                            p.setStatus(Particle.ALIVE);
                            particles.initParticleLocation(i);
                            particles.resetParticleVelocity(i);
                            p.updateVerts(null);
                        }
                       
                    //Either particle wasn't dead, or we don't want to create a particle, so
                    //we are still alive (Is this actually right?)
                    } else {
                        dead = false;
                    }
                   
                    //SHINGOKI ADDED
                    if (p.getStatus() == Particle.ALIVE) {
                       anyAlive = true;
                    }
                   
                    //Next particle
                    i++;
                }
               
                //If we are dead, deactivate and tell our listeners
                if (dead) {
                    setActive(false);
               if ( listeners != null && listeners.size() > 0 ) {
                  for ( ParticleControllerListener listener : listeners ) {
                     listener.onDead( particles );
                  }
               }
                }

                //If we have a bound and any live particles, update it
                if (particles.getBatch(0).getModelBound() != null) {
                    //SHINGOKI ADDED condition
                   if (anyAlive) {
                      particles.updateModelBound();
                      particles.updateWorldBoundManually();
                   }
                }

            }
           
        }
    }

Me again  :smiley:



I tried changing the fog start to be close to the end, so no fog is applied until 900 units. This makes the smoke look ok much further away, I'm guessing that the blockiness/alpha only happens when the particles are actually "shaded" by the fog, not just when a fog state is applied in general.

Nice work!  Also, if you put together a issue report with the particle fix, you can get GP for contest#2.  :slight_smile:

So I've applied the changes, cleaned up a bit and looked through things.  Also clarified the dead status for flowcontrol when releaserate is 0, which may make the anyAlive redundant, but shouldn't be a big issue.  Will be in CVS soon I expect.

Excellent, thanks :slight_smile: I just noticed the change when I got home, everything seems to run well, I don't know whether to be pleased or worried to have some changes in CVS :wink:



I'm gonna use my GP to buy a bronze sword and helmet, so I can finally kill those orcs in Level 2! Or is it not that kind of GP?



Did you have any thoughts about what could be causing the blockiness thing with the fog? I was thinking I might try some plain quads in fog to see if they do the same thing as particle quads.

Ahhh I see :slight_smile:

It took me a while to work out what you meant because I hadn't really thought about how the fog was working, but it all makes sense now, thanks :slight_smile:



I did start out with a normal blend mode using alpha, but I'm not sure quite how the alpha works on particles, the effect wasn't what I was anticipating. I'm going to have another look now though. If I can just get my alpha texture to be faded out, I can have nicer looking smoke (with some colour variation) without the fog issue, so it's worth a try.

It's all fixed now  :smiley: Thanks again for the help. I'm not sure what I got wrong the first time, but normal alpha blending works fine now and looks much nicer.

good to hear! looking forward to seeing more nice screenshots from your game as you progress :slight_smile:

MrCoder said:

looking forward to seeing more nice screenshots from your game as you progress :)


Me too!  As even the screenies with the borked particles looked cool  XD
some options you have:


Sorry to drag up an old thread, but there is a fourth option:
- set fog color to black, for particles only

This will cause the particles to totally fade away when they are fully fogged (as there is no subtraction); thus the effect of the particles will be fogged out in the distance just like the effect of the ground and other objects. I would suggest that as the best option.