Skip to main content
GameDev.net gamedev.net
🔒 Locked 🎮 Unity

First time particle engine (Java / LWJGL)

Started by joeyismusic Aug 29, 2011 at 11:28 AM 6 replies 5.8k views
Original Post
joeyismusic
joeyismusic
Just wondering what any experienced people think about this implementation of a crude point based particle system and how I could improve it (I know my math is really terrible).

A single engine class can do about 9000 particles with 1300 FPS (vsync off, game loop fixed at 100 frames per second) on my machine (but i have really high end hardware).

Anyways, is there a better random function I can use that won't require jars or libs or DLL's that's cross platform??

here's the code:



package joeysturgis.simple2d.tileengine;

import static org.lwjgl.opengl.GL11.*;

public class ParticleEngine {

int particlecount=0;
particle[] particles;

private double x = 0;
private double y = 0;

private double r = 0;
private double g = 0;
private double b = 0;
private double a = 0;
private double applygravity = 0;

private boolean emitter = false;
private boolean randomcolor = false;

private double speed = 0;
private int tornado = 0;
private boolean pause = false;

private class particle {

Timer timer = null;
private ParticleEngine daddy = null;
private double x = 0.0D;
private double y = 0.0D;
private double speed = 1.001;

private double r = 1.0D;
private double g = 0;
private double b = 0;
private double a = 1;

private double lifespan = 1000;

private boolean dead = false;

public particle(ParticleEngine dad){
daddy = dad;
timer = new Timer();
timer.startTimer();
if(daddy.randomcolor){
r = (Math.random()*100) % 3;
g = (Math.random()*100) % 3;
b = (Math.random()*100) % 3;
a = (Math.random()*100);
}
else{
r = daddy.r + (Math.random()/10);
g = daddy.g + (Math.random()/10);
b = daddy.b + (Math.random()/10);
a = daddy.a + (Math.random()/10);
}
}

private void update() {
if(pause) {
timer.pauseTimer();
return;
}
timer.unpauseTimer();
if(Math.random()>.5){
x += 1*Math.random();
}
else{
x -= 1*Math.random();
}
if(Math.random()>.5){
y += 1*Math.random();
}
else{
y -= 1*Math.random();
}
if(timer.getTime() >= lifespan / (Math.random()*Math.random())){
kill();
return;
}
double tornadocalc = Math.random();
if(tornadocalc>.5) { x += daddy.tornado/100; }
else { x -= daddy.tornado/100; }
x *= (speed+daddy.speed);
y *= (speed+daddy.speed);
if(daddy.applygravity!=0){
y += 0.01*daddy.applygravity;
}

}
private void draw() {
glColor4d(r,g,b,a);
//glPointSize((float)(Math.random() *10));
glVertex2d(daddy.x+x, daddy.y+y);

}
private void move(double x, double y){
this.x=x;
this.y=y;
}
private void kill(){
this.timer = null;
dead=true;
}
}

public ParticleEngine(int size, double r, double g, double b, double a, boolean emit) {
if(size<=0) {
return;
}
particlecount = size;
this.r = r;
this.g = g;
this.b = b;
this.a = a;
emitter = emit;
generate();
}

public ParticleEngine(int size, double r, double g, double b, double a, boolean emit, boolean randomcolor) {
if(size<=0) {
return;
}
particlecount = size;
this.r = r;
this.g = g;
this.b = b;
this.a = a;
emitter = emit;
this.randomcolor=randomcolor;
generate();

}

private void generate() {
particles = new particle[particlecount];
for(int i = 0; i < particlecount; i++) {
particles = new particle(this);
}
}

public void update() {
for(int i = 0; i < particlecount; i++) {
if(particles != null){
if(particles.dead){
particles.kill();
particles = null;
}
else{
particles.update();
}
}
else{
particles = null;
if(emitter)
{
particles = new particle(this);
}
}
}
}

public void draw() {
glEnable(GL_POINT_SMOOTH);
glEnable(GL_BLEND);
glPointSize(10.0f);
glBlendFunc(GL_SRC_ALPHA, GL_DST_COLOR);

glBegin(GL_POINTS);

for(int i = 0; i < particlecount; i++) {
if(particles != null){
particles.draw();
}
}

glEnd();
glDisable(GL_BLEND);
}

public void move(double x, double y){
this.x = x;
this.y = y;
}

public void reset(){
for(int i = 0; i < particlecount; i++){
if(particles != null) particles.move(this.x,this.y);
}
}
public void killme(){
for(int i = 0; i < particlecount; i++){
if(particles!=null) particles.kill();
particles = null;
}
}

public void gravity(double i){
applygravity +=i;
}
public void setspeed(double speed){
this.speed = speed;
}
public void tornado(int i){
this.tornado+=i;
if(tornado<0) tornado=0;
}
public void pause(){
if(pause) {
pause = false;
}
else {
pause = true;
}

}
}


