2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Particle/Particle.cpp $
15 * Code for particle system
18 * Revision 1.7 2004/09/20 01:31:44 theoddone33
21 * Revision 1.6 2003/05/25 02:30:43 taylor
24 * Revision 1.5 2002/06/17 06:33:10 relnev
25 * ryan's struct patch for gcc 2.95
27 * Revision 1.4 2002/06/09 04:41:25 relnev
28 * added copyright header
30 * Revision 1.3 2002/06/09 03:16:04 relnev
33 * removed unneeded asm, old sdl 2d setup.
35 * fixed crash caused by opengl_get_region.
37 * Revision 1.2 2002/05/07 03:16:48 theoddone33
38 * The Great Newline Fix
40 * Revision 1.1.1.1 2002/05/03 03:28:10 root
44 * 11 7/21/99 8:10p Dave
45 * First run of supernova effect.
47 * 10 7/15/99 9:20a Andsager
48 * FS2_DEMO initial checkin
50 * 9 7/07/99 3:11p Dave
51 * Fix for uninitialized particle system data.
53 * 8 4/22/99 11:06p Dave
54 * Final pass at beam weapons. Solidified a lot of stuff. All that remains
55 * now is to tweak and fix bugs as they come up. No new beam weapon
58 * 7 1/29/99 12:47a Dave
59 * Put in sounds for beam weapon. A bunch of interface screens (tech
62 * 6 1/28/99 9:10a Andsager
63 * Particles increased in width, life, number. Max particles increased
65 * 5 1/27/99 9:56a Dave
66 * Temporary checkin of beam weapons for Dan to make cool sounds.
68 * 4 1/24/99 11:37p Dave
69 * First full rev of beam weapons. Very customizable. Removed some bogus
70 * Int3()'s in low level net code.
72 * 3 1/21/99 2:06p Dave
73 * Final checkin for multiplayer testing.
75 * 2 10/07/98 10:53a Dave
78 * 1 10/07/98 10:50a Dave
80 * 28 5/13/98 3:25p John
81 * Added code to make explosion impacts not get removed by other
84 * 27 5/11/98 10:06a John
85 * Added new particle for Adam
87 * 26 4/30/98 11:31a Andsager
88 * Added particles to big ship explosions. Modified particle_emit() to
89 * take optional range to increase range at which pariticles are created.
91 * 25 4/17/98 1:42p Allender
92 * fixed NDEBUG build problems
94 * 24 4/17/98 6:58a John
95 * Made particles not reduce in low mem conditions.
97 * 23 4/15/98 4:21p John
98 * Made particles drop off with distance smoothly. Made old particles get
99 * deleted by new ones.
101 * 22 4/15/98 11:15a Adam
102 * upped MAX_PARTICLES to 1500 (from 800). Primary weapon hit sparks were
105 * 21 4/12/98 10:24a John
106 * Made particle drop off distance larger for larger detail levels.
108 * 20 4/09/98 7:58p John
109 * Cleaned up tmapper code a bit. Put NDEBUG around some ndebug stuff.
110 * Took out XPARENT flag settings in all the alpha-blended texture stuff.
112 * 19 4/02/98 11:40a Lawrance
113 * check for #ifdef DEMO instead of #ifdef DEMO_RELEASE
115 * 18 4/01/98 9:21p John
116 * Made NDEBUG, optimized build with no warnings or errors.
118 * 17 4/01/98 10:57a Mike
119 * Reduce array sizes to save memory.
121 * 16 3/31/98 5:18p John
122 * Removed demo/save/restore. Made NDEBUG defined compile. Removed a
123 * bunch of debug stuff out of player file. Made model code be able to
124 * unload models and malloc out only however many models are needed.
127 * 15 3/30/98 4:02p John
128 * Made machines with < 32 MB of RAM use every other frame of certain
129 * bitmaps. Put in code to keep track of how much RAM we've malloc'd.
131 * 14 3/26/98 5:26p John
132 * added new paging code. nonfunctional.
134 * 13 3/22/98 11:02a John
135 * Made a bunch of the detail levels actually do something
137 * 12 2/13/98 5:01p John
138 * Made particles behind you not render
140 * 11 2/06/98 7:28p John
141 * Made debris and particles not get created if > 200 m from eye. Added
142 * max_velocity to asteroid's physics info for aiding in throwing out
145 * 10 2/05/98 9:21p John
146 * Some new Direct3D code. Added code to monitor a ton of stuff in the
149 * 9 1/29/98 11:48a John
150 * Added new counter measure rendering as model code. Made weapons be
151 * able to have impact explosion.
153 * 8 1/26/98 5:10p John
154 * Took particle use out of registry.
156 * 7 1/23/98 5:08p John
157 * Took L out of vertex structure used B (blue) instead. Took all small
158 * fireballs out of fireball types and used particles instead. Fixed some
159 * debris explosion things. Restructured fireball code. Restructured
160 * some lighting code. Made dynamic lighting on by default. Made groups
161 * of lasers only cast one light. Made fireballs not cast light.
163 * 6 1/13/98 8:09p John
164 * Removed the old collision system that checked all pairs. Added code
165 * to disable collisions and particles.
167 * 5 1/02/98 5:04p John
168 * Several explosion related changes. Made fireballs not be used as
169 * ani's. Made ship spark system expell particles. Took away impact
170 * explosion for weapon hitting ship... this needs to get added to weapon
171 * info and makes shield hit more obvious. Only make sparks when hit
174 * 4 12/30/97 6:44p John
175 * Made g3_Draw_bitmap functions account for aspect of bitmap.
177 * 3 12/26/97 5:42p Adam
179 * 2 12/23/97 3:58p John
180 * Second rev of particles
182 * 1 12/23/97 8:26a John
188 #include "freespace.h"
189 #include "linklist.h"
192 #include "particle.h"
197 typedef struct particle {
199 vector pos; // position
200 vector velocity; // velocity
201 float age; // How long it's been alive
202 float max_life; // How much life we had
203 float radius; // radius
204 int type; // type // -1 = None
205 uint optional_data; // depends on type
206 int nframes; // If an ani, how many frames?
209 float tracer_length; // if this is set, draw as a rod to simulate a "tracer" effect
210 short attached_objnum; // if this is set, pos is relative to the attached object. velocity is ignored
211 int attached_sig; // to check for dead/nonexistent objects
212 ubyte reverse; // play any animations in reverse
215 #if defined(FS2_DEMO) || defined(FS1_DEMO)
216 #define MAX_PARTICLES 500
218 #define MAX_PARTICLES 2000 // Reduced from 2000 to 800 by MK on 4/1/98. Most I ever saw was 400 and the system recovers
219 // gracefully from running out of slots.
220 // AP: Put it to 1500 on 4/15/98. Primary hit sparks weren't finding open slots.
221 // Made todo item for John to force oldest smoke particles to give up their slots.
224 int Num_particles = 0;
225 particle Particles[MAX_PARTICLES];
226 int Next_particle = 0;
228 int Anim_bitmap_id_fire = -1;
229 int Anim_num_frames_fire = -1;
231 int Anim_bitmap_id_smoke = -1;
232 int Anim_num_frames_smoke = -1;
234 int Anim_bitmap_id_smoke2 = -1;
235 int Anim_num_frames_smoke2 = -1;
237 static int Particles_enabled = 1;
239 // Reset everything between levels
244 // Particles_enabled = os_config_read_uint( NULL, "UseParticles", 0 );
249 for (i=0; i<MAX_PARTICLES; i++ ) {
250 Particles[i].type = -1;
253 if ( Anim_bitmap_id_fire == -1 ) {
255 Anim_bitmap_id_fire = bm_load_animation( "particleexp01", &Anim_num_frames_fire, &fps, 1 );
257 //Anim_bitmap_id = bm_load( "particleglow01" );
258 //Anim_num_frames = 1;
260 if ( Anim_bitmap_id_smoke == -1 ) {
262 Anim_bitmap_id_smoke = bm_load_animation( "particlesmoke01", &Anim_num_frames_smoke, &fps, 1 );
265 if ( Anim_bitmap_id_smoke2 == -1 ) {
267 Anim_bitmap_id_smoke2 = bm_load_animation( "particlesmoke02", &Anim_num_frames_smoke2, &fps, 1 );
272 void particle_page_in()
276 for (i=0; i<MAX_PARTICLES; i++ ) {
277 Particles[i].type = -1;
280 for (i=0; i<Anim_num_frames_fire; i++ ) {
281 bm_page_in_texture( Anim_bitmap_id_fire + i );
284 for (i=0; i<Anim_num_frames_smoke; i++ ) {
285 bm_page_in_texture( Anim_bitmap_id_smoke + i );
288 for (i=0; i<Anim_num_frames_smoke2; i++ ) {
289 bm_page_in_texture( Anim_bitmap_id_smoke2 + i );
293 DCF(particles,"Turns particles on/off")
296 dc_get_arg(ARG_TRUE|ARG_FALSE|ARG_NONE);
297 if ( Dc_arg_type & ARG_TRUE ) Particles_enabled = 1;
298 else if ( Dc_arg_type & ARG_FALSE ) Particles_enabled = 0;
299 else if ( Dc_arg_type & ARG_NONE ) Particles_enabled ^= 1;
301 if ( Dc_help ) dc_printf( "Usage: particles [bool]\nTurns particle system on/off. If nothing passed, then toggles it.\n" );
302 if ( Dc_status ) dc_printf( "particles are %s\n", (Particles_enabled?"ON":"OFF") );
304 // os_config_write_uint( NULL, "UseParticles", Particles_enabled );
308 //mprintf(( "%s\n", text ));
310 int Num_particles_hwm = 0;
313 int Total_requested = 0;
314 int Total_killed = 0;
315 int next_message = -1;
318 // Creates a single particle. See the PARTICLE_?? defines for types.
319 void particle_create( particle_info *pinfo )
325 if ( next_message == -1 ) {
326 next_message = timestamp(10000);
329 if ( timestamp_elapsed(next_message)) {
330 next_message = timestamp(10000);
331 if ( Total_requested > 1 ) {
332 nprintf(( "Particles", "Particles: Killed off %d%% of the particles\n", (Total_killed*100/Total_requested) ));
339 if ( !Particles_enabled ) return;
346 int FirstParticleFound = Next_particle;
348 p = &Particles[Next_particle++];
349 if ( Next_particle >= MAX_PARTICLES ) {
353 if ( p->type > -1 ) {
354 // Only remove non-persistent ones
355 if ( p->type != PARTICLE_BITMAP_PERSISTENT ) {
363 // Keep trying to find a non-persistent one until we searched through 1/3 the slots.
364 if ( RetryCount < MAX_PARTICLES/3 ) {
367 // Couldn't find any non-persistent ones to remove, so just remove the
368 // first one we would have removed.
369 mprintf(( "DELETING A PERSISTENT PARTICLE!!! This is ok if this only happens rarely. Get John if not.\n" ));
370 Next_particle = FirstParticleFound;
371 p = &Particles[Next_particle++];
372 if ( Next_particle >= MAX_PARTICLES ) {
380 if (Num_particles > Num_particles_hwm) {
381 Num_particles_hwm = Num_particles;
383 if ( Num_particles_hwm == MAX_PARTICLES ) {
384 mprintf(( "All particle slots filled!\n" ));
386 //mprintf(( "Num_particles high water mark = %i\n", Num_particles_hwm));
390 particle_num = p-Particles;
392 // Init the particle data
394 p->velocity = pinfo->vel;
396 p->max_life = pinfo->lifetime;
397 p->radius = pinfo->rad;
398 p->type = pinfo->type;
399 p->optional_data = pinfo->optional_data;
400 p->tracer_length = pinfo->tracer_length;
401 p->attached_objnum = pinfo->attached_objnum;
402 p->attached_sig = pinfo->attached_sig;
403 p->reverse = pinfo->reverse;
405 if ( (p->type == PARTICLE_BITMAP) || (p->type == PARTICLE_BITMAP_PERSISTENT) ) {
407 bm_get_info( p->optional_data,NULL, NULL, NULL, &p->nframes, &fps );
408 if ( p->nframes > 1 ) {
409 // Recalculate max life for ani's
410 p->max_life = i2fl(p->nframes) / i2fl(fps);
417 void particle_create( vector *pos, vector *vel, float lifetime, float rad, int type, uint optional_data )
424 pinfo.lifetime = lifetime;
427 pinfo.optional_data = optional_data;
430 pinfo.tracer_length = -1.0f;
431 pinfo.attached_objnum = -1;
432 pinfo.attached_sig = -1;
435 // lower level function
436 particle_create(&pinfo);
439 MONITOR( NumParticles );
441 void particle_move_all(float frametime)
445 MONITOR_INC( NumParticles, Num_particles );
447 if ( !Particles_enabled ) return;
452 for ( i=0; i<MAX_PARTICLES; i++, p++ ) {
454 if ( p->type == -1 ) {
458 // bogus attached objnum
459 if(p->attached_objnum >= MAX_OBJECTS){
465 Assert(Num_particles >= 0);
469 // if the vector is attached to an object which has become invalid, kill if
470 if(p->attached_objnum >= 0){
471 // if the signature has changed, kill it
472 if(p->attached_sig != Objects[p->attached_objnum].signature){
478 Assert(Num_particles >= 0);
482 // move as a regular particle
485 vm_vec_scale_add2( &p->pos, &p->velocity, frametime );
490 if ( p->age > p->max_life ) {
492 // If it's time expired, remove it from the used list and
493 // into the free list
499 Assert(Num_particles >= 0);
504 // kill all active particles
505 void particle_kill_all()
509 // kill all active particles
512 Num_particles_hwm = 0;
513 for(idx=0; idx<MAX_PARTICLES; idx++){
514 Particles[idx].type = -1;
518 MONITOR( NumParticlesRend );
520 void particle_render_all()
530 if ( !Particles_enabled ) return;
532 MONITOR_INC( NumParticlesRend, Num_particles );
540 for ( i=0; i<MAX_PARTICLES; i++, p++ ) {
542 if ( p->type == -1 ) {
548 // pct complete for the particle
549 pct_complete = p->age / p->max_life;
551 // calculate the alpha to draw at
554 // if this is a tracer style particle, calculate tracer vectors
555 if(p->tracer_length > 0.0f){
558 vm_vec_normalize_quick(&temp);
559 vm_vec_scale_add(&te, &ts, &temp, p->tracer_length);
561 // don't bother rotating
564 // if this is an "attached" particle. move it
565 else if(p->attached_objnum >= 0){
566 // offset the vector, and transform to view coords
567 // vm_vec_add(&te, &Objects[p->attached_objnum].pos, &p->pos);
568 vm_vec_unrotate(&temp, &p->pos, &Objects[p->attached_objnum].orient);
569 vm_vec_add2(&temp, &Objects[p->attached_objnum].pos);
571 flags = g3_rotate_vertex(&pos, &temp);
577 // don't bother rotating again
583 flags = g3_rotate_vertex( &pos, &p->pos );
592 case PARTICLE_DEBUG: // A red sphere, no optional data required
593 gr_set_color( 255, 0, 0 );
594 g3_draw_sphere_ez( &p->pos, p->radius );
597 case PARTICLE_BITMAP:
598 case PARTICLE_BITMAP_PERSISTENT:
599 { // A bitmap, optional data is the bitmap number
600 int framenum = p->optional_data;
602 if ( p->nframes > 1 ) {
603 int n = fl2i(pct_complete * p->nframes + 0.5);
606 else if ( n > p->nframes-1 ) n = p->nframes-1;
612 gr_set_bitmap( framenum, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha, -1, -1);
614 // if this is a tracer style particle
615 if(p->tracer_length > 0.0f){
616 g3_draw_laser(&ts, p->radius, &te, p->radius, TMAP_FLAG_TEXTURED|TMAP_FLAG_XPARENT, 25.0f);
618 // draw as a regular bitmap
620 g3_draw_bitmap(&pos, (p-Particles)%8, p->radius, TMAP_FLAG_TEXTURED );
625 case PARTICLE_FIRE: {
626 int framenum = fl2i(pct_complete * Anim_num_frames_fire + 0.5);
628 if ( framenum < 0 ) framenum = 0;
629 else if ( framenum > Anim_num_frames_fire-1 ) framenum = Anim_num_frames_fire-1;
633 flags = g3_rotate_vertex( &pos, &p->pos );
641 gr_set_bitmap(p->reverse ? Anim_bitmap_id_fire+(Anim_num_frames_fire - framenum - 1) : Anim_bitmap_id_fire+framenum, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha, -1, -1);
643 // if this is a tracer style particle
644 if(p->tracer_length > 0.0f){
645 g3_draw_laser(&ts, p->radius, &te, p->radius, TMAP_FLAG_TEXTURED|TMAP_FLAG_XPARENT, 25.0f);
647 // draw as a regular bitmap
649 g3_draw_bitmap(&pos, (p-Particles)%8, p->radius, TMAP_FLAG_TEXTURED );
654 case PARTICLE_SMOKE: {
655 int framenum = fl2i(pct_complete * Anim_num_frames_smoke + 0.5);
657 if ( framenum < 0 ) framenum = 0;
658 else if ( framenum > Anim_num_frames_smoke-1 ) framenum = Anim_num_frames_smoke-1;
662 flags = g3_rotate_vertex( &pos, &p->pos );
670 gr_set_bitmap(p->reverse ? Anim_bitmap_id_smoke+(Anim_num_frames_smoke - framenum - 1) : Anim_bitmap_id_smoke+framenum, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha, -1, -1);
672 // if this is a tracer style particle
673 if(p->tracer_length > 0.0f){
674 g3_draw_laser(&ts, p->radius, &te, p->radius, TMAP_FLAG_TEXTURED|TMAP_FLAG_XPARENT, 25.0f);
676 // draw as a regular bitmap
678 g3_draw_bitmap(&pos, (p-Particles)%8, p->radius, TMAP_FLAG_TEXTURED );
683 case PARTICLE_SMOKE2: {
684 int framenum = fl2i(pct_complete * Anim_num_frames_smoke2 + 0.5);
686 if ( framenum < 0 ) framenum = 0;
687 else if ( framenum > Anim_num_frames_smoke2-1 ) framenum = Anim_num_frames_smoke2-1;
691 flags = g3_rotate_vertex( &pos, &p->pos );
699 gr_set_bitmap(p->reverse ? Anim_bitmap_id_smoke2+(Anim_num_frames_smoke2 - framenum - 1) : Anim_bitmap_id_smoke2+framenum, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha, -1, -1);
701 // if this is a tracer style particle
702 if(p->tracer_length > 0.0f){
703 g3_draw_laser(&ts, p->radius, &te, p->radius, TMAP_FLAG_TEXTURED|TMAP_FLAG_XPARENT, 25.0f);
705 // draw as a regular bitmap
707 g3_draw_bitmap(&pos, (p-Particles)%8, p->radius, TMAP_FLAG_TEXTURED );
713 // mprintf(( "NP=%d, NCP=%d\n", n, nclipped ));
719 //============================================================================
720 //============== HIGH-LEVEL PARTICLE SYSTEM CREATION CODE ====================
721 //============================================================================
723 // Use a structure rather than pass a ton of parameters to particle_emit
725 typedef struct particle_emitter {
726 int num_low; // Lowest number of particles to create
727 int num_high; // Highest number of particles to create
728 vector pos; // Where the particles emit from
729 vector vel; // Initial velocity of all the particles
730 float lifetime; // How long the particles live
731 vector normal; // What normal the particle emit arond
732 float normal_variance; // How close they stick to that normal 0=good, 1=360 degree
733 float min_vel; // How fast the slowest particle can move
734 float max_vel; // How fast the fastest particle can move
735 float min_rad; // Min radius
736 float max_rad; // Max radius
740 #if MAX_DETAIL_LEVEL != 4
741 #error Max details assumed to be 4 here
743 int detail_max_num[5] = { 0, 50, 75, 100, 125 };
745 // Creates a bunch of particles. You pass a structure
746 // rather than a bunch of parameters.
747 void particle_emit( particle_emitter *pe, int type, uint optional_data, float range )
751 if ( !Particles_enabled ) return;
755 // Account for detail
756 #if MAX_DETAIL_LEVEL != 4
757 #error Code in Particle.cpp assumes MAX_DETAIL_LEVEL == 4
760 int percent = detail_max_num[Detail.num_particles];
762 //Particle rendering drops out too soon. Seems to be around 150 m. Is it detail level controllable? I'd like it to be 500-1000
763 float min_dist = 125.0f;
764 float dist = vm_vec_dist_quick( &pe->pos, &Eye_position ) / range;
765 if ( dist > min_dist ) {
766 percent = fl2i( i2fl(percent)*min_dist / dist );
771 //mprintf(( "Dist = %.1f, percent = %d%%\n", dist, percent ));
773 n1 = (pe->num_low*percent)/100;
774 n2 = (pe->num_high*percent)/100;
777 n = (rand() % (n2-n1+1)) + n1;
782 for (i=0; i<n; i++ ) {
785 vector normal; // What normal the particle emit arond
787 float radius = (( pe->max_rad - pe->min_rad ) * frand()) + pe->min_rad;
789 float speed = (( pe->max_vel - pe->min_vel ) * frand()) + pe->min_vel;
791 float life = (( pe->max_life - pe->min_life ) * frand()) + pe->min_life;
793 normal.xyz.x = pe->normal.xyz.x + (frand()*2.0f - 1.0f)*pe->normal_variance;
794 normal.xyz.y = pe->normal.xyz.y + (frand()*2.0f - 1.0f)*pe->normal_variance;
795 normal.xyz.z = pe->normal.xyz.z + (frand()*2.0f - 1.0f)*pe->normal_variance;
796 vm_vec_normalize_safe( &normal );
797 vm_vec_scale_add( &tmp_vel, &pe->vel, &normal, speed );
799 particle_create( &pe->pos, &tmp_vel, life, radius, type, optional_data );