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