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