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