/*
 *	(c) 2001, Michel Beaudouin-Lafon, mbl@lri.fr
 *
 *	Simple particle system
 */

#include <stdlib.h>
#include <stdio.h>
#include <GL/glut.h>

inline float random (float mean, float variance)
{
  double res;
  res = 2.0 * (double) rand() / (double) RAND_MAX - 0.5;     // (-1 <= res <= 1)
  return res*variance + mean;
}

// -------- Vector --------

class Vector {
public:
	float x, y, z;
	
	Vector ();
	Vector (float vx, float vy, float vz);
	
	Vector& operator += (const Vector& v);
};

Vector::Vector ()
{
	x = y = z = 0.0;
}

Vector::Vector (float vx, float vy, float vz)
{
	x = vx;
	y = vy;
	z = vz;
}

Vector&
Vector::operator += (const Vector& v)
{
	x += v.x;
	y += v.y;
	z += v.z;
	return *this;
}

Vector
operator * (float a, const Vector& v)
{
	return Vector (a * v.x, a * v.y, a * v.z);
}


// -------- Particle --------

class Particle {
protected:
	float mass;
	Vector position;
	Vector speed;
	Vector force;
public:
	Particle ();
	virtual ~Particle ();
	
	void ResetForce ();
	void AddForce (const Vector& v);
	
	virtual void Step (float dt);
	virtual void Render ();
	
	virtual void Dump ();
};


Particle::Particle ()
: position(), speed(), force()
{
	mass = 1.0;
	speed.x = 50.0 + random(0.0, 10.0);
	speed.y = 50.0 + random(0.0, 10.0);
	speed.z = 0.0;
}

Particle::~Particle ()
{
	// rien
}

void
Particle::ResetForce ()
{
	force = Vector ();
}

void
Particle::AddForce (const Vector& v)
{
	force += v;
}

void
Particle::Step (float dt)
{
	speed += (dt / mass) * force;
	position += dt * speed;
}

void
Particle::Render ()
{
	glBegin (GL_POINTS);
	  glVertex3f (position.x, position.y, position.z);
	glEnd ();
}

void
Particle::Dump ()
{
	printf ("%f %f %f\n", position.x, position.y, position.z);
}


// -------- Field --------

class Field {
public:
	Field ();
	virtual ~Field ();
	
	virtual void ComputeForce (Particle& p) = 0;
};

Field::Field ()
{
	// rien
}

Field::~Field ()
{
	// rien
}

// -------- GravityField --------

class GravityField: public Field {
protected:
	float gravity;
public:
	GravityField ();
	GravityField (float g);
	
	void ComputeForce (Particle& p);
};

GravityField::GravityField ()
{
	gravity = 9.81;
}

GravityField::GravityField (float g)
{
	gravity = g;
}

void
GravityField::ComputeForce (Particle& p)
{
	p.AddForce (Vector (0.0, -gravity, 0.0));
}


// -------- System --------

#define MAX_PARTICLES 1000
#define MAX_FIELDS 10

class System {
protected:
	Particle* particles[MAX_PARTICLES];
	int maxp, curp;
	
	Field* fields [MAX_FIELDS];
	int maxf;
	
public:
	System ();
	~System ();
	
	void AddParticle (Particle* p);
	void AddField (Field* f);
	
	virtual void Simulate (float dt);
	virtual void Render ();
	
	virtual void Dump ();
};

System::System ()
{
	for (int p = 0; p < MAX_PARTICLES; p++)
		particles[p] = 0;
	maxp = curp = 0;
	maxf = 0;
}

System::~System ()
{
	for (int p = 0; p < maxp; p++)
		delete particles[p];
	for (int f = 0; f < maxf; f++)
		delete fields[f];
}

void
System::AddParticle (Particle* p)
{
	// new particles replace old one in array 
	if (maxp < MAX_PARTICLES)
		maxp++;
	if (curp == MAX_PARTICLES)
		curp = 0;
	if (particles[curp])
		delete particles[curp];
	particles[curp++] = p;
}

void
System::AddField (Field* f)
{
	if (maxf < MAX_FIELDS) {
		fields [maxf++] = f;
	}
}

void
System::Simulate (float dt)
{
	int p;
	for (p = 0; p < maxp; p++) {
		particles[p]->ResetForce();
		for (int f = 0; f < maxf; f++)
			fields[f]->ComputeForce (*particles[p]);
	}
	for (p = 0; p < maxp; p++)
		particles[p]->Step(dt);

}

void
System::Render ()
{
	for (int p = 0; p < maxp; p++)
		particles[p]->Render();
}

void
System::Dump ()
{
	printf ("%d particles\n", maxp);
	for (int p = 0; p < maxp; p++) {
		particles[p]->Dump();
		if (p > 10) {
			printf ("...\n");
			break;
		}
	}
}


// -------- Main Program --------

float dt = 0.05;
System* psystem = 0;

void
init ()
{
	if (psystem)
		delete psystem;
	
	psystem = new System;
	psystem->AddField (new GravityField);
}

void
idle ()
{
	for (int i = 0; i < 5; i++)
		psystem->AddParticle (new Particle);
	psystem->Simulate (dt);
	glutPostRedisplay ();
}

void
display ()
{
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	psystem->Render ();
	glutSwapBuffers ();
}

void
keyboard (unsigned char key, int x, int y)
{
	switch (key) {
	case 27:
	case 'q':
		exit(0);
		break;
	case ' ':
		init();
		glutPostRedisplay();
		break;
	case 'd':
		psystem->Dump ();
	}
}

void 
reshape(int w, int h)
{
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0.0, (GLdouble) w, 0.0, (GLdouble) h, -1.0, 1.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

int 
main(int argc, char **argv)
{
	glutInit (&argc, argv);
	glutInitWindowSize (512, 256);
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutCreateWindow("particle system");
	
	init();
	
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboard);
	glutIdleFunc (idle);
	
	glutMainLoop();
	return 0;
}
