//Tips for making particles: //For the particle textures, I suggest using 2 channel images, with an alpha channel that //just dublicates the luminosity channel. //This makes the blending between particles nicer. //Such images can be easy created in paint shop by going to Masks->new->from image, //then Masks->Save to Alpha Channel, then pasting a copy of the origional image //into the window, and saving to a single alpha channel image format (like sgi). //Warning, paintshop seems to have some problem with saving 2 channel sgi images. //You can work around it by increasing the color depth to 24. //While it would have been possible for me to have made a fairly comprehensive //generlized particle swarm class, there are so many weird ways that one might //want to tweak it to get a specific effect that the general class would have //have become a byzanten mess, both to write and to use. //Thus, I am actively advocating cut and paste programming for the purpose of //creating specilized particle swarm classes. I have made a couple of pretty nice, modestly //general swarm classes of my own. Copy the one that seems most like what you want to to, //and hack whatever special features you want into it. I know, it goes against everything //that the great Bjarne teaches us, but in this situation, I really do think it is the //right way to do it. #ifndef PARTICLE_H #define PARTICLE_H #include namespace effects { //In order to make bill boarded textures in an arbitrary coordinate system, //we need to have easy access to the basis vectors of the cordinate space. namespace bill_boarding { extern GLfloat * iHat; extern GLfloat * jHat; extern GLfloat * kHat; } using sven::float3; using nehe::neheTexture; using namespace sgg; using namespace gl_ex; using namespace std_ex; //this is the particle swarms base class. //in the interest of speed, I am keeping particle swarms non-polymorphic. //All particle swarms are generally assumed to have the following functions: //void refresh() //void draw() (default provided by ParticleSwarmCommon) //some interesting constructor (usually taking in at least a position variable). //As a general rule, Particle textures are assumed to be grayscale, and particles //to have a color, though if you wanted you could always just load a colored texture //and set the color of all particles to 0xffffff. struct ParticleSwarmCommon { protected: struct Particle : public GLimage { float3 p,v; long age; sven::rgba color; Particle(float3 p, float3 v, sven::rgba color, GLimage i) : GLimage(i), p(p), v(v), color(color), age(0) {} void flat_draw() { glPushAttrib(GL_CURRENT_BIT); glColor(color); glPushMatrix(); glTranslatefv(p.v); GLimage::draw(); glPopMatrix(); glPopAttrib(); } void bb_draw(); //required because of a problem in MSVC's STL implementation. struct mem_flat_draw { void operator()(Particle p) { p.flat_draw(); } }; struct mem_bb_draw { void operator()(Particle p) { p.bb_draw(); } }; }; vector particles; public: static GLfloat pSpeed; void flat_draw() { glPushAttrib(GL_ENABLE_BIT); glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); foreach(particles,Particle::mem_flat_draw()); glPopAttrib(); } void bb_draw() { glPushAttrib(GL_ENABLE_BIT); glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); foreach(particles,Particle::mem_bb_draw()); glPopAttrib(); } void refresh() { for(vector::iterator i=particles.begin();i!=particles.end();i++) { i->age++; i->p+=i->v; } } }; //the float4's are in the range 0-255 struct cmap_point { float first; float4 second; bool operator<(const cmap_point &b) { return first points; vector slopes; int getLast(float normalized_age) { if(normalized_age>1.f) normalized_age=1.f; if(normalized_age<0.f) normalized_age=0.f; for(int i=0;i get_points() { return points; } Linear_Color_Map(vector ps) : points(ps) { if(!points.size()) { points.push_back(cmap_point(0.f,float4(255,255,255,255))); } sort(points.begin(),points.end()); vector::iterator i; points[0].first=0.f; for(i=points.begin();i!=points.end();i++) { if(i->first>1.f) i->first=1.f; } for(i=points.begin();i!=points.end();i++) { slopes.push_back( ((i+1)->second - i->second) / ((i+1)->first - i->first) ); } if(!slopes.size()) slopes.push_back(float4(0,0,0,0)); } rgba operator()(float normalized_age) { int last=getLast(normalized_age); return points[last].second+slopes[last]*(normalized_age-points[last].first); } friend ostream& operator<<(ostream &out, Linear_Color_Map &m) { out << "\n"; for(vector::const_iterator i = m.points.begin();i!=m.points.end();i++) { out << i->first << " :: ( " << i->second[0] << " , " << i->second[1] << " , " << i->second[2] << " , " << i->second[3] << " )\n"; } out << "<\\color_map>\n"; return out; } friend Linear_Color_Map load_Linear_Color_Map(string::const_iterator &start, string::const_iterator finish); }; template struct StandardEmitter : public ParticleSwarmCommon { static vector std_color_map() { vector cm; cm.push_back(cmap_point(0.f,float4(255,255,255,100))); cm.push_back(cmap_point(.3f,float4(255,0,255,140))); cm.push_back(cmap_point(1.f,float4(0,0,255,10))); return cm; } //things that users are encouraged to mess with Color_Map color_fun; float dir; float3 center; float g; int emit_rate; int max_age; float speed; private: string texture_file; neheTexture texture; public: void set_texture(string new_texture) { texture_file=new_texture; texture=neheTexture(new_texture); } string get_texture_file() { return texture_file; } bool done; //the 'off' switch //notice that this default does not work if you use a non-default color map type. seems fair, no? StandardEmitter( float3 center=float3(0,0,0), float dir=PI, int emit_rate=2, float speed=.5, int max_age=120, float g = .05, string texture_file = "particle_images\\particle8.rgb", Color_Map color_fun=Linear_Color_Map(std_color_map()) ) : texture(texture_file), center(center), done(false), texture_file(texture_file), dir(dir), g(g), color_fun(color_fun), emit_rate(emit_rate), max_age(max_age), speed(speed) {} void push_particles(int n) { using namespace sgg; using namespace nehe; for(int i=0;imax_age ) { particles.erase(particles.begin()+i); } else i++; } } friend ostream& operator<<(ostream &out, StandardEmitter &e) { out << "\n" << endl; out << "dir: " << e.dir << endl; out << "g: " << e.g << endl; out << "emit_rate: " << e.emit_rate << endl; out << "speed: " << e.speed << endl; out << "texture_file: \"" << e.texture_file << "\"" << endl; out << "color_fun:\n\n" << e.color_fun << endl; out << "<\\standard_emitter>" << endl; return out; } friend StandardEmitter load_StandardEmitter(string::const_iterator &start, string::const_iterator finish); }; //a general wrapper to take the basic (fast) Swarm classes, and turn //them into SGG::Screen3v compatable (less fast) polymorphic classes. template struct BasicSwarm3v : public Swarm, public Drawable, public Refreshable, public KillFlag { BasicSwarm3v(Swarm b=Swarm()) : Swarm(b) {} void refresh() { Swarm::refresh(); if(done) killme=true; } void draw() { Swarm::flat_draw(); } }; } #endif