2 This file is part of the 3dengfx, realtime visualization system.
4 Copyright (c) 2004, 2005 John Tsiombikas <nuclear@siggraph.org>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include "3dengfx_config.h"
24 #include "3denginefx.hpp"
27 #include "common/config_parser.h"
28 #include "common/err_msg.h"
30 #ifdef SINGLE_PRECISION_MATH
31 #define GL_SCALAR_TYPE GL_FLOAT
33 #define GL_SCALAR_TYPE GL_DOUBLE
34 #endif // SINGLE_PRECISION_MATH
36 static scalar_t global_time;
38 // just a trial and error constant to match point-sprite size with billboard size
39 #define PSPRITE_BILLBOARD_RATIO 100
41 // particle rendering state
42 static bool use_psprites = true;
43 static bool volatile_particles = false;
45 Fuzzy::Fuzzy(scalar_t num, scalar_t range) {
50 scalar_t Fuzzy::operator()() const {
51 return range == 0.0 ? num : frand(range) + num - range / 2.0;
55 FuzzyVec3::FuzzyVec3(const Fuzzy &x, const Fuzzy &y, const Fuzzy &z) {
61 Vector3 FuzzyVec3::operator()() const {
62 return Vector3(x(), y(), z());
66 Particle::Particle() {
72 Particle::Particle(const Vector3 &pos, const Vector3 &vel, scalar_t friction, scalar_t lifespan) {
75 this->friction = friction;
76 this->lifespan = lifespan;
77 birth_time = global_time;
80 Particle::~Particle(){}
82 bool Particle::alive() const {
83 return global_time - birth_time < lifespan;
86 void Particle::update(const Vector3 &ext_force) {
87 scalar_t time = global_time - birth_time;
88 if(time > lifespan) return;
90 velocity = (velocity + ext_force) * friction;
91 translate(velocity); // update position
94 void BillboardParticle::update(const Vector3 &ext_force) {
95 Particle::update(ext_force);
97 scalar_t time = global_time - birth_time;
98 if(time > lifespan) return;
99 scalar_t t = time / lifespan;
101 color = blend_colors(start_color, end_color, t);
103 size = size_start + (size_end - size_start) * t;
105 angle = rot * time + birth_angle;
109 * if we use point sprites, and the particles are not rotating, then the
110 * calling function has taken care to call glBegin() before calling this.
112 void BillboardParticle::draw() const {
114 if(volatile_particles) {
115 tex_rot.translate(Vector3(0.5, 0.5, 0.0));
116 tex_rot.rotate(Vector3(0.0, 0.0, angle));
117 tex_rot.translate(Vector3(-0.5, -0.5, 0.0));
118 set_matrix(XFORM_TEXTURE, tex_rot);
121 Vector3 pos = get_position();
124 if(volatile_particles) {
128 glColor4f(color.r, color.g, color.b, color.a);
129 glVertex3f(pos.x, pos.y, pos.z);
132 glColor4f(color.r, color.g, color.b, color.a);
133 glVertex3f(pos.x, pos.y, pos.z);
135 } else { // don't use point sprites
136 Vertex v(pos, 0, 0, color);
137 draw_point(v, size / PSPRITE_BILLBOARD_RATIO);
143 ParticleSysParams::ParticleSysParams() {
151 big_particles = false;
152 spawn_offset_curve = 0;
153 spawn_offset_curve_area = Fuzzy(0.5, 1.0);
155 src_blend = BLEND_SRC_ALPHA;
156 dest_blend = BLEND_ONE;
161 ParticleSystem::ParticleSystem(const char *fname) {
162 timeslice = 1.0 / 30.0;
163 SysCaps sys_caps = get_system_capabilities();
164 /* XXX: My Radeon Mobility 9000 supports point sprites but does not say so
165 * in the extension string, it only has point params there. So I changed this
166 * condition to && since probably if a card supports one, it will also support
169 psprites_unsupported = !sys_caps.point_sprites && !sys_caps.point_params;
173 ptype = PTYPE_BILLBOARD;
178 if(!psys::load_particle_sys_params(fname, &psys_params)) {
179 error("Error loading particle file: %s", fname);
185 ParticleSystem::~ParticleSystem() {
189 void ParticleSystem::reset() {
191 std::list<Particle*>::iterator iter = particles.begin();
192 while(iter != particles.end()) {
198 void ParticleSystem::set_update_interval(scalar_t timeslice) {
199 this->timeslice = timeslice;
202 void ParticleSystem::set_params(const ParticleSysParams &psys_params) {
203 this->psys_params = psys_params;
206 ParticleSysParams *ParticleSystem::get_params() {
210 void ParticleSystem::set_particle_type(ParticleType ptype) {
214 void ParticleSystem::update(const Vector3 &ext_force) {
217 curr_time = global_time;
218 int updates_missed = (int)round((global_time - prev_update) / timeslice);
220 if(!updates_missed) return; // less than a timeslice has elapsed, nothing to do
222 PRS prs = get_prs((unsigned long)(global_time * 1000.0));
223 curr_pos = prs.position;
224 curr_halo_rot = psys_params.halo_rot * global_time;
226 curr_rot = fmod(psys_params.glob_rot * global_time, two_pi);
228 // spawn new particles
229 scalar_t spawn = psys_params.birth_rate() * (global_time - prev_update);
230 int spawn_count = (int)round(spawn);
232 // handle sub-timeslice spawning rates
233 fraction += spawn - round(spawn);
237 } else if(fraction < -1.0) {
243 if(prev_update < 0.0) {
245 prev_update = curr_time;
248 dp = (curr_pos - prev_pos) / (scalar_t)spawn_count;
252 scalar_t dt = (global_time - prev_update) / (scalar_t)spawn_count;
253 scalar_t t = prev_update;
255 for(int i=0; i<spawn_count; i++) {
259 case PTYPE_BILLBOARD:
260 particle = new BillboardParticle;
262 curr_rot = fmod(psys_params.glob_rot * t, two_pi);
264 BillboardParticle *bbp = (BillboardParticle*)particle;
265 bbp->texture = psys_params.billboard_tex;
266 bbp->start_color = psys_params.start_color;
267 bbp->end_color = psys_params.end_color;
268 bbp->rot = psys_params.rot;
269 bbp->birth_angle = curr_rot;
275 error("Only billboarded particles implemented currently");
280 //PRS sub_prs = get_prs((unsigned long)(t * 1000.0));
283 Vector3 offset = psys_params.spawn_offset();
284 if(psys_params.spawn_offset_curve) {
285 float t = psys_params.spawn_offset_curve_area();
286 offset += (*psys_params.spawn_offset_curve)(t);
288 // XXX: correct this rotation to span the whole interval
289 particle->set_position(pos + offset.transformed(prs.rotation));
290 particle->set_rotation(prs.rotation);
291 particle->set_scaling(prs.scale);
293 particle->size_start = psys_params.psize();
294 if(psys_params.psize_end < 0.0) {
295 particle->size_end = particle->size_start;
297 particle->size_end = psys_params.psize_end;
299 particle->velocity = psys_params.shoot_dir().transformed(prs.rotation); // XXX: correct this rotation to span the interval
300 particle->friction = psys_params.friction;
301 particle->birth_time = t;
302 particle->lifespan = psys_params.lifespan();
304 particles.push_back(particle);
313 std::list<Particle*>::iterator iter = particles.begin();
314 while(iter != particles.end()) {
317 while(p->alive() && i++ < updates_missed) {
318 p->update(psys_params.gravity);
325 iter = particles.erase(iter);
329 prev_update = global_time;
333 void ParticleSystem::draw() const {
336 // use point sprites if the system supports them AND we don't need big particles
337 use_psprites = !psys_params.big_particles && !psprites_unsupported;
339 // particles are volatile if they rotate OR they fluctuate in size
340 volatile_particles = psys_params.rot > small_number || psys_params.psize.range > small_number;
342 set_matrix(XFORM_WORLD, Matrix4x4());
343 load_xform_matrices();
345 std::list<Particle*>::const_iterator iter = particles.begin();
346 if(iter != particles.end()) {
348 if(ptype == PTYPE_BILLBOARD) {
349 // ------ setup render state ------
352 set_alpha_blending(true);
353 set_blend_func(psys_params.src_blend, psys_params.dest_blend);
355 if(psys_params.billboard_tex) {
356 enable_texture_unit(0);
357 disable_texture_unit(1);
358 set_texture(0, psys_params.billboard_tex);
359 set_texture_addressing(0, TEXADDR_CLAMP, TEXADDR_CLAMP);
362 set_point_sprites(true);
363 set_point_sprite_coords(0, true);
366 if(!volatile_particles) {
368 prot.translate(Vector3(0.5, 0.5, 0.0));
369 prot.rotate(Vector3(0.0, 0.0, curr_rot));
370 prot.translate(Vector3(-0.5, -0.5, 0.0));
371 set_matrix(XFORM_TEXTURE, prot);
375 set_texture_unit_color(0, TOP_MODULATE, TARG_TEXTURE, TARG_PREV);
376 set_texture_unit_alpha(0, TOP_MODULATE, TARG_TEXTURE, TARG_PREV);
378 if(use_psprites && !volatile_particles) {
379 glPointSize((*iter)->size);
384 // ------ render particles ------
385 while(iter != particles.end()) {
389 if(ptype == PTYPE_BILLBOARD) {
390 // ------ restore render states -------
392 if(!volatile_particles) glEnd();
396 if(psys_params.billboard_tex) {
398 set_point_sprites(false);
399 set_point_sprite_coords(0, false);
401 set_texture_addressing(0, TEXADDR_WRAP, TEXADDR_WRAP);
402 disable_texture_unit(0);
404 set_matrix(XFORM_TEXTURE, Matrix4x4::identity_matrix);
407 set_alpha_blending(false);
413 // ------ render a halo around the emitter if we need to ------
414 if(psys_params.halo) {
415 // construct texture matrix for halo rotation
417 mat.translate(Vector3(0.5, 0.5, 0.0));
418 mat.rotate(Vector3(0, 0, curr_halo_rot));
419 mat.translate(Vector3(-0.5, -0.5, 0.0));
420 set_matrix(XFORM_TEXTURE, mat);
422 set_alpha_blending(true);
423 set_blend_func(BLEND_SRC_ALPHA, BLEND_ONE);
424 enable_texture_unit(0);
425 disable_texture_unit(1);
426 set_texture(0, psys_params.halo);
428 Vertex v(curr_pos, 0, 0, psys_params.halo_color);
429 draw_point(v, psys_params.halo_size() / PSPRITE_BILLBOARD_RATIO);
431 disable_texture_unit(0);
432 set_alpha_blending(false);
434 set_matrix(XFORM_TEXTURE, Matrix4x4::identity_matrix);
439 void psys::set_global_time(unsigned long msec) {
440 global_time = (scalar_t)msec / 1000.0;
444 static Vector3 get_vector(const char *str) {
445 char *buf = new char[strlen(str) + 1];
454 if(!(ptr = strchr(beg, ','))) {
462 if(!(ptr = strchr(beg, ','))) {
475 static Vector4 get_vector4(const char *str) {
476 char *buf = new char[strlen(str) + 1];
485 if(!(ptr = strchr(beg, ','))) {
493 if(!(ptr = strchr(beg, ','))) {
501 if(!(ptr = strchr(beg, ','))) {
515 bool psys::load_particle_sys_params(const char *fname, ParticleSysParams *psp) {
516 Vector3 shoot, shoot_range;
517 Vector3 spawn_off, spawn_off_range;
519 set_parser_state(PS_AssignmentSymbol, ':');
520 set_parser_state(PS_CommentSymbol, '#');
522 if(load_config_file(fname) == -1) return false;
524 const ConfigOption *opt;
525 while((opt = get_next_option())) {
527 if(!strcmp(opt->option, "psize")) {
528 psp->psize.num = opt->flt_value;
530 } else if(!strcmp(opt->option, "psize-r")) {
531 psp->psize.range = opt->flt_value;
533 } else if(!strcmp(opt->option, "psize_end")) {
534 psp->psize_end = opt->flt_value;
536 } else if(!strcmp(opt->option, "life")) {
537 psp->lifespan.num = opt->flt_value;
539 } else if(!strcmp(opt->option, "life-r")) {
540 psp->lifespan.range = opt->flt_value;
542 } else if(!strcmp(opt->option, "birth-rate")) {
543 psp->birth_rate.num = opt->flt_value;
545 } else if(!strcmp(opt->option, "birth-rate-r")) {
546 psp->birth_rate.range = opt->flt_value;
548 } else if(!strcmp(opt->option, "grav")) {
549 psp->gravity = get_vector(opt->str_value);
551 } else if(!strcmp(opt->option, "shoot")) {
552 shoot = get_vector(opt->str_value);
554 } else if(!strcmp(opt->option, "shoot-r")) {
555 shoot_range = get_vector(opt->str_value);
557 } else if(!strcmp(opt->option, "friction")) {
558 psp->friction = opt->flt_value;
560 } else if(!strcmp(opt->option, "spawn_off")) {
561 spawn_off = get_vector(opt->str_value);
563 } else if(!strcmp(opt->option, "spawn_off-r")) {
564 spawn_off_range = get_vector(opt->str_value);
566 } else if(!strcmp(opt->option, "tex")) {
567 psp->billboard_tex = get_texture(opt->str_value);
568 if(!psp->billboard_tex) {
569 error("Could not load texture: \"%s\"", opt->str_value);
572 } else if(!strcmp(opt->option, "color")) {
573 Vector4 v = get_vector4(opt->str_value);
574 psp->start_color = psp->end_color = Color(v.x, v.y, v.z, v.w);
576 } else if(!strcmp(opt->option, "color_start")) {
577 Vector4 v = get_vector4(opt->str_value);
578 psp->start_color = Color(v.x, v.y, v.z, v.w);
580 } else if(!strcmp(opt->option, "color_end")) {
581 Vector4 v = get_vector4(opt->str_value);
582 psp->end_color = Color(v.x, v.y, v.z, v.w);
584 } else if(!strcmp(opt->option, "rot")) {
585 psp->rot = opt->flt_value;
587 } else if(!strcmp(opt->option, "glob_rot")) {
588 psp->glob_rot = opt->flt_value;
590 } else if(!strcmp(opt->option, "halo")) {
591 psp->halo = get_texture(opt->str_value);
593 error("Could not load texture: \"%s\"", opt->str_value);
596 } else if(!strcmp(opt->option, "halo_color")) {
597 Vector4 v = get_vector4(opt->str_value);
598 psp->halo_color = Color(v.x, v.y, v.z, v.w);
600 } else if(!strcmp(opt->option, "halo_size")) {
601 psp->halo_size.num = opt->flt_value;
603 } else if(!strcmp(opt->option, "halo_size-r")) {
604 psp->halo_size.range = opt->flt_value;
606 } else if(!strcmp(opt->option, "halo_rot")) {
607 psp->halo_rot = opt->flt_value;
609 } else if(!strcmp(opt->option, "big_particles")) {
610 if(!strcmp(opt->str_value, "true")) {
611 psp->big_particles = true;
614 } else if(!strcmp(opt->option, "blend_src") || !strcmp(opt->option, "blend_dest")) {
615 BlendingFactor factor = (BlendingFactor)0xfbad;
616 if(!strcmp(opt->str_value, "0")) {
618 } else if(!strcmp(opt->str_value, "1")) {
620 } else if(!strcmp(opt->str_value, "srcc")) {
621 factor = BLEND_SRC_COLOR;
622 } else if(!strcmp(opt->str_value, "srca")) {
623 factor = BLEND_SRC_ALPHA;
624 } else if(!strcmp(opt->str_value, "1-srcc")) {
625 factor = BLEND_ONE_MINUS_SRC_COLOR;
626 } else if(!strcmp(opt->str_value, "1-srca")) {
627 factor = BLEND_ONE_MINUS_SRC_ALPHA;
628 } else if(!strcmp(opt->str_value, "1-dstc")) {
629 factor = BLEND_ONE_MINUS_DST_COLOR;
631 error("psys: invalid blend specification: %s", opt->str_value);
634 if(factor != (BlendingFactor)0xfbad) {
635 if(!strcmp(opt->option, "blend_src")) {
636 psp->src_blend = factor;
638 psp->dest_blend = factor;
641 } else if(!strcmp(opt->option, "spawn_offset_curve")) {
642 if(!(psp->spawn_offset_curve = load_curve(opt->str_value))) {
643 error("psys: could not load spawn offset curve: %s", opt->str_value);
648 psp->shoot_dir = FuzzyVec3(Fuzzy(shoot.x, shoot_range.x), Fuzzy(shoot.y, shoot_range.y), Fuzzy(shoot.z, shoot_range.z));
649 psp->spawn_offset = FuzzyVec3(Fuzzy(spawn_off.x, spawn_off_range.x), Fuzzy(spawn_off.y, spawn_off_range.y), Fuzzy(spawn_off.z, spawn_off_range.z));