06b042985b2383a65d5a431a656ead930977a190
[ld42_outofspace] / src / gamescr.cc
1 #include <assert.h>
2 #include <vector>
3 #include <gmath/gmath.h>
4 #include "game.h"
5 #include "screen.h"
6 #include "opengl.h"
7 #include "texture.h"
8 #include "sdr.h"
9 #include "mesh.h"
10 #include "meshgen.h"
11
12 /* NOTES:
13  * - whistle hhgg music
14  * - select objects and center camera on them
15  */
16
17 struct Particle {
18         float radius;
19         float mass;
20         Vec2 pos;
21         Vec2 vel;
22
23         float vis_height;
24
25         Particle *next, *prev;
26 };
27
28 struct Emitter {
29         Vec2 pos;
30         float mass;
31         float rate, chunk;
32         float angle, spread;
33
34         float spawn_pending;
35 };
36
37 struct Rect {
38         int x, y;
39         int width, height;
40 };
41
42 struct QuadMesh {
43         Vec3 *v;
44         Vec2 *uv;
45         uint16_t *idx;
46
47         int num_verts, num_idx, num_quads;
48
49         unsigned int vbo_v, vbo_uv, ibo;
50 };
51
52 #define SIM_DT          0.016
53
54 #define GRID_SIZE       2048
55 #define GRID_BITS       11
56 #define GRID_X(idx)     (((idx) >> GRID_BITS) & (GRID_SIZE - 1))
57 #define GRID_Y(idx)     ((idx) & (GRID_SIZE - 1))
58 #define GRID_DELTA      ((float)FIELD_SIZE / (float)GRID_SIZE)
59
60 #define FIELD_SIZE      2048
61 #define MIN_CAM_DIST    1.0f
62 #define MAX_CAM_DIST    350.0f
63
64 #define MASS_TO_RADIUS(m)       log((m) + 1.0)
65
66 #define CONTRIB_THRES   0.005
67 #define CONTRIB_RANGE(m) sqrt((m) / CONTRIB_THRES)
68
69 /* gravitational strength */
70 #define GRAV_STR        16.0f
71
72 static int pos_to_grid_x_noclamp(float x);
73 static int pos_to_grid_y_noclamp(float y);
74 static int pos_to_grid(float x, float y);
75 static Vec2 grid_to_pos(int gx, int gy);
76
77 static void calc_contrib_bounds(const Vec2 &pos, float mass, Rect *rect);
78 static void add_influence(const Vec2 &pos, float mass, float radius, const Rect &cbox);
79
80 static Vec2 calc_field_grad(int gidx);
81
82 static void destroy_quadmesh(QuadMesh *m);
83 static void draw_quadmesh(const QuadMesh *m, unsigned int prim = GL_QUADS);
84 static void gen_quad_plane(QuadMesh *mesh, float width, float height, int usub, int vsub);
85
86 static void spawn_particle(const Vec2 &pos, const Vec2 &vel, float mass);
87 static void add_particle(Particle *p);
88 static void remove_particle(Particle *p);
89 static Particle *alloc_particle();
90 void free_particle(Particle *p);
91
92 static float grid[GRID_SIZE * GRID_SIZE];
93 static Particle *grid_part[GRID_SIZE * GRID_SIZE];
94 static Texture *grid_tex;
95
96 static Particle *plist;
97
98 static std::vector<Emitter*> emitters;
99
100 static Texture *gvis_tex;       // texture tile for visualizing a grid
101 static unsigned int field_sdr;
102 static int tess_level = 64;
103 static float field_scale = 16.0f;
104
105 static QuadMesh field_mesh;
106
107 static Mesh *pmesh;
108 static unsigned int particle_sdr;
109
110 static float cam_theta;
111 static float cam_dist = 100.0f;
112 static Vec2 *targ_pos;
113 static Mat4 view_matrix, proj_matrix;
114
115 static bool wireframe;
116
117 // emitter placement data (filled by event handlers, completed in update)
118 static bool emit_place_pending;
119 static Vec2 emit_place_pos;
120
121
122 bool GameScreen::init()
123 {
124         grid_tex = new Texture;
125         grid_tex->create(GRID_SIZE, GRID_SIZE, TEX_2D, GL_LUMINANCE32F_ARB);
126         grid_tex->set_anisotropy(glcaps.max_aniso);
127
128         gvis_tex = new Texture;
129         if(!gvis_tex->load("data/purple_grid.png")) {
130                 return false;
131         }
132         gvis_tex->set_anisotropy(glcaps.max_aniso);
133
134         unsigned int vsdr, tcsdr, tesdr, psdr;
135
136         if(!(vsdr = load_vertex_shader("sdr/field.v.glsl")) ||
137                         !(tcsdr = load_tessctl_shader("sdr/field.tc.glsl")) ||
138                         !(tesdr = load_tesseval_shader("sdr/field.te.glsl")) ||
139                         !(psdr = load_pixel_shader("sdr/field.p.glsl"))) {
140                 return false;
141         }
142
143         if(!(field_sdr = create_program_link(vsdr, tcsdr, tesdr, psdr, 0))) {
144                 return false;
145         }
146         set_uniform_int(field_sdr, "gvis_tex", 0);
147         set_uniform_int(field_sdr, "field_tex", 1);
148         set_uniform_float(field_sdr, "gvis_scale", FIELD_SIZE / 32.0f);
149         set_uniform_int(field_sdr, "tess_level", tess_level);
150         set_uniform_float(field_sdr, "field_scale", field_scale);
151
152         gen_quad_plane(&field_mesh, FIELD_SIZE, FIELD_SIZE, 32, 32);
153
154         pmesh = new Mesh;
155         gen_geosphere(pmesh, 1.0, 2);
156
157         if(!(particle_sdr = create_program_load("sdr/sph.v.glsl", "sdr/sph.p.glsl"))) {
158                 return false;
159         }
160
161         // XXX DBG
162         emit_place_pos = Vec2(0, 0);
163         emit_place_pending = true;
164
165         assert(glGetError() == GL_NO_ERROR);
166         return true;
167 }
168
169 void GameScreen::destroy()
170 {
171         free_program(field_sdr);
172         delete gvis_tex;
173         delete grid_tex;
174         destroy_quadmesh(&field_mesh);
175         delete pmesh;
176 }
177
178 static void simstep()
179 {
180         // move existing particles
181         Particle *p = plist;
182         while(p) {
183                 // calculate the field gradient at the particle position
184                 int gidx = pos_to_grid(p->pos.x, p->pos.y);
185                 Vec2 grad = calc_field_grad(gidx) * GRAV_STR;
186
187                 p->vel += grad * SIM_DT;
188                 p->pos += p->vel * SIM_DT;
189
190                 // if it moved outside of the simulation field, remove it
191                 int gx = pos_to_grid_x_noclamp(p->pos.x);
192                 int gy = pos_to_grid_y_noclamp(p->pos.y);
193                 if(gx < 0 || gx >= GRID_SIZE || gy < 0 || gy >= GRID_SIZE) {
194                         Particle *next = p->next;
195                         grid_part[gidx] = 0;
196                         remove_particle(p);
197                         free_particle(p);
198                         p = next;
199                         continue;
200                 }
201
202                 // find the grid cell it's moving to
203                 int gidx_next = pos_to_grid(p->pos.x, p->pos.y);
204                 p->vis_height = 0.0f;//-grid[gidx_next] * field_scale;
205
206                 if(gidx_next == gidx) {
207                         p = p->next;
208                         continue;
209                 }
210
211                 Particle *destp = grid_part[gidx_next];
212                 if(destp) {
213                         assert(destp != p);
214                         // another particle at the destination, merge them
215                         destp->vel += p->vel;
216                         destp->mass += p->mass;
217                         destp->radius = MASS_TO_RADIUS(destp->mass);
218
219                         // ... and remove it
220                         grid_part[gidx] = 0;
221                         Particle *next = p->next;
222                         remove_particle(p);
223                         free_particle(p);
224                         p = next;
225                 } else {
226                         // destination is empty, go there
227                         if(gidx != gidx_next) {
228                                 grid_part[gidx] = 0;
229                                 grid_part[gidx_next] = p;
230                         }
231
232                         p = p->next;
233                 }
234         }
235
236         // TODO destroy particles which left the simulation field
237
238         // spawn particles
239         int num_emitters = emitters.size();
240         for(int i=0; i<num_emitters; i++) {
241                 Emitter *em = emitters[i];
242                 em->spawn_pending += em->rate * SIM_DT;
243                 while(em->spawn_pending >= 1.0f && em->mass > 0.0f) {
244                         Vec2 pvel;      // XXX calc eject velocity
245
246                         float angle = (float)rand() / (float)RAND_MAX * (M_PI * 2.0);
247                         float emradius = MASS_TO_RADIUS(em->mass);
248                         Vec2 ppos = em->pos + Vec2(cos(angle), sin(angle)) * emradius * 1.00001;
249
250                         float pmass = em->chunk > em->mass ? em->mass : em->chunk;
251                         spawn_particle(ppos, pvel, pmass);
252                         em->mass -= pmass;
253
254                         em->spawn_pending -= 1.0f;
255                 }
256         }
257
258         // remove dead emitters
259         std::vector<Emitter*>::iterator it = emitters.begin();
260         while(it != emitters.end()) {
261                 Emitter *em = *it;
262
263                 if(em->mass <= 0.0f) {
264                         printf("emitter depleted\n");
265                         it = emitters.erase(it);
266                         delete em;
267                 } else {
268                         it++;
269                 }
270         }
271
272         // calculate gravitational field - assume field within radius constant: m / r^2
273         // first clear the field, and then add contributions
274         memset(grid, 0, sizeof grid);
275
276         // contribution of emitters
277         for(int i=0; i<num_emitters; i++) {
278                 Emitter *em = emitters[i];
279                 Rect cbox;
280                 calc_contrib_bounds(em->pos, em->mass, &cbox);
281                 float emradius = MASS_TO_RADIUS(em->mass);
282
283                 add_influence(em->pos, -em->mass, emradius, cbox);
284         }
285
286         // contribution of particles
287         p = plist;
288         while(p) {
289                 Rect cbox;
290                 calc_contrib_bounds(p->pos, p->mass, &cbox);
291                 add_influence(p->pos, p->mass, p->radius, cbox);
292                 p = p->next;
293         }
294
295         // update texture
296         assert(glGetError() == GL_NO_ERROR);
297         grid_tex->bind();
298         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, GRID_SIZE, GRID_SIZE, GL_LUMINANCE,
299                         GL_FLOAT, grid);
300         assert(glGetError() == GL_NO_ERROR);
301 }
302
303 static void update()
304 {
305         if(emit_place_pending) {
306                 emit_place_pending = false;
307                 Emitter *em = new Emitter;
308                 em->pos = emit_place_pos;
309                 em->mass = 100;
310                 em->rate = 10;
311                 em->chunk = 0.001 * em->mass;
312                 em->angle = -1;
313                 em->spread = 0;
314                 em->spawn_pending = 0;
315                 emitters.push_back(em);
316
317                 Rect cbox;
318                 calc_contrib_bounds(em->pos, em->mass, &cbox);
319                 printf("bounds: %d,%d %dx%d\n", cbox.x, cbox.y, cbox.width, cbox.height);
320         }
321
322         // update simulation
323         static float interval;
324         interval += frame_dt;
325         if(interval >= SIM_DT) {
326                 interval -= SIM_DT;
327                 simstep();
328                 assert(glGetError() == GL_NO_ERROR);
329         }
330
331         // update projection matrix
332         proj_matrix.perspective(deg_to_rad(60.0f), win_aspect, 0.5, 5000.0);
333
334         // update view matrix
335         Vec3 targ;
336         if(targ_pos) {
337                 targ.x = targ_pos->x;
338                 targ.z = targ_pos->y;
339         }
340
341         float theta = -deg_to_rad(cam_theta);
342         Vec3 camdir = Vec3(sin(theta) * cam_dist, pow(cam_dist * 0.1, 2.0) + 0.5, cos(theta) * cam_dist);
343         Vec3 campos = targ + camdir;
344
345         view_matrix.inv_lookat(campos, targ, Vec3(0, 1, 0));
346 }
347
348 void GameScreen::draw()
349 {
350         update();
351
352         glClearColor(0.01, 0.01, 0.01, 1);
353         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
354
355         glMatrixMode(GL_PROJECTION);
356         glLoadMatrixf(proj_matrix[0]);
357         glMatrixMode(GL_MODELVIEW);
358         glLoadMatrixf(view_matrix[0]);
359
360         // draw gravitational field
361         if(wireframe) {
362                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
363
364                 float amb[] = {0.5, 0.5, 0.5, 1.0};
365                 glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb);
366         } else {
367                 float amb[] = {0.01, 0.01, 0.01, 1.0};
368                 glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb);
369         }
370
371         bind_texture(gvis_tex, 0);
372         bind_texture(grid_tex, 1);
373
374         glUseProgram(field_sdr);
375         glPatchParameteri(GL_PATCH_VERTICES, 4);
376         draw_quadmesh(&field_mesh, GL_PATCHES);
377
378         if(wireframe) {
379                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
380         }
381
382         // draw particles
383         glUseProgram(particle_sdr);
384
385         Particle *p = plist;
386         while(p) {
387                 int gidx = pos_to_grid(p->pos.x, p->pos.y);
388
389                 glPushMatrix();
390                 glTranslatef(p->pos.x, p->vis_height, p->pos.y);
391                 glScalef(p->radius, p->radius, p->radius);
392
393                 pmesh->draw();
394
395                 glPopMatrix();
396                 p = p->next;
397         }
398
399         glUseProgram(0);
400
401         assert(glGetError() == GL_NO_ERROR);
402 }
403
404 void GameScreen::reshape(int x, int y)
405 {
406 }
407
408
409
410 void GameScreen::keyboard(int key, bool pressed)
411 {
412         if(pressed) {
413                 switch(key) {
414                 case KEY_ESC:
415                         pop_screen();
416                         break;
417
418                 case ']':
419                         if(tess_level < glcaps.max_tess_level) {
420                                 tess_level++;
421                                 printf("tessellation level: %d\n", tess_level);
422                                 set_uniform_int(field_sdr, "tess_level", tess_level);
423                                 glUseProgram(0);
424                         }
425                         break;
426
427                 case '[':
428                         if(tess_level > 1) {
429                                 tess_level--;
430                                 printf("tessellation level: %d\n", tess_level);
431                                 set_uniform_int(field_sdr, "tess_level", tess_level);
432                                 glUseProgram(0);
433                         }
434                         break;
435
436                 case '=':
437                         field_scale += 0.5;
438                         printf("field scale: %f\n", field_scale);
439                         set_uniform_float(field_sdr, "field_scale", field_scale);
440                         break;
441
442                 case '-':
443                         field_scale -= 0.5;
444                         if(field_scale < 0.0f) {
445                                 field_scale = 0.0f;
446                         }
447                         printf("field scale: %f\n", field_scale);
448                         set_uniform_float(field_sdr, "field_scale", field_scale);
449                         break;
450
451                 case 'w':
452                         wireframe = !wireframe;
453                         break;
454
455                 default:
456                         break;
457                 }
458         }
459 }
460
461 static int prev_x, prev_y;
462
463 void GameScreen::mbutton(int bn, bool pressed, int x, int y)
464 {
465         prev_x = x;
466         prev_y = y;
467 }
468
469 void GameScreen::mmotion(int x, int y)
470 {
471         int dx = x - prev_x;
472         int dy = y - prev_y;
473         prev_x = x;
474         prev_y = y;
475
476         if(game_bnstate(2)) {
477                 cam_theta += dx * 0.5;
478         }
479 }
480
481 void GameScreen::mwheel(int dir, int x, int y)
482 {
483         cam_dist -= dir * cam_dist * 0.05f;
484         if(cam_dist <= MIN_CAM_DIST) cam_dist = MIN_CAM_DIST;
485         if(cam_dist > MAX_CAM_DIST) cam_dist = MAX_CAM_DIST;
486 }
487
488 static int pos_to_grid_x_noclamp(float x)
489 {
490         return ((x / (float)FIELD_SIZE) + 0.5f) * (float)GRID_SIZE;
491 }
492
493 static int pos_to_grid_y_noclamp(float y)
494 {
495         return ((y / (float)FIELD_SIZE) + 0.5f) * (float)GRID_SIZE;
496 }
497
498 static int pos_to_grid(float x, float y)
499 {
500         int gx = pos_to_grid_x_noclamp(x);
501         int gy = pos_to_grid_y_noclamp(y);
502
503         if(gx < 0) gx = 0;
504         if(gx >= GRID_SIZE) gx = GRID_SIZE - 1;
505         if(gy < 0) gy = 0;
506         if(gy >= GRID_SIZE) gy = GRID_SIZE - 1;
507
508         return (gx << GRID_BITS) | gy;
509 }
510
511 static Vec2 grid_to_pos(int gx, int gy)
512 {
513         float x = (((float)gx / (float)GRID_SIZE) - 0.5f) * (float)FIELD_SIZE;
514         float y = (((float)gy / (float)GRID_SIZE) - 0.5f) * (float)FIELD_SIZE;
515
516         return Vec2(x, y);
517 }
518
519 static void calc_contrib_bounds(const Vec2 &pos, float mass, Rect *rect)
520 {
521         int gidx = pos_to_grid(pos.x, pos.y);
522         int gx = GRID_X(gidx);
523         int gy = GRID_Y(gidx);
524         int maxrange = (int)ceil(CONTRIB_RANGE(mass));
525
526         int sx = gx - maxrange;
527         int sy = gy - maxrange;
528         int ex = gx + maxrange;
529         int ey = gy + maxrange;
530
531         if(ex > GRID_SIZE) ex = GRID_SIZE;
532         if(ey > GRID_SIZE) ey = GRID_SIZE;
533
534         rect->x = sx < 0 ? 0 : sx;
535         rect->y = sy < 0 ? 0 : sy;
536         rect->width = ex - sx;
537         rect->height = ey - sy;
538 }
539
540 static void add_influence(const Vec2 &pos, float mass, float radius, const Rect &cbox)
541 {
542         float *gptr = grid + cbox.y * GRID_SIZE + cbox.x;
543         Vec2 startpos = grid_to_pos(cbox.x, cbox.y);
544
545         for(int y=0; y<cbox.height; y++) {
546                 for(int x=0; x<cbox.width; x++) {
547                         Vec2 cellpos = Vec2(startpos.x + (float)x * GRID_DELTA, startpos.y);
548
549                         Vec2 dir = cellpos - pos;
550                         float dsq = dot(dir, dir);
551                         float radsq = radius * radius;
552                         if(dsq < radsq) {
553                                 dsq = radsq;
554                         }
555
556                         gptr[x] += mass / dsq;
557                 }
558
559                 startpos.y += GRID_DELTA;
560                 gptr += GRID_SIZE;
561         }
562 }
563
564 static Vec2 calc_field_grad(int gidx)
565 {
566         int gx = GRID_X(gidx);
567         int gy = GRID_Y(gidx);
568
569         int nidx = ((gx + 1 >= GRID_SIZE ? gx : gx + 1) << GRID_BITS) | gy;
570         int pidx = ((gx > 0 ? gx - 1 : gx) << GRID_BITS) | gy;
571         float dfdx = grid[nidx] - grid[pidx];
572
573         nidx = (gx << GRID_BITS) | (gy + 1 >= GRID_SIZE ? gy : gy + 1);
574         pidx = (gx << GRID_BITS) | (gy > 0 ? gy - 1 : gy);
575         float dfdy = grid[nidx] - grid[pidx];
576
577         return Vec2(dfdx, dfdy);
578 }
579
580
581 // ---- quad mesh operations ----
582
583 static void destroy_quadmesh(QuadMesh *m)
584 {
585         glDeleteBuffers(1, &m->vbo_v);
586         glDeleteBuffers(1, &m->vbo_uv);
587         glDeleteBuffers(1, &m->ibo);
588
589         delete [] m->v;
590         delete [] m->uv;
591         delete [] m->idx;
592 }
593
594 static void draw_quadmesh(const QuadMesh *m, unsigned int prim)
595 {
596         glEnableClientState(GL_VERTEX_ARRAY);
597         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
598
599         glBindBuffer(GL_ARRAY_BUFFER, m->vbo_v);
600         glVertexPointer(3, GL_FLOAT, 0, 0);
601
602         glBindBuffer(GL_ARRAY_BUFFER, m->vbo_uv);
603         glTexCoordPointer(2, GL_FLOAT, 0, 0);
604
605         glBindBuffer(GL_ARRAY_BUFFER, 0);
606
607         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ibo);
608         glDrawElements(prim, m->num_idx, GL_UNSIGNED_SHORT, 0);
609         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
610
611         glDisableClientState(GL_VERTEX_ARRAY);
612         glDisableClientState(GL_TEXTURE_COORD_ARRAY);
613 }
614
615 static void gen_quad_plane(QuadMesh *m, float width, float height, int usub, int vsub)
616 {
617         Vec3 *vptr;
618         Vec2 *uvptr;
619         uint16_t *iptr;
620
621         if(usub < 1) usub = 1;
622         if(vsub < 1) vsub = 1;
623
624         int uverts = usub + 1;
625         int vverts = vsub + 1;
626         m->num_verts = uverts * vverts;
627         m->num_quads = usub * vsub;
628         m->num_idx = m->num_quads * 4;
629
630         vptr = m->v = new Vec3[m->num_verts];
631         uvptr = m->uv = new Vec2[m->num_verts];
632         iptr = m->idx = new uint16_t[m->num_idx];
633
634         float du = 1.0f / (float)usub;
635         float dv = 1.0f / (float)vsub;
636
637         float u = 0.0f;
638         for(int i=0; i<uverts; i++) {
639                 float x = (u - 0.5f) * width;
640                 float v = 0.0f;
641                 for(int j=0; j<vverts; j++) {
642                         float y = (v - 0.5f) * height;
643
644                         *vptr++ = Vec3(x, 0, y);
645                         *uvptr++ = Vec2(u, v);
646
647                         if(i < usub && j < vsub) {
648                                 int idx = i * vverts + j;
649
650                                 *iptr++ = idx;
651                                 *iptr++ = idx + 1;
652                                 *iptr++ = idx + vverts + 1;
653                                 *iptr++ = idx + vverts;
654                         }
655
656                         v += dv;
657                 }
658                 u += du;
659         }
660
661         glGenBuffers(1, &m->vbo_v);
662         glBindBuffer(GL_ARRAY_BUFFER, m->vbo_v);
663         glBufferData(GL_ARRAY_BUFFER, m->num_verts * 3 * sizeof(float), m->v, GL_STATIC_DRAW);
664
665         glGenBuffers(1, &m->vbo_uv);
666         glBindBuffer(GL_ARRAY_BUFFER, m->vbo_uv);
667         glBufferData(GL_ARRAY_BUFFER, m->num_verts * 2 * sizeof(float), m->uv, GL_STATIC_DRAW);
668
669         glGenBuffers(1, &m->ibo);
670         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ibo);
671         glBufferData(GL_ELEMENT_ARRAY_BUFFER, m->num_idx * sizeof(uint16_t), m->idx, GL_STATIC_DRAW);
672
673         glBindBuffer(GL_ARRAY_BUFFER, 0);
674         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
675 }
676
677 static void spawn_particle(const Vec2 &pos, const Vec2 &vel, float mass)
678 {
679         int gidx = pos_to_grid(pos.x, pos.y);
680
681         if(grid_part[gidx]) {
682                 // merge with existing
683                 Particle *p = grid_part[gidx];
684                 p->vel += vel;
685                 p->mass += mass;
686                 p->radius = MASS_TO_RADIUS(p->mass);
687
688         } else {
689                 Particle *p = alloc_particle();
690                 p->pos = pos;
691                 p->vel = vel;
692                 p->mass = mass;
693                 p->radius = MASS_TO_RADIUS(mass);
694                 grid_part[gidx] = p;
695
696                 add_particle(p);
697         }
698 }
699
700 static void add_particle(Particle *p)
701 {
702         if(plist) plist->prev = p;
703
704         p->next = plist;
705         p->prev = 0;
706         plist = p;
707 }
708
709 static void remove_particle(Particle *p)
710 {
711         assert(plist->prev == 0);
712
713         if(plist == p) {
714                 assert(p->prev == 0);
715                 plist = p->next;
716         }
717         if(p->prev) {
718                 p->prev->next = p->next;
719         }
720         if(p->next) {
721                 p->next->prev = p->prev;
722         }
723         p->prev = p->next = 0;
724 }
725
726 // particle allocator
727 #define MAX_PFREE_SIZE  256
728 static Particle *pfree_list;
729 static int pfree_size;
730
731 static Particle *alloc_particle()
732 {
733         if(pfree_list) {
734                 Particle *p = pfree_list;
735                 pfree_list = pfree_list->next;
736                 pfree_size--;
737                 return p;
738         }
739
740         return new Particle;
741 }
742
743 void free_particle(Particle *p)
744 {
745         if(pfree_size < MAX_PFREE_SIZE) {
746                 p->next = pfree_list;
747                 p->prev = 0;
748                 pfree_list = p;
749                 pfree_size++;
750         } else {
751                 delete p;
752         }
753 }