Influencer-based ParticleEmitter candidate (mesh-based animated particles)

@nehon
I did take @normen 's advice and try started mostly from scratch, keeping the classes that were pointless to rewrite (Particle related classes and the Particle class itself)

The ParticleEmitter is no longer a Geometry, it’s a Control… and it’self the only control…
Particles are not individual controls anymore.
Initializing and using an emitter is really simple now:

To get one running you only need these 4 lines of code:
[java]
ParticleEmitter p = new ParticleEmitter(“Emitter”, assetManager, ParticleMesh.Type.Triangle, 18000);
p.setSprite(“Textures/emittersprite.png”, 64,64);
someModel.setControl§;
someModel.attachChild(p.getNode()); // or whereever you want to add it.
[/java]

Alternatively, you can use a static mesh that is not added to the scene as a non-animated emitter shape. For more extensive control over the emitter, it looks sorta like this:

[java]
// Set up the basic emitter params
ParticleEmitter p = new ParticleEmitter(“Emitter”, assetManager, ParticleMesh.Type.Triangle, 18000);
p.setSprite(“Textures/emittersprite.png”, 64,64);
p.setEmissionsPerSecond(400);
p.setParticlesPerEmission(10); // This is also calculated when an emission takes place, adding/subtracting to the count to ensure the right number of particles are being emitted. It allows the emitter to emit more particles than what your FPS would allow like in the current version
p.setForceMinMax(.35f,.75f);
p.setLifeMinMax(3f,.5f);

// Retrieve and modify specific influencers
((GravityInfluencer)p.getInfluencer(“Gravity”)).setGravity(0,2,0);
((ColorInfluencer)p.getInfluencer(“Color”)).setStartColor(new ColorRGBA(.4f,.4f,1f,1f));
((ColorInfluencer)p.getInfluencer(“Color”)).setEndColor(new ColorRGBA(1,1,1,0.2f));
((SizeInfluencer)p.getInfluencer(“Size”)).setStartSize(.15f);
((SizeInfluencer)p.getInfluencer(“Size”)).setEndSize(.05f);
((ImpulseInfluencer)p.getInfluencer(“Impulse”)).setEnabled(true);
((PreferredDirectionInfluencer)p.getInfluencer(“PreferredDirection”)).setPreferredDirection(new Vector3f(0,1,0));

// Add a static mesh, add the control and node
p.setShape(someMesh);
rootNode.setControl§;
rootNode.attachChild(p.getNode());
[/java]

2 Likes

Some more general info on changes and a walkthrough of the updateloop:

One of the big changes in the emitter is how it is handling timing it emissions. In the current emitter, it is using while (some condition) to keep the emission for happening until it should… unfortunately, these are in the control update loops and stops your entire game from doing anything as well. This has been change to track intervals and also allow for more than one particle to be emitted at a time. The basic flow of the update loop goes like this:

The emitter update loop is cal:

[java]
It first updates all active particles
for (Particle p : particles) {
if (p.active) p.update(tpf);
}

// then tracks the interval for emitting new particles
currentInterval += tpf;

if (currentInterval >= targetInterval) {
particlesPerEmission = calcParticlesPerEmission();
for (int i = 0; i < particlesPerEmission; i++) {
emitNextParticle();
}
currentInterval -= targetInterval;
}

((Geometry)node.getChild(0)).updateModelBound();
[/java]

emitNextParticle initializes the particle, which in turn has all active influencers help initialize it's data. Some influencers run their initialization and never do anything else. Continuing on… the Particle classes update loop does the following:

[java]
// decrease it's life counter
life -= tpf;
if (life <= 0) {
reset();
return;
}
// Calculates a blend for use with influencer interpolation calls
blend = (startlife - life) / startlife;

// Cycles through the influencers allowing them to update the particles data
for (ParticleInfluencer influencer : emitter.getInfluencers()) {
influencer.update(this, tpf);
}
[/java]

Lastly, an inluencer's update method, looks like this:

[java]
if (enabled) {
// The original code from one of the update methods of the current emitter (in the case of Gravity)
p.velocity.x -= gravity.x * tpf;
p.velocity.y -= gravity.y * tpf;
p.velocity.z -= gravity.z * tpf;
temp.set(p.velocity).multLocal(tpf);
p.position.addLocal(temp);
}
[/java]

And that's pretty much it. There is no use of new etc, etc, in the stock influencers… they all use stores that are generated with the influencer is instantiated.

