textpsys

annotate src/psys.cc @ 0:a4ffd9e6984c

initial commit
author John Tsiombikas <nuclear@member.fsf.org>
date Wed, 19 Aug 2015 09:13:48 +0300
parents
children 4b1360a5d54d
rev   line source
nuclear@0 1 #include <stdio.h>
nuclear@0 2 #include <stdlib.h>
nuclear@0 3 #include <vector>
nuclear@0 4 #include <algorithm>
nuclear@0 5 #include "opengl.h"
nuclear@0 6 #include "psys.h"
nuclear@0 7
nuclear@0 8 #define MAX_SPAWNMAP_SAMPLES 2048
nuclear@0 9
nuclear@0 10 static double frand();
nuclear@0 11 static float rndval(float x, float range);
nuclear@0 12 static Particle *palloc();
nuclear@0 13 static void pfree(Particle *p);
nuclear@0 14 static void pfreelist(Particle *p);
nuclear@0 15
nuclear@0 16 void psys_default(PSysParam *pp)
nuclear@0 17 {
nuclear@0 18 // default parameters
nuclear@0 19 pp->spawn_rate = 10.0;
nuclear@0 20 pp->spawn_range = 0.0;
nuclear@0 21 pp->life = 1.0;
nuclear@0 22 pp->life_range = 0.0;
nuclear@0 23 pp->size = 1.0;
nuclear@0 24 pp->size_range = 0.0;
nuclear@0 25 pp->spawn_map = 0;
nuclear@0 26 pp->spawn_map_speed = 0.0;
nuclear@0 27
nuclear@0 28 pp->gravity = Vector3(0, -9.2, 0);
nuclear@0 29
nuclear@0 30 pp->pimg = 0;
nuclear@0 31 pp->pcolor_start = pp->pcolor_mid = pp->pcolor_end = Vector3(1, 1, 1);
nuclear@0 32 pp->palpha_start = 1.0;
nuclear@0 33 pp->palpha_mid = 0.5;
nuclear@0 34 pp->palpha_end = 0.0;
nuclear@0 35 pp->pscale_start = pp->pscale_mid = pp->pscale_end = 1.0;
nuclear@0 36 }
nuclear@0 37
nuclear@0 38 ParticleSystem::ParticleSystem()
nuclear@0 39 {
nuclear@0 40 active = true;
nuclear@0 41 active_time = 0.0f;
nuclear@0 42 spawn_pending = 0.0f;
nuclear@0 43 plist = 0;
nuclear@0 44 pcount = 0;
nuclear@0 45 smcache = 0;
nuclear@0 46
nuclear@0 47 expl = false;
nuclear@0 48 expl_force = expl_dur = 0.0f;
nuclear@0 49 expl_life = 0.0f;
nuclear@0 50
nuclear@0 51 psys_default(&pp);
nuclear@0 52 }
nuclear@0 53
nuclear@0 54 ParticleSystem::~ParticleSystem()
nuclear@0 55 {
nuclear@0 56 pfreelist(plist);
nuclear@0 57 delete [] smcache;
nuclear@0 58 }
nuclear@0 59
nuclear@0 60 void ParticleSystem::explode(const Vector3 &c, float force, float dur, float life)
nuclear@0 61 {
nuclear@0 62 expl_dur = dur;
nuclear@0 63 expl_force = force;
nuclear@0 64 expl_cent = c;
nuclear@0 65 expl_life = life;
nuclear@0 66 expl = true;
nuclear@0 67 }
nuclear@0 68
nuclear@0 69 bool ParticleSystem::alive() const
nuclear@0 70 {
nuclear@0 71 return active || pcount > 0;
nuclear@0 72 }
nuclear@0 73
nuclear@0 74 void ParticleSystem::update(float dt)
nuclear@0 75 {
nuclear@0 76 if(pp.spawn_map && !smcache) {
nuclear@0 77 gen_spawnmap(MAX_SPAWNMAP_SAMPLES);
nuclear@0 78 }
nuclear@0 79
nuclear@0 80 if(active) {
nuclear@0 81 active_time += dt;
nuclear@0 82 }
nuclear@0 83
nuclear@0 84 if(expl) {
nuclear@0 85 expl = false;
nuclear@0 86 //active = false;
nuclear@0 87
nuclear@0 88 Vector3 cent = expl_cent + pos;
nuclear@0 89
nuclear@0 90 Particle *p = plist;
nuclear@0 91 while(p) {
nuclear@0 92 p->max_life = expl_dur;
nuclear@0 93 Vector3 dir = p->pos - cent;
nuclear@0 94 p->vel += (normalize(dir + Vector3((frand() - 0.5) * 0.5, frand() - 0.5, (frand() - 0.5) * 0.5))) * expl_force;
nuclear@0 95 p = p->next;
nuclear@0 96 }
nuclear@0 97 }
nuclear@0 98
nuclear@0 99 if(expl_life > 0.0) {
nuclear@0 100 expl_life -= dt;
nuclear@0 101 if(expl_life <= 0.0) {
nuclear@0 102 expl_life = 0.0;
nuclear@0 103 active = false;
nuclear@0 104 }
nuclear@0 105 }
nuclear@0 106
nuclear@0 107 // update active particles
nuclear@0 108 Particle *p = plist;
nuclear@0 109 while(p) {
nuclear@0 110 p->life += dt;
nuclear@0 111 if(p->life < p->max_life) {
nuclear@0 112 float t = p->life / p->max_life;
nuclear@0 113
nuclear@0 114 p->pos = p->pos + p->vel * dt;
nuclear@0 115 p->vel = p->vel + pp.gravity * dt;
nuclear@0 116
nuclear@0 117 if(t < 0.5) {
nuclear@0 118 t *= 2.0;
nuclear@0 119 p->color = lerp(pp.pcolor_start, pp.pcolor_mid, t);
nuclear@0 120 p->alpha = lerp(pp.palpha_start, pp.palpha_mid, t);
nuclear@0 121 p->scale = lerp(pp.pscale_start, pp.pscale_mid, t);
nuclear@0 122 } else {
nuclear@0 123 t = (t - 0.5) * 2.0;
nuclear@0 124 p->color = lerp(pp.pcolor_mid, pp.pcolor_end, t);
nuclear@0 125 p->alpha = lerp(pp.palpha_mid, pp.palpha_end, t);
nuclear@0 126 p->scale = lerp(pp.pscale_mid, pp.pscale_end, t);
nuclear@0 127 }
nuclear@0 128
nuclear@0 129 } else {
nuclear@0 130 p->life = -1.0;
nuclear@0 131 }
nuclear@0 132 p = p->next;
nuclear@0 133 }
nuclear@0 134
nuclear@0 135 // remove dead particles
nuclear@0 136 Particle dummy;
nuclear@0 137 dummy.next = plist;
nuclear@0 138 p = &dummy;
nuclear@0 139 while(p->next) {
nuclear@0 140 if(p->next->life < 0.0) {
nuclear@0 141 Particle *tmp = p->next;
nuclear@0 142 p->next = tmp->next;
nuclear@0 143 pfree(tmp);
nuclear@0 144 --pcount;
nuclear@0 145 } else {
nuclear@0 146 p = p->next;
nuclear@0 147 }
nuclear@0 148 }
nuclear@0 149 plist = dummy.next;
nuclear@0 150
nuclear@0 151 float spawn_rate = pp.spawn_rate;
nuclear@0 152 if(pp.spawn_map && pp.spawn_map_speed > 0.0) {
nuclear@0 153 float s = active_time * pp.spawn_map_speed;
nuclear@0 154 if(s > 1.0) s = 1.0;
nuclear@0 155 spawn_rate *= s;
nuclear@0 156 }
nuclear@0 157
nuclear@0 158 // spawn particles as needed
nuclear@0 159 if(active) {
nuclear@0 160 spawn_pending += spawn_rate * dt;
nuclear@0 161
nuclear@0 162 while(spawn_pending >= 1.0f) {
nuclear@0 163 spawn_pending -= 1.0f;
nuclear@0 164 spawn_particle();
nuclear@0 165 }
nuclear@0 166 }
nuclear@0 167 }
nuclear@0 168
nuclear@0 169 void ParticleSystem::draw() const
nuclear@0 170 {
nuclear@0 171 int cur_sdr = 0;
nuclear@0 172 glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
nuclear@0 173 if(cur_sdr) {
nuclear@0 174 glUseProgram(0);
nuclear@0 175 }
nuclear@0 176
nuclear@0 177 glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT);
nuclear@0 178 glDisable(GL_LIGHTING);
nuclear@0 179 glEnable(GL_BLEND);
nuclear@0 180 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
nuclear@0 181
nuclear@0 182 if(pp.pimg) {
nuclear@0 183 if(!pp.pimg->texture) {
nuclear@0 184 pp.pimg->gen_texture();
nuclear@0 185 }
nuclear@0 186 glEnable(GL_TEXTURE_2D);
nuclear@0 187 glBindTexture(GL_TEXTURE_2D, pp.pimg->texture);
nuclear@0 188 }
nuclear@0 189
nuclear@0 190 glBegin(GL_QUADS);
nuclear@0 191 Particle *p = plist;
nuclear@0 192 while(p) {
nuclear@0 193 float hsz = p->size * p->scale * 0.5;
nuclear@0 194 glColor4f(p->color.x, p->color.y, p->color.z, p->alpha);
nuclear@0 195 glTexCoord2f(0, 0); glVertex3f(p->pos.x - hsz, p->pos.y - hsz, p->pos.z);
nuclear@0 196 glTexCoord2f(1, 0); glVertex3f(p->pos.x + hsz, p->pos.y - hsz, p->pos.z);
nuclear@0 197 glTexCoord2f(1, 1); glVertex3f(p->pos.x + hsz, p->pos.y + hsz, p->pos.z);
nuclear@0 198 glTexCoord2f(0, 1); glVertex3f(p->pos.x - hsz, p->pos.y + hsz, p->pos.z);
nuclear@0 199 p = p->next;
nuclear@0 200 }
nuclear@0 201 glEnd();
nuclear@0 202
nuclear@0 203 glPopAttrib();
nuclear@0 204
nuclear@0 205 if(cur_sdr) {
nuclear@0 206 glUseProgram(cur_sdr);
nuclear@0 207 }
nuclear@0 208 }
nuclear@0 209
nuclear@0 210 void ParticleSystem::gen_spawnmap(int count)
nuclear@0 211 {
nuclear@0 212 Image *img = pp.spawn_map;
nuclear@0 213 if(!img) return;
nuclear@0 214
nuclear@0 215 delete [] smcache;
nuclear@0 216 smcache = new Vector3[count];
nuclear@0 217
nuclear@0 218 float umax = (float)img->width;
nuclear@0 219 float vmax = (float)img->height;
nuclear@0 220 float aspect = umax / vmax;
nuclear@0 221
nuclear@0 222 // first generate a bunch of random samples by rejection sampling
nuclear@0 223 printf("generating %d random spawnmap samples\n", count);
nuclear@0 224 for(int i=0; i<count; i++) {
nuclear@0 225 float u, v;
nuclear@0 226 unsigned char val, ord;
nuclear@0 227
nuclear@0 228 do {
nuclear@0 229 u = (double)rand() / (double)RAND_MAX;
nuclear@0 230 v = (double)rand() / (double)RAND_MAX;
nuclear@0 231
nuclear@0 232 int x = (int)(u * umax);
nuclear@0 233 int y = (int)(v * vmax);
nuclear@0 234
nuclear@0 235 unsigned char *pptr = img->pixels + (y * img->width + x) * 3;
nuclear@0 236 val = pptr[0];
nuclear@0 237 ord = pptr[1];
nuclear@0 238 } while(val < 192);
nuclear@0 239
nuclear@0 240 smcache[i] = Vector3(u * 2.0 - 1.0, (1.0 - v * 2.0) / aspect, ord / 255.0);
nuclear@0 241 }
nuclear@0 242
nuclear@0 243 // then order by z
nuclear@0 244 std::sort(smcache, smcache + count,
nuclear@0 245 [](const Vector3 &a, const Vector3 &b) { return a.z < b.z; });
nuclear@0 246
nuclear@0 247 // precalculate the bounds of each slot
nuclear@0 248 smcache_max[0] = 0;
nuclear@0 249 for(int i=1; i<255; i++) {
nuclear@0 250 float maxval = (float)i / 255.0;
nuclear@0 251
nuclear@0 252 int idx = smcache_max[i - 1];
nuclear@0 253 while(++idx < count && smcache[idx].z < maxval);
nuclear@0 254 smcache_max[i] = idx;
nuclear@0 255 }
nuclear@0 256 smcache_max[255] = count;
nuclear@0 257 }
nuclear@0 258
nuclear@0 259 static double frand()
nuclear@0 260 {
nuclear@0 261 return (double)rand() / (double)RAND_MAX;
nuclear@0 262 }
nuclear@0 263
nuclear@0 264 static float rndval(float x, float range)
nuclear@0 265 {
nuclear@0 266 if(fabs(range) < 1e-6) {
nuclear@0 267 return x;
nuclear@0 268 }
nuclear@0 269 return x + (frand() * range - range * 0.5);
nuclear@0 270 }
nuclear@0 271
nuclear@0 272 void ParticleSystem::spawn_particle()
nuclear@0 273 {
nuclear@0 274 Particle *p = palloc();
nuclear@0 275 p->pos = Vector3(rndval(pos.x, pp.spawn_range),
nuclear@0 276 rndval(pos.y, pp.spawn_range),
nuclear@0 277 rndval(pos.z, pp.spawn_range));
nuclear@0 278 p->vel = Vector3(0, 0, 0);
nuclear@0 279 p->color = pp.pcolor_start;
nuclear@0 280 p->alpha = pp.palpha_start;
nuclear@0 281 p->life = 0.0;
nuclear@0 282 p->max_life = rndval(pp.life, pp.life_range);
nuclear@0 283 p->size = rndval(pp.size, pp.size_range);
nuclear@0 284 p->scale = pp.pscale_start;
nuclear@0 285
nuclear@0 286 if(pp.spawn_map) {
nuclear@0 287 float maxz = pp.spawn_map_speed > 0.0 ? active_time * pp.spawn_map_speed : 1.0;
nuclear@0 288 int max_idx = (int)(maxz * 255.0);
nuclear@0 289 if(max_idx > 255) max_idx = 255;
nuclear@0 290 if(max_idx < 1) max_idx = 1;
nuclear@0 291
nuclear@0 292 int idx = rand() % smcache_max[max_idx];
nuclear@0 293
nuclear@0 294 p->pos.x += smcache[idx].x;
nuclear@0 295 p->pos.y += smcache[idx].y;
nuclear@0 296 }
nuclear@0 297
nuclear@0 298 p->next = plist;
nuclear@0 299 plist = p;
nuclear@0 300 ++pcount;
nuclear@0 301 }
nuclear@0 302
nuclear@0 303 // particle allocator
nuclear@0 304 #define MAX_POOL_SIZE 8192
nuclear@0 305 static Particle *ppool;
nuclear@0 306 static int ppool_size;
nuclear@0 307
nuclear@0 308 static Particle *palloc()
nuclear@0 309 {
nuclear@0 310 if(ppool) {
nuclear@0 311 Particle *p = ppool;
nuclear@0 312 ppool = ppool->next;
nuclear@0 313 --ppool_size;
nuclear@0 314 return p;
nuclear@0 315 }
nuclear@0 316 return new Particle;
nuclear@0 317 }
nuclear@0 318
nuclear@0 319 static void pfree(Particle *p)
nuclear@0 320 {
nuclear@0 321 if(!p) return;
nuclear@0 322
nuclear@0 323 if(ppool_size < MAX_POOL_SIZE) {
nuclear@0 324 p->next = ppool;
nuclear@0 325 ppool = p;
nuclear@0 326 ++ppool_size;
nuclear@0 327 } else {
nuclear@0 328 delete p;
nuclear@0 329 }
nuclear@0 330 }
nuclear@0 331
nuclear@0 332 static void pfreelist(Particle *p)
nuclear@0 333 {
nuclear@0 334 if(!p) return;
nuclear@0 335
nuclear@0 336 Particle *it = p;
nuclear@0 337 int new_pool_size = ppool_size;
nuclear@0 338
nuclear@0 339 while(it->next && new_pool_size < MAX_POOL_SIZE) {
nuclear@0 340 it = it->next;
nuclear@0 341 ++new_pool_size;
nuclear@0 342 }
nuclear@0 343
nuclear@0 344 Particle *last = it;
nuclear@0 345 it = it->next;
nuclear@0 346
nuclear@0 347 // add the first lot to the pool
nuclear@0 348 last->next = ppool;
nuclear@0 349 ppool = p;
nuclear@0 350 ppool_size = new_pool_size;
nuclear@0 351
nuclear@0 352 // delete the rest;
nuclear@0 353 while(it) {
nuclear@0 354 p = it;
nuclear@0 355 it = it->next;
nuclear@0 356 delete p;
nuclear@0 357 }
nuclear@0 358 }