Danny02
Danny02
Ok there are two things to have a look at, first the software design and second how u used the graphic API (here OpenGL).

SW:

I noticed several points.

general:
  • use the standard Java naming convention pls (upper case Classes)
  • don't use inner classes
  • use data classes like vectors for position, velocity & color)
    about your desgin
    • am I right that each particle is creating his own Thread just to know what time it is? Just do the update with a given delta time.
    • I would put the update logic in the particle container(ParticleEngine) not in the particles. Or create a particle interface so each particle can have its own update function, but with this u can't have interaction between particles.

API:

I see u are using OpenGL 1, it would be way faster using Buffer Objects for the particles.



Also very important you aren't sorting your particles which give u wrong results if not additive blending, what u aren't using.

ps: pls use milliseconds to messure your perfomance and not FPS, because this metric is a lot more comparable(i.e. 20->40 fps is a much higher speedup then from 120->140FPS)
joeyismusic
joeyismusic

Ok there are two things to have a look at, first the software design and second how u used the graphic API (here OpenGL).

SW:

I noticed several points.

general:
  • use the standard Java naming convention pls (upper case Classes)
  • don't use inner classes
  • use data classes like vectors for position, velocity & color)
    about your desgin
    • am I right that each particle is creating his own Thread just to know what time it is? Just do the update with a given delta time.
    • I would put the update logic in the particle container(ParticleEngine) not in the particles. Or create a particle interface so each particle can have its own update function, but with this u can't have interaction between particles.

API:

I see u are using OpenGL 1, it would be way faster using Buffer Objects for the particles.



Also very important you aren't sorting your particles which give u wrong results if not additive blending, what u aren't using.

ps: pls use milliseconds to messure your perfomance and not FPS, because this metric is a lot more comparable(i.e. 20->40 fps is a much higher speedup then from 120->140FPS)


Thanks for the reply. My fps is measured like this...



