nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include "opengl.h" nuclear@0: #include "psys.h" nuclear@0: nuclear@0: #define MAX_SPAWNMAP_SAMPLES 2048 nuclear@0: nuclear@0: static double frand(); nuclear@0: static float rndval(float x, float range); nuclear@0: static Particle *palloc(); nuclear@0: static void pfree(Particle *p); nuclear@0: static void pfreelist(Particle *p); nuclear@0: nuclear@0: void psys_default(PSysParam *pp) nuclear@0: { nuclear@0: // default parameters nuclear@0: pp->spawn_rate = 10.0; nuclear@0: pp->spawn_range = 0.0; nuclear@0: pp->life = 1.0; nuclear@0: pp->life_range = 0.0; nuclear@0: pp->size = 1.0; nuclear@0: pp->size_range = 0.0; nuclear@0: pp->spawn_map = 0; nuclear@0: pp->spawn_map_speed = 0.0; nuclear@0: nuclear@0: pp->gravity = Vector3(0, -9.2, 0); nuclear@0: nuclear@0: pp->pimg = 0; nuclear@0: pp->pcolor_start = pp->pcolor_mid = pp->pcolor_end = Vector3(1, 1, 1); nuclear@0: pp->palpha_start = 1.0; nuclear@0: pp->palpha_mid = 0.5; nuclear@0: pp->palpha_end = 0.0; nuclear@0: pp->pscale_start = pp->pscale_mid = pp->pscale_end = 1.0; nuclear@0: } nuclear@0: nuclear@0: ParticleSystem::ParticleSystem() nuclear@0: { nuclear@0: active = true; nuclear@0: active_time = 0.0f; nuclear@0: spawn_pending = 0.0f; nuclear@0: plist = 0; nuclear@0: pcount = 0; nuclear@0: smcache = 0; nuclear@0: nuclear@0: expl = false; nuclear@0: expl_force = expl_dur = 0.0f; nuclear@0: expl_life = 0.0f; nuclear@0: nuclear@0: psys_default(&pp); nuclear@0: } nuclear@0: nuclear@0: ParticleSystem::~ParticleSystem() nuclear@0: { nuclear@0: pfreelist(plist); nuclear@0: delete [] smcache; nuclear@0: } nuclear@0: nuclear@0: void ParticleSystem::explode(const Vector3 &c, float force, float dur, float life) nuclear@0: { nuclear@0: expl_dur = dur; nuclear@0: expl_force = force; nuclear@0: expl_cent = c; nuclear@0: expl_life = life; nuclear@0: expl = true; nuclear@0: } nuclear@0: nuclear@0: bool ParticleSystem::alive() const nuclear@0: { nuclear@0: return active || pcount > 0; nuclear@0: } nuclear@0: nuclear@0: void ParticleSystem::update(float dt) nuclear@0: { nuclear@0: if(pp.spawn_map && !smcache) { nuclear@0: gen_spawnmap(MAX_SPAWNMAP_SAMPLES); nuclear@0: } nuclear@0: nuclear@0: if(active) { nuclear@0: active_time += dt; nuclear@0: } nuclear@0: nuclear@0: if(expl) { nuclear@0: expl = false; nuclear@0: //active = false; nuclear@0: nuclear@0: Vector3 cent = expl_cent + pos; nuclear@0: nuclear@0: Particle *p = plist; nuclear@0: while(p) { nuclear@0: p->max_life = expl_dur; nuclear@0: Vector3 dir = p->pos - cent; nuclear@0: p->vel += (normalize(dir + Vector3((frand() - 0.5) * 0.5, frand() - 0.5, (frand() - 0.5) * 0.5))) * expl_force; nuclear@0: p = p->next; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(expl_life > 0.0) { nuclear@0: expl_life -= dt; nuclear@0: if(expl_life <= 0.0) { nuclear@0: expl_life = 0.0; nuclear@0: active = false; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // update active particles nuclear@0: Particle *p = plist; nuclear@0: while(p) { nuclear@0: p->life += dt; nuclear@0: if(p->life < p->max_life) { nuclear@0: float t = p->life / p->max_life; nuclear@0: nuclear@0: p->pos = p->pos + p->vel * dt; nuclear@0: p->vel = p->vel + pp.gravity * dt; nuclear@0: nuclear@0: if(t < 0.5) { nuclear@0: t *= 2.0; nuclear@0: p->color = lerp(pp.pcolor_start, pp.pcolor_mid, t); nuclear@0: p->alpha = lerp(pp.palpha_start, pp.palpha_mid, t); nuclear@0: p->scale = lerp(pp.pscale_start, pp.pscale_mid, t); nuclear@0: } else { nuclear@0: t = (t - 0.5) * 2.0; nuclear@0: p->color = lerp(pp.pcolor_mid, pp.pcolor_end, t); nuclear@0: p->alpha = lerp(pp.palpha_mid, pp.palpha_end, t); nuclear@0: p->scale = lerp(pp.pscale_mid, pp.pscale_end, t); nuclear@0: } nuclear@0: nuclear@0: } else { nuclear@0: p->life = -1.0; nuclear@0: } nuclear@0: p = p->next; nuclear@0: } nuclear@0: nuclear@0: // remove dead particles nuclear@0: Particle dummy; nuclear@0: dummy.next = plist; nuclear@0: p = &dummy; nuclear@0: while(p->next) { nuclear@0: if(p->next->life < 0.0) { nuclear@0: Particle *tmp = p->next; nuclear@0: p->next = tmp->next; nuclear@0: pfree(tmp); nuclear@0: --pcount; nuclear@0: } else { nuclear@0: p = p->next; nuclear@0: } nuclear@0: } nuclear@0: plist = dummy.next; nuclear@0: nuclear@0: float spawn_rate = pp.spawn_rate; nuclear@0: if(pp.spawn_map && pp.spawn_map_speed > 0.0) { nuclear@0: float s = active_time * pp.spawn_map_speed; nuclear@0: if(s > 1.0) s = 1.0; nuclear@0: spawn_rate *= s; nuclear@0: } nuclear@0: nuclear@0: // spawn particles as needed nuclear@0: if(active) { nuclear@0: spawn_pending += spawn_rate * dt; nuclear@0: nuclear@0: while(spawn_pending >= 1.0f) { nuclear@0: spawn_pending -= 1.0f; nuclear@0: spawn_particle(); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: void ParticleSystem::draw() const nuclear@0: { nuclear@0: int cur_sdr = 0; nuclear@0: glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr); nuclear@0: if(cur_sdr) { nuclear@0: glUseProgram(0); nuclear@0: } nuclear@0: nuclear@0: glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT); nuclear@0: glDisable(GL_LIGHTING); nuclear@0: glEnable(GL_BLEND); nuclear@0: glBlendFunc(GL_SRC_ALPHA, GL_ONE); nuclear@0: nuclear@0: if(pp.pimg) { nuclear@0: if(!pp.pimg->texture) { nuclear@0: pp.pimg->gen_texture(); nuclear@0: } nuclear@0: glEnable(GL_TEXTURE_2D); nuclear@0: glBindTexture(GL_TEXTURE_2D, pp.pimg->texture); nuclear@0: } nuclear@0: nuclear@0: glBegin(GL_QUADS); nuclear@0: Particle *p = plist; nuclear@0: while(p) { nuclear@0: float hsz = p->size * p->scale * 0.5; nuclear@0: glColor4f(p->color.x, p->color.y, p->color.z, p->alpha); nuclear@0: glTexCoord2f(0, 0); glVertex3f(p->pos.x - hsz, p->pos.y - hsz, p->pos.z); nuclear@0: glTexCoord2f(1, 0); glVertex3f(p->pos.x + hsz, p->pos.y - hsz, p->pos.z); nuclear@0: glTexCoord2f(1, 1); glVertex3f(p->pos.x + hsz, p->pos.y + hsz, p->pos.z); nuclear@0: glTexCoord2f(0, 1); glVertex3f(p->pos.x - hsz, p->pos.y + hsz, p->pos.z); nuclear@0: p = p->next; nuclear@0: } nuclear@0: glEnd(); nuclear@0: nuclear@0: glPopAttrib(); nuclear@0: nuclear@0: if(cur_sdr) { nuclear@0: glUseProgram(cur_sdr); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: void ParticleSystem::gen_spawnmap(int count) nuclear@0: { nuclear@0: Image *img = pp.spawn_map; nuclear@0: if(!img) return; nuclear@0: nuclear@0: delete [] smcache; nuclear@0: smcache = new Vector3[count]; nuclear@0: nuclear@0: float umax = (float)img->width; nuclear@0: float vmax = (float)img->height; nuclear@0: float aspect = umax / vmax; nuclear@0: nuclear@0: // first generate a bunch of random samples by rejection sampling nuclear@0: printf("generating %d random spawnmap samples\n", count); nuclear@0: for(int i=0; ipixels + (y * img->width + x) * 3; nuclear@0: val = pptr[0]; nuclear@0: ord = pptr[1]; nuclear@0: } while(val < 192); nuclear@0: nuclear@0: smcache[i] = Vector3(u * 2.0 - 1.0, (1.0 - v * 2.0) / aspect, ord / 255.0); nuclear@0: } nuclear@0: nuclear@0: // then order by z nuclear@0: std::sort(smcache, smcache + count, nuclear@0: [](const Vector3 &a, const Vector3 &b) { return a.z < b.z; }); nuclear@0: nuclear@0: // precalculate the bounds of each slot nuclear@0: smcache_max[0] = 0; nuclear@0: for(int i=1; i<255; i++) { nuclear@0: float maxval = (float)i / 255.0; nuclear@0: nuclear@0: int idx = smcache_max[i - 1]; nuclear@0: while(++idx < count && smcache[idx].z < maxval); nuclear@0: smcache_max[i] = idx; nuclear@0: } nuclear@0: smcache_max[255] = count; nuclear@0: } nuclear@0: nuclear@0: static double frand() nuclear@0: { nuclear@0: return (double)rand() / (double)RAND_MAX; nuclear@0: } nuclear@0: nuclear@0: static float rndval(float x, float range) nuclear@0: { nuclear@0: if(fabs(range) < 1e-6) { nuclear@0: return x; nuclear@0: } nuclear@0: return x + (frand() * range - range * 0.5); nuclear@0: } nuclear@0: nuclear@0: void ParticleSystem::spawn_particle() nuclear@0: { nuclear@0: Particle *p = palloc(); nuclear@0: p->pos = Vector3(rndval(pos.x, pp.spawn_range), nuclear@0: rndval(pos.y, pp.spawn_range), nuclear@0: rndval(pos.z, pp.spawn_range)); nuclear@0: p->vel = Vector3(0, 0, 0); nuclear@0: p->color = pp.pcolor_start; nuclear@0: p->alpha = pp.palpha_start; nuclear@0: p->life = 0.0; nuclear@0: p->max_life = rndval(pp.life, pp.life_range); nuclear@0: p->size = rndval(pp.size, pp.size_range); nuclear@0: p->scale = pp.pscale_start; nuclear@0: nuclear@0: if(pp.spawn_map) { nuclear@0: float maxz = pp.spawn_map_speed > 0.0 ? active_time * pp.spawn_map_speed : 1.0; nuclear@0: int max_idx = (int)(maxz * 255.0); nuclear@0: if(max_idx > 255) max_idx = 255; nuclear@0: if(max_idx < 1) max_idx = 1; nuclear@0: nuclear@0: int idx = rand() % smcache_max[max_idx]; nuclear@0: nuclear@0: p->pos.x += smcache[idx].x; nuclear@0: p->pos.y += smcache[idx].y; nuclear@0: } nuclear@0: nuclear@0: p->next = plist; nuclear@0: plist = p; nuclear@0: ++pcount; nuclear@0: } nuclear@0: nuclear@0: // particle allocator nuclear@0: #define MAX_POOL_SIZE 8192 nuclear@0: static Particle *ppool; nuclear@0: static int ppool_size; nuclear@0: nuclear@0: static Particle *palloc() nuclear@0: { nuclear@0: if(ppool) { nuclear@0: Particle *p = ppool; nuclear@0: ppool = ppool->next; nuclear@0: --ppool_size; nuclear@0: return p; nuclear@0: } nuclear@0: return new Particle; nuclear@0: } nuclear@0: nuclear@0: static void pfree(Particle *p) nuclear@0: { nuclear@0: if(!p) return; nuclear@0: nuclear@0: if(ppool_size < MAX_POOL_SIZE) { nuclear@0: p->next = ppool; nuclear@0: ppool = p; nuclear@0: ++ppool_size; nuclear@0: } else { nuclear@0: delete p; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: static void pfreelist(Particle *p) nuclear@0: { nuclear@0: if(!p) return; nuclear@0: nuclear@0: Particle *it = p; nuclear@0: int new_pool_size = ppool_size; nuclear@0: nuclear@0: while(it->next && new_pool_size < MAX_POOL_SIZE) { nuclear@0: it = it->next; nuclear@0: ++new_pool_size; nuclear@0: } nuclear@0: nuclear@0: Particle *last = it; nuclear@0: it = it->next; nuclear@0: nuclear@0: // add the first lot to the pool nuclear@0: last->next = ppool; nuclear@0: ppool = p; nuclear@0: ppool_size = new_pool_size; nuclear@0: nuclear@0: // delete the rest; nuclear@0: while(it) { nuclear@0: p = it; nuclear@0: it = it->next; nuclear@0: delete p; nuclear@0: } nuclear@0: }