Sooo… the reason I threw this out here was so you (or anyone else could point out potential design flaws. I'm really happy with the outcome thus far… as I can run an emitter with 30,000 particles and spam emitAllParticles and my crappy machine is still getting 300+ FPS.

2 Likes

At the rate you’re going you’ll be part of the core pretty soon. :stuck_out_tongue:

I, for one, would welcome you as our newly added overlord. :wink:

1 Like

That’s pretty good!
i don’t get this though : someModel.attachChild(p.getNode());

1 Like

That’s awesome, this new particle system will change my life :)…as usual Good Job!!!

However, as nehon said:

[java]rootNode.setControl§;
rootNode.attachChild(p.getNode());[/java]

…shocks me a little…why don’t you manage the attachChild inside the control…and just play with a setEnable to activate or deactivate?

1 Like

Believe it or not, there is a reason for it… it’s probably a poorly thought out reason… but a reason none the less. lol

Here is what I was attempting to do with it (and was going to cry for assistance on this one soon).

setSpatial tries to determine whether or not the Node you’re adding it to as a control contains the Mesh w/ animation you want as your emitter shape.
If it finds what it expects, it sets the shape and then sets up it’s own Node, Geometry + ParticleMesh that you can attach where you like in your scene graph.
Otherwise, the Control just needs some node that will add it to the update loop and Mesh (shape of the emitter) can be set to some model that is not associated with the scene in any way.

I think it’s awesome that you all picked thoseline out, because the solution was a 5 minute throw-together and a total Hail Mary =)

Any options for how to make that work in a smother fashion would be really appreciated!

1 Like

Looking very nice, although I do have a couple of comments that could improve the API/etc a bit. I hope this makes sense, don’t hesitate to ask if anything isn’t clear though.

@t0neg0d said:

[java]
// Set up the basic emitter params
ParticleEmitter p = new ParticleEmitter(“Emitter”, assetManager, ParticleMesh.Type.Triangle, 18000);
p.setSprite(“Textures/emittersprite.png”, 64,64);
p.setEmissionsPerSecond(400);
p.setParticlesPerEmission(10); // This is also calculated when an emission takes place, adding/subtracting to the count to ensure the right number of particles are being emitted. It allows the emitter to emit more particles than what your FPS would allow like in the current version
p.setForceMinMax(.35f,.75f);
p.setLifeMinMax(3f,.5f);

// Retrieve and modify specific influencers
((GravityInfluencer)p.getInfluencer(“Gravity”)).setGravity(0,2,0);
((ColorInfluencer)p.getInfluencer(“Color”)).setStartColor(new ColorRGBA(.4f,.4f,1f,1f));
((ColorInfluencer)p.getInfluencer(“Color”)).setEndColor(new ColorRGBA(1,1,1,0.2f));
((SizeInfluencer)p.getInfluencer(“Size”)).setStartSize(.15f);
((SizeInfluencer)p.getInfluencer(“Size”)).setEndSize(.05f);
((ImpulseInfluencer)p.getInfluencer(“Impulse”)).setEnabled(true);
((PreferredDirectionInfluencer)p.getInfluencer(“PreferredDirection”)).setPreferredDirection(new Vector3f(0,1,0));

// Add a static mesh, add the control and node
p.setShape(someMesh);
rootNode.setControl(p);
rootNode.attachChild(p.getNode());
[/java]

I’m not a big fan of the getInfluencer(“Color”) pattern here. Id suggest:
[java]
public interface Influencer {
// define the update method here, etc.
// define a “getInfluencerClass” method - this allows you to control what this gets mapped as. For example your ColorInfluencer would return ColourInfluencer.class - and then subclasses of ColourInfluencer still use the same “slot” in the mapping unless they specifically over-ride this
class getInfluencerClass();
} // All influencers implement this
[/java]
The mapping then could be
[java]
Map<class, ? extends Influencer> influencers;

public T getInfluencer(class t) {
return (T)influencers.get(t);
}

public setInfluencer(T t) {
influencers.put(t.getInfluencerClass(), t);
}
[/java]

The syntax may not be exactly right as I just did it off the top of my head, but it gets rid of the string matching and instead matches by class. This both means no strings to remember and that the classes get mapped automatically. No casting needed.

I would also not instantiate the influencers automatically. Instead have the constructor take an Influencer… as the final parameter. You can then do something like

[java]
// Set up the basic emitter params
ParticleEmitter p = new ParticleEmitter(“Emitter”, assetManager, ParticleMesh.Type.Triangle, 18000, new GravityInfluencer(0.2f), new ColourInfluencer(ColorRGBA.Red, ColorRGBA.Yellow));
p.setSprite(“Textures/emittersprite.png”, 64,64);
p.setEmissionsPerSecond(400);
p.setParticlesPerEmission(10); // This is also calculated when an emission takes place, adding/subtracting to the count to ensure the right number of particles are being emitted. It allows the emitter to emit more particles than what your FPS would allow like in the current version
p.setForceMinMax(.35f,.75f);
p.setLifeMinMax(3f,.5f);
[/java]

