I don't think I'll bother any more with this, so I'll just make it start
[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 #include "goatkit/goatkit.h"
12
13 /* NOTES:
14  * - whistle hhgg music
15  * - select objects and center camera on them
16  * - we need to rely on actual collisions instead of grid occupancy
17  * - on collision use the weighted sum of the velocities
18  *
19  * XXX revert the direct n^2 gravitational thing, and find a way to exclude
20  *     own field when sampling the ... field
21  */
22
23 struct Particle {
24         float radius;
25         float mass;
26         Vec2 pos;
27         Vec2 vel;
28
29         float vis_height;
30
31         Particle *next, *prev;
32 };
33
34 struct Emitter {
35         Vec2 pos;
36         float mass;
37         float rate, chunk;
38         float angle, spread;
39
40         float spawn_pending;
41 };
42
43 struct Rect {
44         int x, y;
45         int width, height;
46 };
47
48 struct QuadMesh {
49         Vec3 *v;
50         Vec2 *uv;
51         uint16_t *idx;
52
53         int num_verts, num_idx, num_quads;
54
55         unsigned int vbo_v, vbo_uv, ibo;
56 };
57
58 #define SIM_DT          0.016
59
60 #define GRID_SIZE       2048
61 #define GRID_BITS       11
62 #define GRID_X(idx)     (((idx) >> GRID_BITS) & (GRID_SIZE - 1))
63 #define GRID_Y(idx)     ((idx) & (GRID_SIZE - 1))
64 #define GRID_DELTA      ((float)FIELD_SIZE / (float)GRID_SIZE)
65
66 #define FIELD_SIZE      2048
67 #define MIN_CAM_DIST    1.0f
68 #define MAX_CAM_DIST    350.0f
69
70 #define MASS_TO_RADIUS(m)       log((m) + 1.0)
71
72 #define CONTRIB_THRES   0.005
73 #define CONTRIB_RANGE(m) sqrt((m) / CONTRIB_THRES)
74
75 /* gravitational strength */
76 #define GRAV_STR        16.0f
77
78 #define INIT_MASS_BUDGET        600.0f
79 #define EM_MASS_DEFAULT         100.0f
80
81 static int pos_to_grid_x_noclamp(float x);
82 static int pos_to_grid_y_noclamp(float y);
83 static int pos_to_grid(float x, float y);
84 static Vec2 grid_to_pos(int gx, int gy);
85
86 static void calc_contrib_bounds(const Vec2 &pos, float mass, Rect *rect);
87 static void add_influence(const Vec2 &pos, float mass, float radius, const Rect &cbox);
88
89 static Vec2 calc_field_grad(int gidx);
90
91 static void destroy_quadmesh(QuadMesh *m);
92 static void draw_quadmesh(const QuadMesh *m, unsigned int prim = GL_QUADS);
93 static void gen_quad_plane(QuadMesh *mesh, float width, float height, int usub, int vsub);
94
95 static void spawn_particle(const Vec2 &pos, const Vec2 &vel, float mass);
96 static void add_particle(Particle *p);
97 static void remove_particle(Particle *p);
98 static Particle *alloc_particle();
99 void free_particle(Particle *p);
100
101 static bool pause;
102
103 static float grid[GRID_SIZE * GRID_SIZE];
104 static Particle *grid_part[GRID_SIZE * GRID_SIZE];
105 static Texture *grid_tex;
106
107 static Particle *plist;
108
109 static std::vector<Emitter*> emitters;
110
111 static Texture *gvis_tex;       // texture tile for visualizing a grid
112 static unsigned int field_sdr;
113 static int tess_level = 64;
114 static float field_scale = 16.0f;
115
116 static QuadMesh field_mesh;
117
118 static Mesh *pmesh;
119 static unsigned int particle_sdr;
120
121 static Vec2 cam_pos;
122 static float cam_theta;
123 static float cam_dist = 100.0f;
124 static Vec2 *targ_pos = &cam_pos;
125 static Mat4 view_matrix, proj_matrix;
126
127 static bool wireframe;
128 static int mouse_x, mouse_y;
129
130 // emitter placement data (filled by event handlers, completed in update)
131 static bool emit_place_pending;
132 static Vec2 emit_place_pos;
133
134 static bool placing_emitter;
135
136 static float mass_left;
137
138 // UI
139 static void ui_handler(goatkit::Widget *w, const goatkit::Event &ev, void *cls);
140 static int ui_virt_width = 800;
141 static int ui_virt_height = 600;
142 static goatkit::Screen *ui;
143 static goatkit::Button *bn_emitter;
144 static goatkit::Slider *slider_mass;
145 static bool mouse_over_ui;
146
147
148 bool GameScreen::init()
149 {
150         grid_tex = new Texture;
151         grid_tex->create(GRID_SIZE, GRID_SIZE, TEX_2D, GL_LUMINANCE32F_ARB);
152         grid_tex->set_anisotropy(glcaps.max_aniso);
153
154         gvis_tex = new Texture;
155         if(!gvis_tex->load("data/purple_grid.png")) {
156                 return false;
157         }
158         gvis_tex->set_anisotropy(glcaps.max_aniso);
159
160         unsigned int vsdr, tcsdr, tesdr, psdr;
161
162         if(!(vsdr = load_vertex_shader("sdr/field.v.glsl")) ||
163                         !(tcsdr = load_tessctl_shader("sdr/field.tc.glsl")) ||
164                         !(tesdr = load_tesseval_shader("sdr/field.te.glsl")) ||
165                         !(psdr = load_pixel_shader("sdr/field.p.glsl"))) {
166                 return false;
167         }
168
169         if(!(field_sdr = create_program_link(vsdr, tcsdr, tesdr, psdr, 0))) {
170                 return false;
171         }
172         set_uniform_int(field_sdr, "gvis_tex", 0);
173         set_uniform_int(field_sdr, "field_tex", 1);
174         set_uniform_float(field_sdr, "gvis_scale", FIELD_SIZE / 32.0f);
175         set_uniform_int(field_sdr, "tess_level", tess_level);
176         set_uniform_float(field_sdr, "field_scale", field_scale);
177
178         gen_quad_plane(&field_mesh, FIELD_SIZE, FIELD_SIZE, 32, 32);
179
180         pmesh = new Mesh;
181         gen_geosphere(pmesh, 1.0, 2);
182
183         if(!(particle_sdr = create_program_load("sdr/sph.v.glsl", "sdr/sph.p.glsl"))) {
184                 return false;
185         }
186
187         mass_left = INIT_MASS_BUDGET;
188
189         ui = new goatkit::Screen;
190         ui->hide();
191
192         bn_emitter = new goatkit::Button;
193         bn_emitter->set_position(5, 5);
194         bn_emitter->set_size(250, 30);
195         bn_emitter->set_text("new white hole");
196         bn_emitter->set_callback(goatkit::EV_CLICK, ui_handler);
197         ui->add_widget(bn_emitter);
198
199         slider_mass = new goatkit::Slider;
200         slider_mass->set_position(300, 5);
201         slider_mass->set_size(400, 30);
202         slider_mass->set_continuous_change(false);
203         slider_mass->set_range(0, mass_left);
204         slider_mass->set_value(EM_MASS_DEFAULT);
205         slider_mass->set_callback(goatkit::EV_CHANGE, ui_handler);
206         ui->add_widget(slider_mass);
207
208         ui->set_visibility_transition(300);
209
210         assert(glGetError() == GL_NO_ERROR);
211         return true;
212 }
213
214 void GameScreen::destroy()
215 {
216         free_program(field_sdr);
217         delete gvis_tex;
218         delete grid_tex;
219         destroy_quadmesh(&field_mesh);
220         delete pmesh;
221
222         delete bn_emitter;
223 }
224
225 void GameScreen::start()
226 {
227         ui->show();
228         slider_mass->hide();
229 }
230
231 void GameScreen::stop()
232 {
233 }
234
235 static void simstep()
236 {
237         if(pause) return;
238
239         // distribute forces
240         Particle *p = plist;
241         while(p) {
242                 int num_emitters = emitters.size();
243                 for(int i=0; i<num_emitters; i++) {
244                         Emitter *em = emitters[i];
245                         Vec2 dir = p->pos - em->pos;
246                         p->vel += dir * em->mass * GRAV_STR * 0.01 / dot(dir, dir) * SIM_DT;
247                 }
248
249                 Particle *q = plist;
250                 while(q) {
251                         if(p != q) {
252                                 Vec2 dir = q->pos - p->pos;
253                                 float accel = GRAV_STR * q->mass / dot(dir, dir);
254                                 p->vel += dir * accel * SIM_DT;
255                         }
256                         q = q->next;
257                 }
258                 p = p->next;
259         }
260
261         // move existing particles
262         p = plist;
263         while(p) {
264                 // calculate the field gradient at the particle position
265                 int gidx = pos_to_grid(p->pos.x, p->pos.y);
266                 //Vec2 grad = calc_field_grad(gidx) * GRAV_STR;
267
268                 //p->vel += grad * SIM_DT;
269                 p->pos += p->vel * SIM_DT;
270
271                 // if it moved outside of the simulation field, remove it
272                 int gx = pos_to_grid_x_noclamp(p->pos.x);
273                 int gy = pos_to_grid_y_noclamp(p->pos.y);
274                 if(gx < 0 || gx >= GRID_SIZE || gy < 0 || gy >= GRID_SIZE) {
275                         Particle *next = p->next;
276                         grid_part[gidx] = 0;
277                         remove_particle(p);
278                         free_particle(p);
279                         p = next;
280                         continue;
281                 }
282
283                 // find the grid cell it's moving to
284                 int gidx_next = pos_to_grid(p->pos.x, p->pos.y);
285                 p->vis_height = 0.0f;//-grid[gidx_next] * field_scale;
286
287                 if(gidx_next == gidx) {
288                         p = p->next;
289                         continue;
290                 }
291
292                 Particle *destp = grid_part[gidx_next];
293                 if(destp) {
294                         assert(destp != p);
295                         // another particle at the destination, merge them
296                         destp->vel += p->vel;
297                         destp->mass += p->mass;
298                         destp->radius = MASS_TO_RADIUS(destp->mass);
299
300                         // ... and remove it
301                         grid_part[gidx] = 0;
302                         Particle *next = p->next;
303                         remove_particle(p);
304                         free_particle(p);
305                         p = next;
306                 } else {
307                         // destination is empty, go there
308                         if(gidx != gidx_next) {
309                                 grid_part[gidx] = 0;
310                                 grid_part[gidx_next] = p;
311                         }
312
313                         p = p->next;
314                 }
315         }
316
317         // TODO destroy particles which left the simulation field
318
319         // spawn particles
320         int num_emitters = emitters.size();
321         for(int i=0; i<num_emitters; i++) {
322                 Emitter *em = emitters[i];
323                 em->spawn_pending += em->rate * SIM_DT;
324                 while(em->spawn_pending >= 1.0f && em->mass > 0.0f) {
325                         Vec2 pvel;      // XXX calc eject velocity
326
327                         float angle = (float)rand() / (float)RAND_MAX * (M_PI * 2.0);
328                         float emradius = MASS_TO_RADIUS(em->mass);
329                         Vec2 ppos = em->pos + Vec2(cos(angle), sin(angle)) * emradius * 1.00001;
330
331                         float pmass = em->chunk > em->mass ? em->mass : em->chunk;
332                         spawn_particle(ppos, pvel, pmass);
333                         em->mass -= pmass;
334
335                         em->spawn_pending -= 1.0f;
336                 }
337         }
338
339         // remove dead emitters
340         std::vector<Emitter*>::iterator it = emitters.begin();
341         while(it != emitters.end()) {
342                 Emitter *em = *it;
343
344                 if(em->mass <= 0.0f) {
345                         printf("emitter depleted\n");
346                         it = emitters.erase(it);
347                         delete em;
348                 } else {
349                         it++;
350                 }
351         }
352
353         // calculate gravitational field - assume field within radius constant: m / r^2
354         // first clear the field, and then add contributions
355         memset(grid, 0, sizeof grid);
356
357         // contribution of emitters
358         for(int i=0; i<num_emitters; i++) {
359                 Emitter *em = emitters[i];
360                 Rect cbox;
361                 calc_contrib_bounds(em->pos, em->mass, &cbox);
362                 float emradius = MASS_TO_RADIUS(em->mass);
363
364                 add_influence(em->pos, -em->mass, emradius, cbox);
365         }
366
367         // contribution of particles
368         p = plist;
369         while(p) {
370                 Rect cbox;
371                 calc_contrib_bounds(p->pos, p->mass, &cbox);
372                 add_influence(p->pos, p->mass, p->radius, cbox);
373                 p = p->next;
374         }
375
376         // update texture
377         assert(glGetError() == GL_NO_ERROR);
378         grid_tex->bind();
379         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, GRID_SIZE, GRID_SIZE, GL_LUMINANCE,
380                         GL_FLOAT, grid);
381         assert(glGetError() == GL_NO_ERROR);
382 }
383
384 static void update()
385 {
386         if(placing_emitter) {
387                 float x = (float)mouse_x / (float)win_width;
388                 float y = 1.0f - (float)mouse_y / (float)win_height;
389                 Ray pick_ray = mouse_pick_ray(x, y, view_matrix, proj_matrix);
390
391                 float ndotdir = pick_ray.dir.y;
392                 if(fabs(ndotdir) > 1e-6f) {
393                         float ndotpdir = -pick_ray.origin.y;
394                         float t = ndotpdir / ndotdir;
395
396                         x = pick_ray.origin.x + pick_ray.dir.x * t;
397                         y = pick_ray.origin.z + pick_ray.dir.z * t;
398
399                         if(x >= -FIELD_SIZE / 2 && x < FIELD_SIZE / 2 &&
400                                         y >= -FIELD_SIZE / 2 && y < FIELD_SIZE / 2) {
401                                 emit_place_pos = Vec2(x, y);
402                         }
403                 }
404         }
405
406         if(emit_place_pending) {
407                 emit_place_pending = false;
408                 Emitter *em = new Emitter;
409                 em->pos = emit_place_pos;
410                 em->mass = slider_mass->get_value();
411                 em->rate = 10;
412                 em->chunk = 0.001 * em->mass;
413                 em->angle = -1;
414                 em->spread = 0;
415                 em->spawn_pending = 0;
416                 emitters.push_back(em);
417
418                 mass_left -= em->mass;
419                 if(mass_left < 0.0f) mass_left = 0.0f;
420
421                 Rect cbox;
422                 calc_contrib_bounds(em->pos, em->mass, &cbox);
423                 printf("bounds: %d,%d %dx%d\n", cbox.x, cbox.y, cbox.width, cbox.height);
424         }
425
426         // update simulation
427         static float interval;
428         interval += frame_dt;
429         if(interval >= SIM_DT) {
430                 interval -= SIM_DT;
431                 simstep();
432                 assert(glGetError() == GL_NO_ERROR);
433         }
434
435         // update projection matrix
436         proj_matrix.perspective(deg_to_rad(60.0f), win_aspect, 0.5, 5000.0);
437
438         // update view matrix
439         Vec3 targ;
440         if(targ_pos) {
441                 targ.x = targ_pos->x;
442                 targ.z = targ_pos->y;
443         }
444
445         float theta = -deg_to_rad(cam_theta);
446         Vec3 camdir = Vec3(sin(theta) * cam_dist, pow(cam_dist * 0.1, 2.0) + 0.5, cos(theta) * cam_dist);
447         Vec3 campos = targ + camdir;
448
449         view_matrix.inv_lookat(campos, targ, Vec3(0, 1, 0));
450 }
451
452 void GameScreen::draw()
453 {
454         update();
455
456         glClearColor(0.01, 0.01, 0.01, 1);
457         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
458
459         glMatrixMode(GL_PROJECTION);
460         glLoadMatrixf(proj_matrix[0]);
461         glMatrixMode(GL_MODELVIEW);
462         glLoadMatrixf(view_matrix[0]);
463
464         // draw gravitational field
465         if(wireframe) {
466                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
467
468                 float amb[] = {0.5, 0.5, 0.5, 1.0};
469                 glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb);
470         } else {
471                 float amb[] = {0.01, 0.01, 0.01, 1.0};
472                 glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb);
473         }
474
475         bind_texture(gvis_tex, 0);
476         bind_texture(grid_tex, 1);
477
478         glUseProgram(field_sdr);
479         glPatchParameteri(GL_PATCH_VERTICES, 4);
480         draw_quadmesh(&field_mesh, GL_PATCHES);
481
482         if(wireframe) {
483                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
484         }
485
486         // draw particles
487         glUseProgram(particle_sdr);
488
489         Particle *p = plist;
490         while(p) {
491                 int gidx = pos_to_grid(p->pos.x, p->pos.y);
492
493                 glPushMatrix();
494                 glTranslatef(p->pos.x, p->vis_height, p->pos.y);
495                 glScalef(p->radius, p->radius, p->radius);
496
497                 pmesh->draw();
498
499                 glPopMatrix();
500                 p = p->next;
501         }
502
503         glUseProgram(0);
504
505         // draw emitter placement marker if we are in placement mode
506         if(placing_emitter && !mouse_over_ui) {
507                 glPushMatrix();
508                 glTranslatef(emit_place_pos.x, 0, emit_place_pos.y);
509
510                 glBegin(GL_LINES);
511                 glColor3f(0, 1, 0);
512                 glVertex3f(0, -1000, 0);
513                 glVertex3f(0, 1000, 0);
514                 glEnd();
515
516                 float s = MASS_TO_RADIUS(slider_mass->get_value());
517                 if(s < 0.1) s = 0.1;
518                 glScalef(s, s, s);
519
520                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
521                 pmesh->draw();
522                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
523
524                 glPopMatrix();
525         }
526
527         // draw the UI
528         glMatrixMode(GL_PROJECTION);
529         glLoadIdentity();
530         glOrtho(0, ui_virt_width, ui_virt_height, 0, -1, 1);
531
532         glMatrixMode(GL_MODELVIEW);
533         glLoadIdentity();
534
535         ui->draw();
536
537
538         assert(glGetError() == GL_NO_ERROR);
539 }
540
541 void GameScreen::reshape(int x, int y)
542 {
543         ui_virt_width = x;
544         ui_virt_height = y;
545         ui->set_size(ui_virt_width, ui_virt_height);
546 }
547
548
549
550 void GameScreen::keyboard(int key, bool pressed)
551 {
552         if(pressed) {
553                 switch(key) {
554                 case KEY_ESC:
555                         pop_screen();
556                         break;
557
558                 case ']':
559                         if(tess_level < glcaps.max_tess_level) {
560                                 tess_level++;
561                                 printf("tessellation level: %d\n", tess_level);
562                                 set_uniform_int(field_sdr, "tess_level", tess_level);
563                                 glUseProgram(0);
564                         }
565                         break;
566
567                 case '[':
568                         if(tess_level > 1) {
569                                 tess_level--;
570                                 printf("tessellation level: %d\n", tess_level);
571                                 set_uniform_int(field_sdr, "tess_level", tess_level);
572                                 glUseProgram(0);
573                         }
574                         break;
575
576                 case '=':
577                         field_scale += 0.5;
578                         printf("field scale: %f\n", field_scale);
579                         set_uniform_float(field_sdr, "field_scale", field_scale);
580                         break;
581
582                 case '-':
583                         field_scale -= 0.5;
584                         if(field_scale < 0.0f) {
585                                 field_scale = 0.0f;
586                         }
587                         printf("field scale: %f\n", field_scale);
588                         set_uniform_float(field_sdr, "field_scale", field_scale);
589                         break;
590
591                 case 'w':
592                         wireframe = !wireframe;
593                         break;
594
595                 case ' ':
596                         pause = !pause;
597                         break;
598
599                 default:
600                         break;
601                 }
602         }
603 }
604
605 #define UI_HEIGHT       50
606
607 void GameScreen::mbutton(int bn, bool pressed, int x, int y)
608 {
609         mouse_x = x;
610         mouse_y = y;
611
612         mouse_over_ui = y < UI_HEIGHT;
613
614         ui->sysev_mouse_button(bn, pressed, x * ui_virt_width / win_width,
615                         y * ui_virt_height / win_height);
616
617         if(placing_emitter && bn == 0 && pressed && !mouse_over_ui) {
618                 emit_place_pending = true;
619                 placing_emitter = false;
620                 slider_mass->hide();
621         }
622 }
623
624 void GameScreen::mmotion(int x, int y)
625 {
626         int dx = x - mouse_x;
627         int dy = y - mouse_y;
628         mouse_x = x;
629         mouse_y = y;
630
631         mouse_over_ui = y < UI_HEIGHT;
632
633         ui->sysev_mouse_motion(x * ui_virt_width / win_width, y * ui_virt_height / win_height);
634         if(ui->get_mouse_grab()) {
635                 return;
636         }
637
638         if(game_bnstate(0)) {
639                 float pan_speed = pow(cam_dist, 1.5) * 0.00035; // magic
640                 Vec2 dir = rotate(Vec2(dx, dy) * pan_speed, deg_to_rad(cam_theta));
641                 cam_pos.x -= dir.x;
642                 cam_pos.y -= dir.y;
643         }
644
645         if(game_bnstate(2)) {
646                 cam_theta += dx * 0.5;
647         }
648 }
649
650 void GameScreen::mwheel(int dir, int x, int y)
651 {
652         cam_dist -= dir * cam_dist * 0.05f;
653         if(cam_dist <= MIN_CAM_DIST) cam_dist = MIN_CAM_DIST;
654         if(cam_dist > MAX_CAM_DIST) cam_dist = MAX_CAM_DIST;
655 }
656
657 static void ui_handler(goatkit::Widget *w, const goatkit::Event &ev, void *cls)
658 {
659         if(w == bn_emitter) {
660                 if(placing_emitter) {
661                         placing_emitter = false;
662                         slider_mass->hide();
663                 } else {
664                         if(mass_left > 0.0f) {
665                                 placing_emitter = true;
666                                 slider_mass->set_range(0, mass_left);
667                                 slider_mass->set_value(mass_left >= EM_MASS_DEFAULT ? EM_MASS_DEFAULT : mass_left);
668                                 slider_mass->show();
669                         }
670                 }
671                 return;
672         }
673
674         if(w == slider_mass) {
675                 printf("foo: %f\n", slider_mass->get_value());
676         }
677 }
678
679 static int pos_to_grid_x_noclamp(float x)
680 {
681         return ((x / (float)FIELD_SIZE) + 0.5f) * (float)GRID_SIZE;
682 }
683
684 static int pos_to_grid_y_noclamp(float y)
685 {
686         return ((y / (float)FIELD_SIZE) + 0.5f) * (float)GRID_SIZE;
687 }
688
689 static int pos_to_grid(float x, float y)
690 {
691         int gx = pos_to_grid_x_noclamp(x);
692         int gy = pos_to_grid_y_noclamp(y);
693
694         if(gx < 0) gx = 0;
695         if(gx >= GRID_SIZE) gx = GRID_SIZE - 1;
696         if(gy < 0) gy = 0;
697         if(gy >= GRID_SIZE) gy = GRID_SIZE - 1;
698
699         return (gx << GRID_BITS) | gy;
700 }
701
702 static Vec2 grid_to_pos(int gx, int gy)
703 {
704         float x = (((float)gx / (float)GRID_SIZE) - 0.5f) * (float)FIELD_SIZE;
705         float y = (((float)gy / (float)GRID_SIZE) - 0.5f) * (float)FIELD_SIZE;
706
707         return Vec2(x, y);
708 }
709
710 static void calc_contrib_bounds(const Vec2 &pos, float mass, Rect *rect)
711 {
712         int gidx = pos_to_grid(pos.x, pos.y);
713         int gx = GRID_X(gidx);
714         int gy = GRID_Y(gidx);
715         int maxrange = (int)ceil(CONTRIB_RANGE(mass));
716
717         int sx = gx - maxrange;
718         int sy = gy - maxrange;
719         int ex = gx + maxrange;
720         int ey = gy + maxrange;
721
722         if(ex > GRID_SIZE) ex = GRID_SIZE;
723         if(ey > GRID_SIZE) ey = GRID_SIZE;
724
725         rect->x = sx < 0 ? 0 : sx;
726         rect->y = sy < 0 ? 0 : sy;
727         rect->width = ex - sx;
728         rect->height = ey - sy;
729 }
730
731 static void add_influence(const Vec2 &pos, float mass, float radius, const Rect &cbox)
732 {
733         float *gptr = grid + cbox.y * GRID_SIZE + cbox.x;
734         Vec2 startpos = grid_to_pos(cbox.x, cbox.y);
735
736         for(int y=0; y<cbox.height; y++) {
737                 for(int x=0; x<cbox.width; x++) {
738                         Vec2 cellpos = Vec2(startpos.x + (float)x * GRID_DELTA, startpos.y);
739
740                         Vec2 dir = cellpos - pos;
741                         float dsq = dot(dir, dir);
742                         float radsq = radius * radius;
743                         if(dsq < radsq) {
744                                 dsq = radsq;
745                         }
746
747                         gptr[x] += mass / dsq;
748                 }
749
750                 startpos.y += GRID_DELTA;
751                 gptr += GRID_SIZE;
752         }
753 }
754
755 static Vec2 calc_field_grad(int gidx)
756 {
757         int gx = GRID_X(gidx);
758         int gy = GRID_Y(gidx);
759
760         int nidx = ((gx + 1 >= GRID_SIZE ? gx : gx + 1) << GRID_BITS) | gy;
761         int pidx = ((gx > 0 ? gx - 1 : gx) << GRID_BITS) | gy;
762         float dfdx = grid[nidx] - grid[pidx];
763
764         nidx = (gx << GRID_BITS) | (gy + 1 >= GRID_SIZE ? gy : gy + 1);
765         pidx = (gx << GRID_BITS) | (gy > 0 ? gy - 1 : gy);
766         float dfdy = grid[nidx] - grid[pidx];
767
768         return Vec2(dfdx, dfdy);
769 }
770
771
772 // ---- quad mesh operations ----
773
774 static void destroy_quadmesh(QuadMesh *m)
775 {
776         glDeleteBuffers(1, &m->vbo_v);
777         glDeleteBuffers(1, &m->vbo_uv);
778         glDeleteBuffers(1, &m->ibo);
779
780         delete [] m->v;
781         delete [] m->uv;
782         delete [] m->idx;
783 }
784
785 static void draw_quadmesh(const QuadMesh *m, unsigned int prim)
786 {
787         glEnableClientState(GL_VERTEX_ARRAY);
788         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
789
790         glBindBuffer(GL_ARRAY_BUFFER, m->vbo_v);
791         glVertexPointer(3, GL_FLOAT, 0, 0);
792
793         glBindBuffer(GL_ARRAY_BUFFER, m->vbo_uv);
794         glTexCoordPointer(2, GL_FLOAT, 0, 0);
795
796         glBindBuffer(GL_ARRAY_BUFFER, 0);
797
798         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ibo);
799         glDrawElements(prim, m->num_idx, GL_UNSIGNED_SHORT, 0);
800         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
801
802         glDisableClientState(GL_VERTEX_ARRAY);
803         glDisableClientState(GL_TEXTURE_COORD_ARRAY);
804 }
805
806 static void gen_quad_plane(QuadMesh *m, float width, float height, int usub, int vsub)
807 {
808         Vec3 *vptr;
809         Vec2 *uvptr;
810         uint16_t *iptr;
811
812         if(usub < 1) usub = 1;
813         if(vsub < 1) vsub = 1;
814
815         int uverts = usub + 1;
816         int vverts = vsub + 1;
817         m->num_verts = uverts * vverts;
818         m->num_quads = usub * vsub;
819         m->num_idx = m->num_quads * 4;
820
821         vptr = m->v = new Vec3[m->num_verts];
822         uvptr = m->uv = new Vec2[m->num_verts];
823         iptr = m->idx = new uint16_t[m->num_idx];
824
825         float du = 1.0f / (float)usub;
826         float dv = 1.0f / (float)vsub;
827
828         float u = 0.0f;
829         for(int i=0; i<uverts; i++) {
830                 float x = (u - 0.5f) * width;
831                 float v = 0.0f;
832                 for(int j=0; j<vverts; j++) {
833                         float y = (v - 0.5f) * height;
834
835                         *vptr++ = Vec3(x, 0, y);
836                         *uvptr++ = Vec2(u, v);
837
838                         if(i < usub && j < vsub) {
839                                 int idx = i * vverts + j;
840
841                                 *iptr++ = idx;
842                                 *iptr++ = idx + 1;
843                                 *iptr++ = idx + vverts + 1;
844                                 *iptr++ = idx + vverts;
845                         }
846
847                         v += dv;
848                 }
849                 u += du;
850         }
851
852         glGenBuffers(1, &m->vbo_v);
853         glBindBuffer(GL_ARRAY_BUFFER, m->vbo_v);
854         glBufferData(GL_ARRAY_BUFFER, m->num_verts * 3 * sizeof(float), m->v, GL_STATIC_DRAW);
855
856         glGenBuffers(1, &m->vbo_uv);
857         glBindBuffer(GL_ARRAY_BUFFER, m->vbo_uv);
858         glBufferData(GL_ARRAY_BUFFER, m->num_verts * 2 * sizeof(float), m->uv, GL_STATIC_DRAW);
859
860         glGenBuffers(1, &m->ibo);
861         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ibo);
862         glBufferData(GL_ELEMENT_ARRAY_BUFFER, m->num_idx * sizeof(uint16_t), m->idx, GL_STATIC_DRAW);
863
864         glBindBuffer(GL_ARRAY_BUFFER, 0);
865         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
866 }
867
868 static void spawn_particle(const Vec2 &pos, const Vec2 &vel, float mass)
869 {
870         int gidx = pos_to_grid(pos.x, pos.y);
871
872         if(grid_part[gidx]) {
873                 // merge with existing
874                 Particle *p = grid_part[gidx];
875                 p->vel += vel;
876                 p->mass += mass;
877                 p->radius = MASS_TO_RADIUS(p->mass);
878
879         } else {
880                 Particle *p = alloc_particle();
881                 p->pos = pos;
882                 p->vel = vel;
883                 p->mass = mass;
884                 p->radius = MASS_TO_RADIUS(mass);
885                 grid_part[gidx] = p;
886
887                 add_particle(p);
888         }
889 }
890
891 static void add_particle(Particle *p)
892 {
893         if(plist) plist->prev = p;
894
895         p->next = plist;
896         p->prev = 0;
897         plist = p;
898 }
899
900 static void remove_particle(Particle *p)
901 {
902         assert(plist->prev == 0);
903
904         if(plist == p) {
905                 assert(p->prev == 0);
906                 plist = p->next;
907         }
908         if(p->prev) {
909                 p->prev->next = p->next;
910         }
911         if(p->next) {
912                 p->next->prev = p->prev;
913         }
914         p->prev = p->next = 0;
915 }
916
917 // particle allocator
918 #define MAX_PFREE_SIZE  256
919 static Particle *pfree_list;
920 static int pfree_size;
921
922 static Particle *alloc_particle()
923 {
924         if(pfree_list) {
925                 Particle *p = pfree_list;
926                 pfree_list = pfree_list->next;
927                 pfree_size--;
928                 return p;
929         }
930
931         return new Particle;
932 }
933
934 void free_particle(Particle *p)
935 {
936         if(pfree_size < MAX_PFREE_SIZE) {
937                 p->next = pfree_list;
938                 p->prev = 0;
939                 pfree_list = p;
940                 pfree_size++;
941         } else {
942                 delete p;
943         }
944 }