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