public void dofps() // calculate fps
{
iFPS++;
if(fpsTimer.getTime() >= 1000)
{
//iFPS = (int)(lTotalFrames / (frameTimer.getTime() / 1000));
//System.out.println(iFPS);
iAvgFPS=iFPS;
iFPS = 0;
fpsTimer.startTimer();
}
//This is only called when the game runs "draw()" which is only "drawing" stuff.

The game loop is...




private void run() // the game loop
{
bIsRunning = true;
double next_game_tick = lCurrentTime;
while(bIsRunning && !Display.isCloseRequested())
{
if(!dotime())
{
break;
}
int loops = 0;
while(lCurrentTime > next_game_tick && loops < 10)
{
update();
next_game_tick += 10;
loops++;
lTotalFrames ++;
}
draw();
dofps();
}
bIsRunning = false;
destroy();
}

public boolean dotime() //calculate time for the frame, also determine if time hacking
{
lCurrentTime = System.currentTimeMillis();
if(lCurrentTime >= lTimeStarted) // current time should always be larger than time started, or player is time traveling O_o
{
lTotalTime = lCurrentTime - lTimeStarted; //keep track of time... accurately
return true;
}
else
{
ErrorClass.ReportError("You are time traveling!"); //back to the future
stop(); return false;
}
}



so yeah i'm actually getting 1000+ fps from what I can tell.

My timer class just returns a subtraction operation...



public long getTime()
{
if(!bStarted)
{
return -1;
}
if(bPaused)
{
return lTimePaused;
}
return (Game.getCurrentTime() - lTimeStarted);
}


and game object is public static final (so there's only one) and getCurrentTime() just returns a variable.
Rene Z
Rene Z
May I ask why you think you need a better random function? Also, define better.


Concerning style I agree with Danny02, and here's a few more tips:

- Rename ParticleEngine to ParticleEmitter, it's more descriptive of what it does.

- Don't create a timer for every particle. It causes a lot of duplicate calculations, and wastes memory. Particle systems is one of the areas where this can really matter. Pass the update time as a parameter to the 'update' method. This also makes it easier to control the update speed, which is useful for things like pause and slow motion.

- Use floats instead of doubles in you non-OpenGL code, unless you really need their precision. Always avoid doubles in your OpenGL code.

- Remove the 'if(daddy.randomcolor)' branch from the particle constructor. Pass the color to the constructor, or add a method to ParticleEngine which returns an appropriate color.

- Remove the null check from your ParticleEngine update loop. There are two things you can do, depending on how 'clean' you want your code to be. The clean solution is to remove dead particles from your particles array, and add new particles later. The other option is to re-use instances, which may be faster depending on some other factors, but is more error-prone. (Not using a particle class at all is a good option too, but I won't get into that now). Either way, you should keep all living particles in the first part of the array, and the dead at the end. Remove the particle 'dead' member. The kill method will look something like:

numberOfLivingParticles--;
particles[indexOfParticleToKill] = particles[numberOfLivingParticles];
particles[numberOfLivingParticles] = null; // If creating new particles, or
particles[numberOfLivingParticles] = particleToKill; // if re-using instances


- Remove the alive/dead branch from your update loop. Branching in tight loops should be avoided. If you keep the particle array sorted, the update function can look something like:

for(int i = 0; i < numberOfLivingParticles, ++i){
// Update the particle i. Keep in mind that if the particle removes itself during the update, the particle
// copied over the current particle will not be updated this frame. Usually not a problem, but if it is that should be easy to fix.
}

for(int i = numberOfLivingParticles; i < sizeOfParticlesArray; ++i) { // Or spawn x particles per second.
// make new living particles, depending on if you're creating new instances or reusing them.
}


Please let me know if you have any questions.

EDIT:
I forgot, if you want meaningful data when measuring performance, simulate enough particles to stress your system. Go for an update time of about thirty milliseconds.
joeyismusic
joeyismusic

May I ask why you think you need a better random function? Also, define better.


Concerning style I agree with Danny02, and here's a few more tips:

- Rename ParticleEngine to ParticleEmitter, it's more descriptive of what it does.

- Don't create a timer for every particle. It causes a lot of duplicate calculations, and wastes memory. Particle systems is one of the areas where this can really matter. Pass the update time as a parameter to the 'update' method. This also makes it easier to control the update speed, which is useful for things like pause and slow motion.

- Use floats instead of doubles in you non-OpenGL code, unless you really need their precision. Always avoid doubles in your OpenGL code.

- Remove the 'if(daddy.randomcolor)' branch from the particle constructor. Pass the color to the constructor, or add a method to ParticleEngine which returns an appropriate color.

- Remove the null check from your ParticleEngine update loop. There are two things you can do, depending on how 'clean' you want your code to be. The clean solution is to remove dead particles from your particles array, and add new particles later. The other option is to re-use instances, which may be faster depending on some other factors, but is more error-prone. (Not using a particle class at all is a good option too, but I won't get into that now). Either way, you should keep all living particles in the first part of the array, and the dead at the end. Remove the particle 'dead' member. The kill method will look something like:

numberOfLivingParticles--;
particles[indexOfParticleToKill] = particles[numberOfLivingParticles];
particles[numberOfLivingParticles] = null; // If creating new particles, or
particles[numberOfLivingParticles] = particleToKill; // if re-using instances


- Remove the alive/dead branch from your update loop. Branching in tight loops should be avoided. If you keep the particle array sorted, the update function can look something like:

for(int i = 0; i < numberOfLivingParticles, ++i){
// Update the particle i. Keep in mind that if the particle removes itself during the update, the particle
// copied over the current particle will not be updated this frame. Usually not a problem, but if it is that should be easy to fix.
}

for(int i = numberOfLivingParticles; i < sizeOfParticlesArray; ++i) { // Or spawn x particles per second.
// make new living particles, depending on if you're creating new instances or reusing them.
}


Please let me know if you have any questions.

EDIT:
I forgot, if you want meaningful data when measuring performance, simulate enough particles to stress your system. Go for an update time of about thirty milliseconds.



Thanks for your reply,

I'm not sure I understand the timing problems you mentioned...

If each particle doesn't have its own timer (which is honestly just a few long variables), then how can each particle have its own unique start and end time?



class MyTimer
{
long timestarted = 0;

MyTimer() {
timestarted = Game.getCurrentTime();
}

void starTimer(long time) {
timestarted = time;
}

long getTime() {
return Game.getCurrentTime() - timestarted;
}
}

class Particle
{
MyTimer timer = null;
long lifespan = 1000;

Particle() {
timer = new MyTimer();
}

public boolean getIsDead() {
if(timer.getTime() >= lifespan){
return true;
}
return false;
}
}


What is wrong with that implementation? It's basically what I have....

and since this post, I rewrote the emitter to just re-use particles.
joeyismusic
joeyismusic
here's a video of it in action with vsync enabled

Rene Z
Rene Z
The video looks nice, how many points are that?

Making the update time a parameter of update gives you a lot of flexibility, like a variable time step so the game runs fine on slower systems and control over the speed of the animation (the slow motion effect I mentioned). Pausing the game can be a problem with your current approach because it looks like you use a single source of time info for every system in the engine (Game.getCurrentTime()). If every system in the game uses this as a source for the current time, you can't pause the game but animate the buttons of an overlay menu in an easy manner. Specifying the update time yourself allows you to use different timers for different parts of the game.

A simple way to handle the life span of your particles is keep a single float member called lifeTime or something similar, which keeps track of the time the particle has left until it dies. In update, all you have to do is:

if(lifeTime <= 0.f) {
killParticle();
}
lifeTime -= timePerFrame;


You can use the same method with longs/ints too, if you wish.
joeyismusic
joeyismusic
The video looks nice, how many points are that?Making the update time a parameter of update gives you a lot of flexibility, like a variable time step so the game runs fine on slower systems and control over the speed of the animation (the slow motion effect I mentioned). Pausing the game can be a problem with your current approach because it looks like you use a single source of time info for every system in the engine (Game.getCurrentTime()). If every system in the game uses this as a source for the current time, you can't pause the game but animate the buttons of an overlay menu in an easy manner. Specifying the update time yourself allows you to use different timers for different parts of the game.A simple way to handle the life span of your particles is keep a single float member called lifeTime or something similar, which keeps track of the time the particle has left until it dies. In update, all you have to do is:if(lifeTime <= 0.f) { killParticle();}lifeTime -= timePerFrame;You can use the same method with longs/ints too, if you wish.
Thanks! The animated quads is 200 quads (my system was handling 2000 at one point). and the Points is 9000.Also, thanks for your ideas and contributions. I am going to try and wrap my head around your optimizations. I do not intend in any way to use this system I have wrote for use in an actual graphics engine. I just wanted to see if how I thought particle systems worked was the actual way that they work.

Also I dont think you understand how my timer works. because I can pause anything on the screen, including individual particles.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.