2

I'm using the ShaderParticleEngine library for Three.js to create particle emitters.

I picked several code snippets on the Internet to have a working emitter. Firstly I believed that is wasn't working.

But in fact, the emitter was displayed on the map, but a single motionless particle was on the screen. After some debugging I undestood that the particle was moving but infinitely slowly. I need to use tick(delta * 1000) to see the emitter in action. And the result is quite ugly (full of gaps, alone particles).I have no problem of low FPS.

The only solution I found is to remove delta argument in the tick function call: particleGroup.tick().

The result is better but is still deceiving, judge by yourself:

Online Emitter Editor: Editor

My result: Ingame

I can't understand. I use the same code proposed in the library examples and I use the export feature in the emitter editor. If I try other variations (eg. on particle life/velocity) I get a very different result in my game, maybe the particle life is not computed correctly because delta argument isn't given?

My game loop:

var animate = function () {

    requestAnimationFrame( animate );

    render();

    stats.update();

};

var render = function() {

    time = ctx3d.clock.getElapsedTime();
    delta = ctx3d.clock.getDelta();

    particleGroup.tick(delta);

    if(ctx3d.move)
    {
        ctx3d.ship.position.z += delta * 500 * 3000;
        //ctx3d.camera.position.x = ctx3d.ship.position.x;
        //ctx3d.camera.position.z = ctx3d.ship.position.z;
    }

    ctx3d.renderer.render(ctx3d.scene, ctx3d.camera);

}

Delta value loop by loop:

30.0000010000003385357559 9.999985195463523e-7 30.0000020000006770715117 0.0000010000003385357559 30.0000020000006770715117 0.0000010000003385357559 0.0000020000006770715117 30.0000010000003385357559 0.000002999999196617864 0.0000010000003385357559 9.999985195463523e-7 0.000002999999196617864 0.0000010000003385357559 0.000001999998858082108 0.0000010000003385357559 20.0000020000006770715117 9.999985195463523e-7 0.0000010000003385357559

Morgan Touverey Quilling
  • 4,181
  • 4
  • 29
  • 41
  • I'm sorry I don't have a solution for you. but the bottom one would be an awesome 'pulse jet' particle emitter if the spots moved from left to right. Do you have this up anywhere I can take a peek at it? – gnarbarian Nov 03 '14 at 21:40
  • Ha ha :) Can you just reformulate your last sentence? (I'm french and my English is a little poor.) If I understand you want to see it online? I'm afraid the graphical result depends a lot of the machine, it's a matter of time & deltas. – Morgan Touverey Quilling Nov 03 '14 at 21:51
  • You understood perfectly. Regardless, I would still love to see it on jsfiddle or something. :) What online emitter editor are you using? What are you making by the way? – gnarbarian Nov 03 '14 at 22:17
  • Couple of things: the `delta` or `dt` argument for `particleGroup#tick` takes a value in seconds (eg. 0.016s at 60fps). This is because THREE.Clock outputs a deltaTime value in seconds, and I figured that would be a good base to use. – SquareFeet Nov 03 '14 at 22:30
  • 1
    About the lack of smoothness on your emitter: it will probably be down to you not maintaining a consistent framerate. I noticed in the screenshots you posted on your Github issue, the FPS was around 53fps. This might be causing issues, since your `dt` value will be jumping around a little. I would love for the emitter to emit particles at equal rates despite jumping deltaTime values, but the emitter can only do its thing when it gets told to (via `tick`), and unless I think of a strange way to "fill in the blanks" without leaving gaps, or you stabilise your framerate, no dice! – SquareFeet Nov 03 '14 at 22:34
  • 1
    By the way, @gnarbarian: the editor and emitter library Morgan is using is a little side-project of mine: https://squarefeet.github.io/ShaderParticleEngine/ – SquareFeet Nov 03 '14 at 22:43
  • Ok, that's a good explanation. But how do you do to stabilize FPS? Stop use of requestAnimationFrame and do my own framerate-control function? @gnarbarian: I can give you the emitter configuration, the rest of the code is useless for you. I'm making an open-source game where the player is an AI, I decided to migrate from EaselJS to Three yesterday, so I'm doing some experiments. – Morgan Touverey Quilling Nov 03 '14 at 22:43
  • @SquareFeet: Yeah, the variable framerate can explain that, but how to explain that the emitter is infinitely slow when I pass the delta argument to tick()? The screenshot above uses no argument in tick(). If getDelta returns seconds, why sometimes it returns 30.0000010000003385357559 or 0.0000010000003385357559 ? – Morgan Touverey Quilling Nov 03 '14 at 22:49
  • Well, without seeing the internals of `ctx3d.clock` I can't debug that for you ;) Try replacing `ctx3d.clock` with an instance of `THREE.Clock`, and using `.getDelta()` on that clock instance as the argument for `particleGroup.tick()`? – SquareFeet Nov 03 '14 at 23:02
  • ctx3d.clock is an instance of THREE.Clock ;) `this.clock = new THREE.Clock();` that's all! – Morgan Touverey Quilling Nov 03 '14 at 23:13
  • I understood why the delta value were inconsistent and very low. That's because I call getElapsedTime() before getDelta(). Inverse the lines of the two functions solved the problem. – Morgan Touverey Quilling Nov 05 '14 at 13:49

2 Answers2

3

To solve smoothness, try the following:

function makeSmoothSPETick(simulator, timeDelta, maxSubstepSize){
  var numSubsteps = Math.floor(timeDelta/maxSubstepSize);
  var leftOverTime = timeDelta%maxSubstepSize;
  while(numSubsteps-->0){
      simulator.tick(maxSubstepSize);
  }
  if(leftOverTime> 0){
      //handle the rest
      simulator.tick(leftOverTime);
  }
}

