added 3dengfx into the repo, probably not the correct version for this
[summerhack] / src / 3dengfx / src / 3dengfx / psys.cpp
1 /*
2 This file is part of the 3dengfx, realtime visualization system.
3
4 Copyright (c) 2004, 2005 John Tsiombikas <nuclear@siggraph.org>
5
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.
10
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.
15
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
19 */
20
21 #include <vector>
22 #include <cmath>
23 #include "3dengfx_config.h"
24 #include "3denginefx.hpp"
25 #include "texman.hpp"
26 #include "psys.hpp"
27 #include "common/config_parser.h"
28 #include "common/err_msg.h"
29
30 #ifdef SINGLE_PRECISION_MATH
31 #define GL_SCALAR_TYPE  GL_FLOAT
32 #else
33 #define GL_SCALAR_TYPE  GL_DOUBLE
34 #endif  // SINGLE_PRECISION_MATH
35
36 static scalar_t global_time;
37
38 // just a trial and error constant to match point-sprite size with billboard size
39 #define PSPRITE_BILLBOARD_RATIO         100
40
41 // particle rendering state
42 static bool use_psprites = true;
43 static bool volatile_particles = false;
44
45 Fuzzy::Fuzzy(scalar_t num, scalar_t range) {
46         this->num = num;
47         this->range = range;
48 }
49
50 scalar_t Fuzzy::operator()() const {
51         return range == 0.0 ? num : frand(range) + num - range / 2.0;
52 }
53
54
55 FuzzyVec3::FuzzyVec3(const Fuzzy &x, const Fuzzy &y, const Fuzzy &z) {
56         this->x = x;
57         this->y = y;
58         this->z = z;
59 }
60
61 Vector3 FuzzyVec3::operator()() const {
62         return Vector3(x(), y(), z());
63 }
64
65
66 Particle::Particle() {
67         friction = 1.0;
68         lifespan = 0;
69         birth_time = 0;
70 }
71
72 Particle::Particle(const Vector3 &pos, const Vector3 &vel, scalar_t friction, scalar_t lifespan) {
73         set_position(pos);
74         velocity = vel;
75         this->friction = friction;
76         this->lifespan = lifespan;
77         birth_time = global_time;
78 }
79
80 Particle::~Particle(){}
81
82 bool Particle::alive() const {
83         return global_time - birth_time < lifespan;
84 }
85
86 void Particle::update(const Vector3 &ext_force) {
87         scalar_t time = global_time - birth_time;
88         if(time > lifespan) return;
89
90         velocity = (velocity + ext_force) * friction;
91         translate(velocity);    // update position
92 }
93
94 void BillboardParticle::update(const Vector3 &ext_force) {
95         Particle::update(ext_force);
96         
97         scalar_t time = global_time - birth_time;
98         if(time > lifespan) return;
99         scalar_t t = time / lifespan;
100         
101         color = blend_colors(start_color, end_color, t);
102
103         size = size_start + (size_end - size_start) * t;
104
105         angle = rot * time + birth_angle;
106 }
107
108 /* NOTE:
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.
111  */
112 void BillboardParticle::draw() const {
113         Matrix4x4 tex_rot;
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);
119         }
120
121         Vector3 pos = get_position();
122         
123         if(use_psprites) {
124                 if(volatile_particles) {
125                         glPointSize(size);
126                         
127                         glBegin(GL_POINTS);
128                         glColor4f(color.r, color.g, color.b, color.a);
129                         glVertex3f(pos.x, pos.y, pos.z);
130                         glEnd();
131                 } else {
132                         glColor4f(color.r, color.g, color.b, color.a);
133                         glVertex3f(pos.x, pos.y, pos.z);
134                 }
135         } else {        // don't use point sprites
136                 Vertex v(pos, 0, 0, color);
137                 draw_point(v, size / PSPRITE_BILLBOARD_RATIO);
138         }
139 }
140
141
142
143 ParticleSysParams::ParticleSysParams() {
144         psize_end = -1.0;
145         friction = 0.95;
146         billboard_tex = 0;
147         halo = 0;
148         rot = 0.0;
149         glob_rot = 0.0;
150         halo_rot = 0.0;
151         big_particles = false;
152         spawn_offset_curve = 0;
153         spawn_offset_curve_area = Fuzzy(0.5, 1.0);
154
155         src_blend = BLEND_SRC_ALPHA;
156         dest_blend = BLEND_ONE;
157 }
158
159
160
161 ParticleSystem::ParticleSystem(const char *fname) {
162         timeslice = 1.0 / 50.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
167          * the other.
168          */
169         psprites_unsupported = !sys_caps.point_sprites && !sys_caps.point_params;
170
171         prev_update = -1.0;
172         fraction = 0.0;
173         ptype = PTYPE_BILLBOARD;
174
175         ready = true;
176
177         if(fname) {
178                 if(!psys::load_particle_sys_params(fname, &psys_params)) {
179                         error("Error loading particle file: %s", fname);
180                         ready = false;
181                 }
182         }
183 }
184
185 ParticleSystem::~ParticleSystem() {
186         reset();
187 }
188
189 void ParticleSystem::reset() {
190         prev_update = -1.0;
191         std::list<Particle*>::iterator iter = particles.begin();
192         while(iter != particles.end()) {
193                 delete *iter++;
194         }
195         particles.clear();
196 }
197
198 void ParticleSystem::set_update_interval(scalar_t timeslice) {
199         this->timeslice = timeslice;
200 }
201
202 void ParticleSystem::set_params(const ParticleSysParams &psys_params) {
203         this->psys_params = psys_params;
204 }
205
206 ParticleSysParams *ParticleSystem::get_params() {
207         return &psys_params;
208 }
209
210 void ParticleSystem::set_particle_type(ParticleType ptype) {
211         this->ptype = ptype;
212 }
213
214 void ParticleSystem::update(const Vector3 &ext_force) {
215         if(!ready) return;
216         
217         curr_time = global_time;
218         int updates_missed = (int)round((global_time - prev_update) / timeslice);
219
220         if(!updates_missed) return;     // less than a timeslice has elapsed, nothing to do
221         
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;
225
226         curr_rot = fmod(psys_params.glob_rot * global_time, two_pi);
227
228         // spawn new particles
229         scalar_t spawn = psys_params.birth_rate() * (global_time - prev_update);
230         int spawn_count = (int)round(spawn);
231
232         // handle sub-timeslice spawning rates
233         fraction += spawn - round(spawn);
234         if(fraction > 1.0) {
235                 fraction -= 1.0;
236                 spawn_count++;
237         } else if(fraction < -1.0) {
238                 fraction += 1.0;
239                 spawn_count--;
240         }
241
242         Vector3 dp, pos;
243         if(prev_update < 0.0) {
244                 prev_pos = curr_pos;
245                 prev_update = curr_time;
246                 return;
247         } else {
248                 dp = (curr_pos - prev_pos) / (scalar_t)spawn_count;
249                 pos = prev_pos;
250         }
251         
252         scalar_t dt = (global_time - prev_update) / (scalar_t)spawn_count;
253         scalar_t t = prev_update;
254         
255         for(int i=0; i<spawn_count; i++) {
256                 Particle *particle;
257                 
258                 switch(ptype) {
259                 case PTYPE_BILLBOARD:
260                         particle = new BillboardParticle;
261                         {
262                                 curr_rot = fmod(psys_params.glob_rot * t, two_pi);
263                                 
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;
270                         }
271
272                         break;
273
274                 default:
275                         error("Only billboarded particles implemented currently");
276                         exit(-1);
277                         break;
278                 }
279                 
280                 //PRS sub_prs = get_prs((unsigned long)(t * 1000.0));
281                 
282
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);
287                 }
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);
292
293                 particle->size_start = psys_params.psize();
294                 if(psys_params.psize_end < 0.0) {
295                         particle->size_end = particle->size_start;
296                 } else {
297                         particle->size_end = psys_params.psize_end;
298                 }
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();
303
304                 particles.push_back(particle);
305
306                 pos += dp;
307                 t += dt;
308         }
309         
310
311         // update particles
312         
313         std::list<Particle*>::iterator iter = particles.begin();
314         while(iter != particles.end()) {
315                 Particle *p = *iter;
316                 int i = 0;
317                 while(p->alive() && i++ < updates_missed) {
318                         p->update(psys_params.gravity);
319                 }
320
321                 if(p->alive()) {
322                         iter++;
323                 } else {
324                         delete *iter;
325                         iter = particles.erase(iter);
326                 }
327         }
328
329         prev_update = global_time;
330         prev_pos = curr_pos;
331 }
332
333 void ParticleSystem::draw() const {
334         if(!ready) return;
335
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;
338
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;
341         
342         set_matrix(XFORM_WORLD, Matrix4x4());
343         load_xform_matrices();
344
345         std::list<Particle*>::const_iterator iter = particles.begin();
346         if(iter != particles.end()) {
347                 
348                 if(ptype == PTYPE_BILLBOARD) {
349                         // ------ setup render state ------
350                         set_lighting(false);
351                         set_zwrite(false);
352                         set_alpha_blending(true);
353                         set_blend_func(psys_params.src_blend, psys_params.dest_blend);
354
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);
360
361                                 if(use_psprites) {
362                                         set_point_sprites(true);
363                                         set_point_sprite_coords(0, true);
364                                 }
365
366                                 if(!volatile_particles) {
367                                         Matrix4x4 prot;
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);
372                                 }
373                         }
374
375                         set_texture_unit_color(0, TOP_MODULATE, TARG_TEXTURE, TARG_PREV);
376                         set_texture_unit_alpha(0, TOP_MODULATE, TARG_TEXTURE, TARG_PREV);
377
378                         if(use_psprites && !volatile_particles) {
379                                 glPointSize((*iter)->size);
380                                 glBegin(GL_POINTS);
381                         }
382                 }
383
384                 // ------ render particles ------
385                 while(iter != particles.end()) {
386                         (*iter++)->draw();
387                 }
388         
389                 if(ptype == PTYPE_BILLBOARD) {
390                         // ------ restore render states -------
391                         if(use_psprites) {
392                                 if(!volatile_particles) glEnd();
393                                 glPointSize(1.0);
394                         }
395
396                         if(psys_params.billboard_tex) {
397                                 if(use_psprites) {
398                                         set_point_sprites(true);
399                                         set_point_sprite_coords(0, true);
400                                 }
401                                 set_texture_addressing(0, TEXADDR_WRAP, TEXADDR_WRAP);
402                                 disable_texture_unit(0);
403
404                                 set_matrix(XFORM_TEXTURE, Matrix4x4::identity_matrix);
405                         }
406
407                         set_alpha_blending(false);
408                         set_zwrite(true);
409                         set_lighting(true);
410                 }
411         }
412
413         // ------ render a halo around the emitter if we need to ------
414         if(psys_params.halo) {
415                 // construct texture matrix for halo rotation
416                 Matrix4x4 mat;
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);
421
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);
427                 set_zwrite(false);
428                 Vertex v(curr_pos, 0, 0, psys_params.halo_color);
429                 draw_point(v, psys_params.halo_size() / PSPRITE_BILLBOARD_RATIO);
430                 set_zwrite(true);
431                 disable_texture_unit(0);
432                 set_alpha_blending(false);
433
434                 set_matrix(XFORM_TEXTURE, Matrix4x4::identity_matrix);
435         }
436 }
437
438
439 void psys::set_global_time(unsigned long msec) {
440         global_time = (scalar_t)msec / 1000.0;
441 }
442
443
444 static Vector3 get_vector(const char *str) {
445         char *buf = new char[strlen(str) + 1];
446         strcpy(buf, str);
447
448         Vector3 res;
449
450         char *beg = buf;
451         char *ptr;
452         
453         res.x = atof(beg);
454         if(!(ptr = strchr(beg, ','))) {
455                 delete [] buf;
456                 return res;
457         }
458         *ptr = 0;
459         beg = ptr + 1;
460         
461         res.y = atof(beg);
462         if(!(ptr = strchr(beg, ','))) {
463                 delete [] buf;
464                 return res;
465         }
466         *ptr = 0;
467         beg = ptr + 1;
468
469         res.z = atof(beg);
470
471         delete [] buf;
472         return res;
473 }
474
475 static Vector4 get_vector4(const char *str) {
476         char *buf = new char[strlen(str) + 1];
477         strcpy(buf, str);
478
479         Vector4 res;
480
481         char *beg = buf;
482         char *ptr;
483         
484         res.x = atof(beg);
485         if(!(ptr = strchr(beg, ','))) {
486                 delete [] buf;
487                 return res;
488         }
489         *ptr = 0;
490         beg = ptr + 1;
491         
492         res.y = atof(beg);
493         if(!(ptr = strchr(beg, ','))) {
494                 delete [] buf;
495                 return res;
496         }
497         *ptr = 0;
498         beg = ptr + 1;
499
500         res.z = atof(beg);
501         if(!(ptr = strchr(beg, ','))) {
502                 delete [] buf;
503                 return res;
504         }
505         *ptr = 0;
506         beg = ptr + 1;
507         
508         res.w = atof(beg);
509
510         delete [] buf;
511         return res;
512 }
513
514
515 bool psys::load_particle_sys_params(const char *fname, ParticleSysParams *psp) {
516         Vector3 shoot, shoot_range;
517         Vector3 spawn_off, spawn_off_range;
518         
519         set_parser_state(PS_AssignmentSymbol, ':');
520         set_parser_state(PS_CommentSymbol, '#');
521
522         if(load_config_file(fname) == -1) return false;
523
524         const ConfigOption *opt;
525         while((opt = get_next_option())) {
526
527                 if(!strcmp(opt->option, "psize")) {
528                         psp->psize.num = opt->flt_value;
529                         
530                 } else if(!strcmp(opt->option, "psize-r")) {
531                         psp->psize.range = opt->flt_value;
532
533                 } else if(!strcmp(opt->option, "psize_end")) {
534                         psp->psize_end = opt->flt_value;
535
536                 } else if(!strcmp(opt->option, "life")) {
537                         psp->lifespan.num = opt->flt_value;
538
539                 } else if(!strcmp(opt->option, "life-r")) {
540                         psp->lifespan.range = opt->flt_value;
541
542                 } else if(!strcmp(opt->option, "birth-rate")) {
543                         psp->birth_rate.num = opt->flt_value;
544
545                 } else if(!strcmp(opt->option, "birth-rate-r")) {
546                         psp->birth_rate.range = opt->flt_value;
547
548                 } else if(!strcmp(opt->option, "grav")) {
549                         psp->gravity = get_vector(opt->str_value);
550                         
551                 } else if(!strcmp(opt->option, "shoot")) {
552                         shoot = get_vector(opt->str_value);
553
554                 } else if(!strcmp(opt->option, "shoot-r")) {
555                         shoot_range = get_vector(opt->str_value);
556
557                 } else if(!strcmp(opt->option, "friction")) {
558                         psp->friction = opt->flt_value;
559
560                 } else if(!strcmp(opt->option, "spawn_off")) {
561                         spawn_off = get_vector(opt->str_value);
562
563                 } else if(!strcmp(opt->option, "spawn_off-r")) {
564                         spawn_off_range = get_vector(opt->str_value);
565
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);
570                         }
571
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);
575                         
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);
579
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);
583
584                 } else if(!strcmp(opt->option, "rot")) {
585                         psp->rot = opt->flt_value;
586
587                 } else if(!strcmp(opt->option, "glob_rot")) {
588                         psp->glob_rot = opt->flt_value;
589                         
590                 } else if(!strcmp(opt->option, "halo")) {
591                         psp->halo = get_texture(opt->str_value);
592                         if(!psp->halo) {
593                                 error("Could not load texture: \"%s\"", opt->str_value);
594                         }
595
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);
599
600                 } else if(!strcmp(opt->option, "halo_size")) {
601                         psp->halo_size.num = opt->flt_value;
602
603                 } else if(!strcmp(opt->option, "halo_size-r")) {
604                         psp->halo_size.range = opt->flt_value;
605                         
606                 } else if(!strcmp(opt->option, "halo_rot")) {
607                         psp->halo_rot = opt->flt_value;
608                         
609                 } else if(!strcmp(opt->option, "big_particles")) {
610                         if(!strcmp(opt->str_value, "true")) {
611                                 psp->big_particles = true;
612                         }
613                         
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")) {
617                                 factor = BLEND_ZERO;
618                         } else if(!strcmp(opt->str_value, "1")) {
619                                 factor = BLEND_ONE;
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;
630                         } else {
631                                 error("psys: invalid blend specification: %s", opt->str_value);
632                         }
633
634                         if(factor != (BlendingFactor)0xfbad) {
635                                 if(!strcmp(opt->option, "blend_src")) {
636                                         psp->src_blend = factor;
637                                 } else {
638                                         psp->dest_blend = factor;
639                                 }
640                         }
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);
644                         }
645                 }
646         }
647
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));
650
651         return true;
652 }