Influencers can be added/removed etc after if needed but everything is just set up cleanly how you need at the start.

1 Like

Hi again!

I made some first steps in the direction of real-time rendering for fur, grass etc. This vid just shows off the basics of generating the static particles and having them follow the face they were generate from. I’m sure you can piece the possibilities together. Again, influencers could be developed to effect the particle based of a fixed rotation point… think fur moving in wind.

[video]http://youtu.be/EuoBMJxwHQQ[/video]

2 Likes

Damn forums ate my > < and everything in between.

The get/set should have <T> at the start of the line

The class should be class<? extends Influencer>

etc.

2 Likes

@zarch
Sorry for the stupid question… but how does this work when Influencer is an interface?
[java]
Map<class, ? extends Influencer> influencers;
[/java]

1 Like

Or do I change this to an abstract class?

1 Like
@t0neg0d said: @zarch Sorry for the stupid question... but how does this work when Influencer is an interface? [java] Map<class, ? extends Influencer> influencers; [/java]

Because “? extends” is generics wild card notation. It has nothing to do with the “extends” in a class hierarchy.

I just means that value must implement Influence.

1 Like
@pspeed said: Because "? extends" is generics wild card notation. It has nothing to do with the "extends" in a class hierarchy.

I just means that value must implement Influence.

This didn’t work as explained… starting from

[java]
Map<Class, ? extends ParticleInfluencer> influencers
[/java]

On. red squiggly heaven, as you can not extend an interface. What am I missing in the translation here?

1 Like
@t0neg0d said: This didn't work as explained... starting from

[java]
Map<Class, ? extends ParticleInfluencer> influencers
[/java]

On. red squiggly heaven, as you can not extend an interface. What am I missing in the translation here?

Nope: something else is wrong because that’s a perfectly legal statement. Take a screen shot or something because we don’t have enough information to figure out what’s really wrong.

1 Like
@pspeed said: Nope: something else is wrong because that's a perfectly legal statement. Take a screen shot or something because we don't have enough information to figure out what's really wrong.

I’m thinking that the example the @zarch gave got partially eaten by the forum. Maybe he can repost the example, as I would like to change this. The current method of getting influencers is grueling.

EDIT: I should probably also mention that I get a little lost with type casting when using nested < > and extends… brain starts to swim

1 Like
@zarch said: Damn forums ate my > < and everything in between.

The get/set should have <T> at the start of the line

The class should be class<? extends Influencer>

etc.

I think this may be the thing I am missing… questions, so I understand the syntax properly… when you class should be, do you mean that class was not supposed to be Class? i.e.

[java]
public T getInfluencer(class<? extends Influencer> t) {
return (T)influencers.get(t);
}
[/java]

Or am I way off here? The syntax for this throws me every time. I’m sure this will help solidify it in my feeble brain after this is working correctly.

1 Like
@t0neg0d said: I think this may be the thing I am missing... questions, so I understand the syntax properly... when you class should be, do you mean that class was not supposed to be Class? i.e.

[java]
public T getInfluencer(class<? extends Influencer> t) {
return (T)influencers.get(t);
}
[/java]

Or am I way off here? The syntax for this throws me every time. I’m sure this will help solidify it in my feeble brain after this is working correctly.

I don’t know what got eaten, exactly, but that’s wrong in any case.

Because our forum sucks donkey balls, I’ll just do this in my editor and take a screen shot. (Good thing this isn’t a coding forum.)

But really, I think allowing an influencer to be registered for some other class is overkill. I don’t understand why it can’t work like app states, controls, etc… just pass the type of what you are looking for.

2 Likes
@pspeed said: I don't know what got eaten, exactly, but that's wrong in any case.

Because our forum sucks donkey balls, I’ll just do this in my editor and take a screen shot. (Good thing this isn’t a coding forum.)

But really, I think allowing an influencer to be registered for some other class is overkill. I don’t understand why it can’t work like app states, controls, etc… just pass the type of what you are looking for.

Ok… that fixed the issue. Thanks for taking the time to show this!

1 Like

More stuff…

Campfire example… this is really starting to look nice!

[video]http://youtu.be/5F2WpuFEpIw[/video]

2 Likes

@zarch @pspeed
The idea and help were most appreciated… I did what Paul said and followed the example in AppStateManager which simplified the process and provides the same end product.

[java]
// Add new
emiiter.addInfluencer(new ColorInfluencer());
// Get existing
emitter.getInfluencer(ColorInfluencer.class).setStartColor(etc, etc, etc);
[/java]

Hopefully I followed what the end goal was correctly :wink:

2 Likes