/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #include "globalincs/systemvars.h" #include "graphics/2d.h" #include "render/3d.h" #include "bmpman/bmpman.h" #include "particle/particle.h" #include "object/object.h" #include "cmdline/cmdline.h" #include "graphics/grbatch.h" #ifndef NDEBUG #include "io/timer.h" #endif typedef struct particle { // old style data vec3d pos; // position vec3d velocity; // velocity float age; // How long it's been alive float max_life; // How much life we had float radius; // radius int type; // type // -1 = None uint optional_data; // depends on type int nframes; // If an ani, how many frames? // new style data float tracer_length; // if this is set, draw as a rod to simulate a "tracer" effect short attached_objnum; // if this is set, pos is relative to the attached object. velocity is ignored int attached_sig; // to check for dead/nonexistent objects ubyte reverse; // play any animations in reverse int particle_index; // used to keep particle offset in dynamic array for orient usage } particle; int Num_particles = 0; static SCP_vector Particles; int Next_particle = 0; int Anim_bitmap_id_fire = -1; int Anim_num_frames_fire = -1; int Anim_bitmap_id_smoke = -1; int Anim_num_frames_smoke = -1; int Anim_bitmap_id_smoke2 = -1; int Anim_num_frames_smoke2 = -1; static int Particles_enabled = 1; geometry_batcher *PARTICLE_FIRE_batcher = NULL; geometry_batcher *PARTICLE_SMOKE_batcher = NULL; geometry_batcher *PARTICLE_SMOKE2_batcher = NULL; // Reset everything between levels void particle_init() { int fps; // Particles_enabled = os_config_read_uint( NULL, "UseParticles", 0 ); Num_particles = 0; Next_particle = 0; Particles.clear(); // FIRE!!! if ( Anim_bitmap_id_fire == -1 ) { Anim_bitmap_id_fire = bm_load_animation( "particleexp01", &Anim_num_frames_fire, &fps, NULL, 0 ); } if ( (Anim_bitmap_id_fire > -1) && (PARTICLE_FIRE_batcher == NULL) ) { Assert( Anim_num_frames_fire > 0 ); PARTICLE_FIRE_batcher = new geometry_batcher[Anim_num_frames_fire]; Verify( PARTICLE_FIRE_batcher != NULL ); } // Cough, cough if ( Anim_bitmap_id_smoke == -1 ) { Anim_bitmap_id_smoke = bm_load_animation( "particlesmoke01", &Anim_num_frames_smoke, &fps, NULL, 0 ); } if ( (Anim_bitmap_id_smoke > -1) && (PARTICLE_SMOKE_batcher == NULL) ) { Assert( Anim_num_frames_smoke > 0 ); PARTICLE_SMOKE_batcher = new geometry_batcher[Anim_num_frames_smoke]; Verify( PARTICLE_SMOKE_batcher != NULL ); } // wheeze if ( Anim_bitmap_id_smoke2 == -1 ) { Anim_bitmap_id_smoke2 = bm_load_animation( "particlesmoke02", &Anim_num_frames_smoke2, &fps, NULL, 0 ); } if ( (Anim_bitmap_id_smoke2 > -1) && (PARTICLE_SMOKE2_batcher == NULL) ) { Assert( Anim_num_frames_smoke2 > 0 ); PARTICLE_SMOKE2_batcher = new geometry_batcher[Anim_num_frames_smoke2]; Verify( PARTICLE_SMOKE2_batcher != NULL ); } } // only call from game_shutdown()!!! void particle_close() { if ( PARTICLE_FIRE_batcher != NULL ) { delete[] PARTICLE_FIRE_batcher; PARTICLE_FIRE_batcher = NULL; } if ( PARTICLE_SMOKE_batcher != NULL ) { delete[] PARTICLE_SMOKE_batcher; PARTICLE_SMOKE_batcher = NULL; } if ( PARTICLE_SMOKE2_batcher != NULL ) { delete[] PARTICLE_SMOKE2_batcher; PARTICLE_SMOKE2_batcher = NULL; } } void particle_page_in() { if (Anim_bitmap_id_fire > -1) { bm_page_in_texture( Anim_bitmap_id_fire, Anim_num_frames_fire ); } if (Anim_bitmap_id_smoke > -1) { bm_page_in_texture( Anim_bitmap_id_smoke, Anim_num_frames_smoke ); } if (Anim_bitmap_id_smoke2 > -1) { bm_page_in_texture( Anim_bitmap_id_smoke2, Anim_num_frames_smoke2 ); } } DCF(particles,"Turns particles on/off") { if ( Dc_command ) { dc_get_arg(ARG_TRUE|ARG_FALSE|ARG_NONE); if ( Dc_arg_type & ARG_TRUE ) Particles_enabled = 1; else if ( Dc_arg_type & ARG_FALSE ) Particles_enabled = 0; else if ( Dc_arg_type & ARG_NONE ) Particles_enabled ^= 1; } if ( Dc_help ) dc_printf( "Usage: particles [bool]\nTurns particle system on/off. If nothing passed, then toggles it.\n" ); if ( Dc_status ) dc_printf( "particles are %s\n", (Particles_enabled?"ON":"OFF") ); // os_config_write_uint( NULL, "UseParticles", Particles_enabled ); } int Num_particles_hwm = 0; // Creates a single particle. See the PARTICLE_?? defines for types. void particle_create( particle_info *pinfo ) { particle new_particle; if ( !Particles_enabled ) return; #ifndef NDEBUG if (Particles.size() > (uint)Num_particles_hwm) { Num_particles_hwm = (int)Particles.size(); nprintf(("Particles", "Num_particles high water mark = %i\n", Num_particles_hwm)); } #endif // Init the particle data memset( &new_particle, 0, sizeof(particle) ); new_particle.pos = pinfo->pos; new_particle.velocity = pinfo->vel; new_particle.age = 0.0f; new_particle.max_life = pinfo->lifetime; new_particle.radius = pinfo->rad; new_particle.type = pinfo->type; new_particle.optional_data = pinfo->optional_data; new_particle.tracer_length = pinfo->tracer_length; new_particle.attached_objnum = pinfo->attached_objnum; new_particle.attached_sig = pinfo->attached_sig; new_particle.reverse = pinfo->reverse; new_particle.particle_index = (int)Particles.size(); if ( (new_particle.type == PARTICLE_BITMAP) || (new_particle.type == PARTICLE_BITMAP_PERSISTENT) ) { int fps; bm_get_info( new_particle.optional_data, NULL, NULL, NULL, &new_particle.nframes, &fps ); if ( new_particle.nframes > 1 ) { // Recalculate max life for ani's new_particle.max_life = i2fl(new_particle.nframes) / i2fl(fps); } } else { new_particle.nframes = 1; } Particles.push_back( new_particle ); } void particle_create( vec3d *pos, vec3d *vel, float lifetime, float rad, int type, uint optional_data, float tracer_length, object *objp, bool reverse ) { particle_info pinfo; if ( (type < 0) || (type >= NUM_PARTICLE_TYPES) ) { Int3(); return; } // setup old data pinfo.pos = *pos; pinfo.vel = *vel; pinfo.lifetime = lifetime; pinfo.rad = rad; pinfo.type = type; pinfo.optional_data = optional_data; // setup new data pinfo.tracer_length = -1.0f; if(objp == NULL) { pinfo.attached_objnum = -1; pinfo.attached_sig = -1; } else { pinfo.attached_objnum = OBJ_INDEX(objp); pinfo.attached_sig = objp->signature; } pinfo.reverse = reverse? 1 : 0; // lower level function particle_create(&pinfo); } MONITOR( NumParticles ) void particle_move_all(float frametime) { particle *p; MONITOR_INC( NumParticles, Num_particles ); if ( !Particles_enabled ) return; uint part_size = Particles.size(); for (uint i = 0; i < part_size; i++) { p = &Particles[i]; // bogus attached objnum if (p->attached_objnum >= MAX_OBJECTS) { Particles.erase( Particles.begin() + i ); part_size--; continue; } p->age += frametime; if ( p->age > p->max_life ) { // If it's time expired remove it Particles.erase( Particles.begin() + i ); part_size--; continue; } // if the vector is attached to an object which has become invalid, kill if if (p->attached_objnum >= 0) { // if the signature has changed, kill it if (p->attached_sig != Objects[p->attached_objnum].signature) { Particles.erase( Particles.begin() + i ); part_size--; continue; } } // move as a regular particle else { // Move the particle vm_vec_scale_add2( &p->pos, &p->velocity, frametime ); } } } // kill all active particles void particle_kill_all() { // int idx; // kill all active particles Num_particles = 0; Next_particle = 0; Num_particles_hwm = 0; Particles.clear(); } MONITOR( NumParticlesRend ) void particle_render_all() { particle *p; ubyte flags; float pct_complete; float alpha = 1.0f; vertex pos, pos_htl; vec3d ts, te, temp; int rotate = 1; int i; int framenum, cur_frame; if ( !Particles_enabled ) return; MONITOR_INC( NumParticlesRend, Num_particles ); // int n = 0; int nclipped = 0; int part_size = (int)Particles.size(); for (i = 0; i < part_size; i++) { p = &Particles[i]; // n++; // make sure "rotate" is enabled for this particle rotate = 1; // pct complete for the particle pct_complete = p->age / p->max_life; // calculate the alpha to draw at alpha = 1.0f; // if this is a tracer style particle, calculate tracer vectors if (p->tracer_length > 0.0f) { ts = p->pos; temp = p->velocity; vm_vec_normalize_quick(&temp); vm_vec_scale_add(&te, &ts, &temp, p->tracer_length); // don't bother rotating rotate = 0; } // if this is an "attached" particle. move it else if (p->attached_objnum >= 0) { // offset the vector, and transform to view coords // vm_vec_add(&te, &Objects[p->attached_objnum].pos, &p->pos); vm_vec_unrotate(&temp, &p->pos, &Objects[p->attached_objnum].orient); vm_vec_add2(&temp, &Objects[p->attached_objnum].pos); flags = g3_rotate_vertex(&pos, &temp); if (flags) { nclipped++; continue; } if (!Cmdline_nohtl) g3_transfer_vertex(&pos_htl, &temp); // don't bother rotating again rotate = 0; } // rotate the vertex if (rotate) { flags = g3_rotate_vertex( &pos, &p->pos ); if ( flags ) { nclipped++; continue; } if (!Cmdline_nohtl) g3_transfer_vertex(&pos_htl, &p->pos); } switch (p->type) { case PARTICLE_DEBUG: // A red sphere, no optional data required gr_set_color( 255, 0, 0 ); g3_draw_sphere_ez( &p->pos, p->radius ); break; case PARTICLE_BITMAP: case PARTICLE_BITMAP_PERSISTENT: { // A bitmap, optional data is the bitmap number framenum = p->optional_data; if ( p->nframes > 1 ) { int n = fl2i(pct_complete * p->nframes + 0.5); if ( n < 0 ) n = 0; else if ( n > p->nframes-1 ) n = p->nframes-1; framenum += n; } // if this is a tracer style particle if (p->tracer_length > 0.0f) { batch_add_laser( framenum, &ts, p->radius, &te, p->radius ); } // draw as a regular bitmap else { int tmap_flags = TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT; batch_add_bitmap( framenum, tmap_flags, (Cmdline_nohtl) ? &pos : &pos_htl, p->particle_index % 8, p->radius, alpha ); } break; } case PARTICLE_FIRE: { if ( Anim_bitmap_id_fire == -1 ) break; framenum = fl2i(pct_complete * Anim_num_frames_fire + 0.5); if ( framenum < 0 ) framenum = 0; else if ( framenum > Anim_num_frames_fire-1 ) framenum = Anim_num_frames_fire-1; cur_frame = p->reverse ? (Anim_num_frames_fire - framenum - 1) : framenum; Assert(cur_frame < Anim_num_frames_fire); PARTICLE_FIRE_batcher[cur_frame].add_allocate(1); // if this is a tracer style particle if (p->tracer_length > 0.0f) { PARTICLE_FIRE_batcher[cur_frame].draw_laser(&ts, p->radius, &te, p->radius, 255,255,255); } // draw as a regular bitmap else { PARTICLE_FIRE_batcher[cur_frame].draw_bitmap( (Cmdline_nohtl) ? &pos : &pos_htl, p->particle_index % 8, p->radius ); } break; } case PARTICLE_SMOKE: { if ( Anim_bitmap_id_smoke == -1 ) break; framenum = fl2i(pct_complete * Anim_num_frames_smoke + 0.5); if ( framenum < 0 ) framenum = 0; else if ( framenum > Anim_num_frames_smoke-1 ) framenum = Anim_num_frames_smoke-1; cur_frame = p->reverse ? (Anim_num_frames_smoke - framenum - 1) : framenum; Assert(cur_frame < Anim_num_frames_smoke); PARTICLE_SMOKE_batcher[cur_frame].add_allocate(1); // if this is a tracer style particle if (p->tracer_length > 0.0f) { PARTICLE_SMOKE_batcher[cur_frame].draw_laser(&ts, p->radius, &te, p->radius, 255,255,255); } // draw as a regular bitmap else { // as part of a coding mistake I thought the freaky UV changing from the orient var was really cool // and added some realism to the smoke effect. so, it's not using the original particle_index // for UV orient like all of the other effects but the current particle offset instead. - taylor PARTICLE_SMOKE_batcher[cur_frame].draw_bitmap( (Cmdline_nohtl) ? &pos : &pos_htl, (i + cur_frame) % 8, p->radius ); } break; } case PARTICLE_SMOKE2: { if ( Anim_bitmap_id_smoke2 == -1 ) break; framenum = fl2i(pct_complete * Anim_num_frames_smoke2 + 0.5); if ( framenum < 0 ) framenum = 0; else if ( framenum > Anim_num_frames_smoke2-1 ) framenum = Anim_num_frames_smoke2-1; cur_frame = p->reverse ? (Anim_num_frames_smoke2 - framenum - 1) : framenum; Assert(cur_frame < Anim_num_frames_smoke2); PARTICLE_SMOKE2_batcher[cur_frame].add_allocate(1); // if this is a tracer style particle if (p->tracer_length > 0.0f) { PARTICLE_SMOKE2_batcher[cur_frame].draw_laser(&ts, p->radius, &te, p->radius, 255,255,255); } // draw as a regular bitmap else { PARTICLE_SMOKE2_batcher[cur_frame].draw_bitmap( (Cmdline_nohtl) ? &pos : &pos_htl, p->particle_index % 8, p->radius ); } break; } } } for (i = 0; ipos, &Eye_position ) / range; if ( dist > min_dist ) { percent = fl2i( i2fl(percent)*min_dist / dist ); if ( percent < 1 ) { return; } } //mprintf(( "Dist = %.1f, percent = %d%%\n", dist, percent )); n1 = (pe->num_low*percent)/100; n2 = (pe->num_high*percent)/100; // How many to emit? n = (rand() % (n2-n1+1)) + n1; if ( n < 1 ) return; for (i=0; imax_rad - pe->min_rad ) * frand()) + pe->min_rad; float speed = (( pe->max_vel - pe->min_vel ) * frand()) + pe->min_vel; float life = (( pe->max_life - pe->min_life ) * frand()) + pe->min_life; normal.xyz.x = pe->normal.xyz.x + (frand()*2.0f - 1.0f)*pe->normal_variance; normal.xyz.y = pe->normal.xyz.y + (frand()*2.0f - 1.0f)*pe->normal_variance; normal.xyz.z = pe->normal.xyz.z + (frand()*2.0f - 1.0f)*pe->normal_variance; vm_vec_normalize_safe( &normal ); vm_vec_scale_add( &tmp_vel, &pe->vel, &normal, speed ); particle_create( &pe->pos, &tmp_vel, life, radius, type, optional_data ); } }