If you use this function in your code - it will allow you to essentially subdivide steps that are too large into smaller ones of fixed size. As SquareFeet pointed out, say 16ms for 60FPS - you could use something like this:

var render = function() {

    time = ctx3d.clock.getElapsedTime();
    delta = ctx3d.clock.getDelta();

    makeSmoothSPETick(particleGroup, delta, 0.016);

    if(ctx3d.move)
    {
        ctx3d.ship.position.z += delta * 500 * 3000;
        //ctx3d.camera.position.x = ctx3d.ship.position.x;
        //ctx3d.camera.position.z = ctx3d.ship.position.z;
    }

    ctx3d.renderer.render(ctx3d.scene, ctx3d.camera);

}

You should get results visually similar to what you'd expect if you were running at smooth 60fps. Beware though, if target hardware can't handle these substeps - you may need to get more logic into your solver algorithm. I'd suggest keeping statistics for past 100 frames or so, and using that to decide how much you can split your incoming step value.

EDIT: To make sure your timing isn't getting mangled, please try the following:

var lastFrameTime = Date.now()/1000;
var animate = function () {

    requestAnimationFrame( animate );

    render();

    stats.update();

};

var render = function() {

    time = Date.now()/1000; //getting current time in seconds since epoch
    delta = time-lastFrameTime;
    lastFrameTime = time;

    particleGroup.tick(delta);

    if(ctx3d.move)
    {
        ctx3d.ship.position.z += delta * 500 * 3000;
        //ctx3d.camera.position.x = ctx3d.ship.position.x;
        //ctx3d.camera.position.z = ctx3d.ship.position.z;
    }

    ctx3d.renderer.render(ctx3d.scene, ctx3d.camera);

}
travnik
  • 690
  • 7
  • 18
  • Also, i can see clear banding on your particle trail at regular intervals, so I would guess some position updates are being triggered at those band boundaries (elsewhere in your code probably). – travnik Nov 03 '14 at 22:59
  • Thanks for you help. _(Warning, there's a typo in your code on Math.floow)_ Unhappily the result is the same as a simple tick(delta), all I get is a single, non-moving particle in my universe: http://i.imgur.com/fsuqEUv.png Btw, I'm not changing the emitter position or anything else. – Morgan Touverey Quilling Nov 03 '14 at 23:08
  • @MorganTouvereyQuilling: Yep, this is because the emitter's tick code already tries to do this vague approximation of 'catching up' for you (see: https://github.com/squarefeet/ShaderParticleEngine/blob/master/src/ShaderParticleEmitter.js#L245-L252). The issue with this, though, is that it will not be able to emit particles *between frames*. I do wonder if I could set a particle's age to be correct despite it being emitted at the same time as other particles... Let me experiment and I'll get back you! – SquareFeet Nov 03 '14 at 23:17
  • @MorganTouvereyQuilling: Quick explanation of why this idea might work: The shaders doing the particle movement are all stateless, so a particle's position is dependant on the forces acting upon it as a function of the age of the particle, i.e. time. (see: https://github.com/squarefeet/ShaderParticleEngine/blob/master/src/ShaderParticleGroup.js#L438-L445). If I set the particle's age to be slightly offset according to when it *should* have been emitted, I think I can make you some smooth emitters... Maybe! – SquareFeet Nov 03 '14 at 23:21
  • Ok SquareFeet, thanks! @travnik: interesting, with the use of Date, the emitter works almost as well as in the editor: http://i.imgur.com/pwBvtHO.png – Morgan Touverey Quilling Nov 03 '14 at 23:23
1

I hope posting this as an answer is okay...

I've bumped the particle engine up a minor version to 0.7.7, having implemented a fix for your issue of "not-very-smooth-looking" emitters.

What was happening before was this:

  • SPE.Emitter.tick() called with a dt value
  • This tick function determines how many particles should be marked alive based on the dt argument passed to it. For larger dt values, more particles are marked as alive, for smaller values fewer are marked as alice.
  • The emitter then resets these particles and waits for the next call.

Assuming more than one particle is going to be marked as alive per frame, and they all originate at the same position in space, then all the particles will be at the same place when they're activated. This is why you saw some "clumping" happening.

What happens now is this:

  • SPE.Emitter.tick() called with a dt value, just as before.
  • The tick function now determines how many particles should be marked as alive, and whilst marking them so, sets each particles age to be a fraction of the dt value passed in.

So (!), assuming 100 particles are emitted per frame, and a dt value of 0.016 is passed to the emitter's tick function, each of those 100 particles that will be marked as alive is assigned an age value of (0.016 / 100) * i where i is the particle index (in this case, a value of 0 to 100).

I hope that makes sense. You can see the changes here: https://github.com/squarefeet/ShaderParticleEngine/blob/master/src/ShaderParticleEmitter.js#L240-L246

Master branch has been updated.

SquareFeet
  • 651
  • 4
  • 16
  • Thanks! That works good with a delta of 0.016. http://i.imgur.com/QbSvALe.png But when I use Clock.getDelta I have the same problem as before (http://i.imgur.com/RYTrbXm.png) do you think there's a bug in Three.js? With the use of Date as showed by travnik in his answer edit, it works. Not very well, but it works. Again, thanks a lot for your work!! – Morgan Touverey Quilling Nov 04 '14 at 09:43
  • Another question: is it normal that the emitter origin seems to be a little different depending on the camera position? – Morgan Touverey Quilling Nov 04 '14 at 12:25