fbd8609db08c68ecf84678fae031b3b356012c23
[ld37_one_room] / src / app.cc
1 #include <stdio.h>
2 #include <assert.h>
3 #include <goatvr.h>
4 #include "app.h"
5 #include "opengl.h"
6 #include "sdr.h"
7 #include "ubersdr.h"
8 #include "texture.h"
9 #include "mesh.h"
10 #include "meshgen.h"
11 #include "scene.h"
12 #include "metascene.h"
13 #include "datamap.h"
14 #include "ui.h"
15 #include "opt.h"
16 #include "post.h"
17 #include "blob_exhibit.h"
18
19 #define NEAR_CLIP       0.5
20 #define FAR_CLIP        1000.0
21
22 static void draw_scene();
23 static void toggle_flight();
24 static void calc_framerate();
25 static void prebake_shaders();
26
27 long time_msec;
28 int win_width, win_height;
29 float win_aspect;
30 bool fb_srgb;
31 bool opt_gear_wireframe;
32
33 TextureSet texman;
34 SceneSet sceneman;
35
36 unsigned int sdr_shadow, sdr_shadow_notex;
37
38 static float cam_dist = 0.0;
39 static float cam_theta, cam_phi;
40 static Vec3 cam_pos;
41 static float floor_y;   // last floor height
42 static float user_eye_height = 1.65;
43
44 static float walk_speed = 3.0f;
45 static float mouse_speed = 0.5f;
46 static bool show_walk_mesh, noclip = false;
47
48 static bool have_headtracking, should_swap;
49
50 static int prev_mx, prev_my;
51 static bool bnstate[8];
52 static bool keystate[256];
53 static bool gpad_bnstate[64];
54 static Vec2 joy_move, joy_look;
55 static float joy_deadzone = 0.01;
56
57 static float framerate;
58
59 static Mat4 view_matrix, mouse_view_matrix, proj_matrix;
60 static MetaScene *mscn;
61 static unsigned int sdr_post_gamma;
62
63 static long prev_msec;
64
65 static BlobExhibit *blobs;
66 static bool show_blobs;
67
68
69 bool app_init(int argc, char **argv)
70 {
71         if(!init_options(argc, argv, "demo.conf")) {
72                 return false;
73         }
74         app_resize(opt.width, opt.height);
75         app_fullscreen(opt.fullscreen);
76
77         if(opt.vr) {
78                 if(goatvr_init() == -1) {
79                         return false;
80                 }
81                 goatvr_set_origin_mode(GOATVR_HEAD);
82
83                 goatvr_startvr();
84                 should_swap = goatvr_should_swap() != 0;
85                 user_eye_height = goatvr_get_eye_height();
86                 have_headtracking = goatvr_have_headtracking();
87
88                 goatvr_recenter();
89         }
90
91         int srgb_capable;
92         glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT, &srgb_capable);
93         printf("Framebuffer %s sRGB-capable\n", srgb_capable ? "is" : "is not");
94         fb_srgb = srgb_capable != 0;
95         glEnable(GL_FRAMEBUFFER_SRGB);
96
97         glEnable(GL_MULTISAMPLE);
98         glEnable(GL_DEPTH_TEST);
99         glEnable(GL_CULL_FACE);
100         glEnable(GL_LIGHTING);
101         glEnable(GL_NORMALIZE);
102         glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
103
104         Mesh::use_custom_sdr_attr = false;
105
106         float ambient[] = {0.02, 0.02, 0.02, 0.0};
107         glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
108
109         glClearColor(0.1, 0.1, 0.1, 1.0);
110
111         mscn = new MetaScene;
112         if(!mscn->load(opt.scenefile ? opt.scenefile : "data/room.scene")) {
113                 return false;
114         }
115
116         cam_pos = mscn->start_pos;
117         debug_log("start_pos: [%g %g %g]\n", cam_pos.x, cam_pos.y, cam_pos.z);
118         Vec3 dir = rotate(Vec3(0, 0, 1), mscn->start_rot);
119         dir.y = 0;
120         cam_theta = rad_to_deg(acos(dot(dir, Vec3(0, 0, 1))));
121
122         blobs = new BlobExhibit;
123         blobs->node = new SceneNode;
124         blobs->init();
125         blobs->node->set_position(Vec3(0, 1, 0));
126         blobs->node->set_scaling(Vec3(0.1, 0.1, 0.1));
127         blobs->node->update(0);
128
129         if(uber_init("sdr/uber.v.glsl", "sdr/uber.p.glsl") == -1) {
130                 return false;
131         }
132         prebake_shaders();
133         /*
134         if(!(sdr_shadow_notex = create_program_load("sdr/shadow.v.glsl", "sdr/shadow-notex.p.glsl"))) {
135                 return false;
136         }
137         set_uniform_int(sdr_shadow_notex, "shadowmap", 1);
138         set_uniform_int(sdr_shadow_notex, "envmap", 2);
139         */
140
141         if(!fb_srgb) {
142                 sdr_post_gamma = create_program_load("sdr/post_gamma.v.glsl", "sdr/post_gamma.p.glsl");
143         }
144
145         glUseProgram(0);
146
147         if(opt.vr || opt.fullscreen) {
148                 app_grab_mouse(true);
149         }
150         return true;
151 }
152
153 void app_cleanup()
154 {
155         app_grab_mouse(false);
156         if(opt.vr) {
157                 goatvr_shutdown();
158         }
159
160         blobs->destroy();
161         delete blobs->node;
162         delete blobs;
163
164         texman.clear();
165         sceneman.clear();
166 }
167
168 static bool constrain_walk_mesh(const Vec3 &v, Vec3 *newv)
169 {
170         Mesh *wm = mscn->walk_mesh;
171         if(!wm) {
172                 *newv = v;
173                 return true;
174         }
175
176         Ray downray = Ray(v, Vec3(0, -1, 0));
177         HitPoint hit;
178         if(mscn->walk_mesh->intersect(downray, &hit)) {
179                 *newv = hit.pos;
180                 newv->y += user_eye_height;
181                 return true;
182         }
183         return false;
184 }
185
186 static void update(float dt)
187 {
188         texman.update();
189         sceneman.update();
190
191         mscn->update(dt);
192         if(show_blobs) {
193                 blobs->update(dt);
194         }
195
196         float speed = walk_speed * dt;
197         Vec3 dir;
198
199         // joystick
200         float jdeadsq = joy_deadzone * joy_deadzone;
201         float jmove_lensq = length_sq(joy_move);
202         float jlook_lensq = length_sq(joy_look);
203
204         if(jmove_lensq > jdeadsq) {
205                 float len = sqrt(jmove_lensq);
206                 jmove_lensq -= jdeadsq;
207
208                 float mag = len * len;
209                 dir.x += mag * joy_move.x / len * 2.0 * speed;
210                 dir.z += mag * joy_move.y / len * 2.0 * speed;
211         }
212         if(jlook_lensq > jdeadsq) {
213                 float len = sqrt(jlook_lensq);
214                 jlook_lensq -= jdeadsq;
215
216                 float mag = len * len;
217                 cam_theta += mag * joy_look.x / len * 200.0 * dt;
218                 cam_phi += mag * joy_look.y / len * 100.0 * dt;
219                 if(cam_phi < -90.0f) cam_phi = -90.0f;
220                 if(cam_phi > 90.0f) cam_phi = 90.0f;
221         }
222
223         // keyboard move
224         if(keystate[(int)'w']) {
225                 dir.z -= speed;
226         }
227         if(keystate[(int)'s']) {
228                 dir.z += speed;
229         }
230         if(keystate[(int)'d']) {
231                 dir.x += speed;
232         }
233         if(keystate[(int)'a']) {
234                 dir.x -= speed;
235         }
236         if(keystate[(int)'q'] || gpad_bnstate[GPAD_UP]) {
237                 cam_pos.y += speed;
238         }
239         if(keystate[(int)'z'] || gpad_bnstate[GPAD_DOWN]) {
240                 cam_pos.y -= speed;
241         }
242
243         float theta = M_PI * cam_theta / 180.0f;
244         Vec3 newpos;
245         newpos.x = cam_pos.x + cos(theta) * dir.x - sin(theta) * dir.z;
246         newpos.y = cam_pos.y;
247         newpos.z = cam_pos.z + sin(theta) * dir.x + cos(theta) * dir.z;
248
249         if(noclip) {
250                 cam_pos = newpos;
251         } else {
252                 if(!constrain_walk_mesh(newpos, &cam_pos)) {
253                         float dtheta = M_PI / 32.0;
254                         float theta = dtheta;
255                         Vec2 dir2d = newpos.xz() - cam_pos.xz();
256
257                         for(int i=0; i<16; i++) {
258                                 Vec2 dvec = rotate(dir2d, theta);
259                                 Vec3 pos = cam_pos + Vec3(dvec.x, 0, dvec.y);
260                                 if(constrain_walk_mesh(pos, &cam_pos)) {
261                                         break;
262                                 }
263                                 dvec = rotate(dir2d, -theta);
264                                 pos = cam_pos + Vec3(dvec.x, 0, dvec.y);
265                                 if(constrain_walk_mesh(pos, &cam_pos)) {
266                                         break;
267                                 }
268                                 theta += dtheta;
269                         }
270                 }
271                 floor_y = cam_pos.y - user_eye_height;
272         }
273
274         // calculate mouselook view matrix
275         mouse_view_matrix = Mat4::identity;
276         mouse_view_matrix.pre_translate(0, 0, -cam_dist);
277         if(!have_headtracking) {
278                 mouse_view_matrix.pre_rotate_x(deg_to_rad(cam_phi));
279         }
280         mouse_view_matrix.pre_rotate_y(deg_to_rad(cam_theta));
281         mouse_view_matrix.pre_translate(-cam_pos.x, -cam_pos.y, -cam_pos.z);
282 }
283
284 static void set_light(int idx, const Vec3 &pos, const Vec3 &color)
285 {
286         unsigned int lt = GL_LIGHT0 + idx;
287         float posv[] = { pos.x, pos.y, pos.z, 1 };
288         float colv[] = { color.x, color.y, color.z, 1 };
289
290         glEnable(lt);
291         glLightfv(lt, GL_POSITION, posv);
292         glLightfv(lt, GL_DIFFUSE, colv);
293         glLightfv(lt, GL_SPECULAR, colv);
294
295         uber_enable_light(idx);
296 }
297
298 void app_display()
299 {
300         float dt = (float)(time_msec - prev_msec) / 1000.0f;
301         prev_msec = time_msec;
302
303         update(dt);
304
305         if(opt.vr) {
306                 // VR mode
307                 goatvr_draw_start();
308                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
309
310                 for(int i=0; i<2; i++) {
311                         // for each eye
312                         goatvr_draw_eye(i);
313
314                         proj_matrix = goatvr_projection_matrix(i, NEAR_CLIP, FAR_CLIP);
315                         glMatrixMode(GL_PROJECTION);
316                         glLoadMatrixf(proj_matrix[0]);
317
318                         view_matrix = mouse_view_matrix * Mat4(goatvr_view_matrix(i));
319                         glMatrixMode(GL_MODELVIEW);
320                         glLoadMatrixf(view_matrix[0]);
321
322                         draw_scene();
323                 }
324                 goatvr_draw_done();
325
326                 if(should_swap) {
327                         app_swap_buffers();
328                 }
329
330         } else {
331                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
332
333                 proj_matrix.perspective(deg_to_rad(60.0), win_aspect, NEAR_CLIP, FAR_CLIP);
334                 glMatrixMode(GL_PROJECTION);
335                 glLoadMatrixf(proj_matrix[0]);
336
337                 view_matrix = mouse_view_matrix;
338                 glMatrixMode(GL_MODELVIEW);
339                 glLoadMatrixf(view_matrix[0]);
340
341                 draw_scene();
342
343                 if(!fb_srgb && sdr_post_gamma) {
344                         slow_post(sdr_post_gamma);
345                         glUseProgram(0);
346                 }
347                 app_swap_buffers();
348         }
349         assert(glGetError() == GL_NO_ERROR);
350
351         calc_framerate();
352 }
353
354
355 static void draw_scene()
356 {
357         static const Vec3 lpos[] = { Vec3(-50, 75, 100), Vec3(100, 0, 30), Vec3(-10, -10, 60) };
358         set_light(0, lpos[0], Vec3(1.0, 0.8, 0.7) * 0.8);
359         //set_light(1, lpos[1], Vec3(0.6, 0.7, 1.0) * 0.6);
360         //set_light(2, lpos[2], Vec3(0.8, 1.0, 0.8) * 0.3);
361
362         mscn->draw();
363         if(show_blobs) {
364                 blobs->draw();
365         }
366
367         if(show_walk_mesh && mscn->walk_mesh) {
368                 glPushAttrib(GL_ENABLE_BIT);
369                 glEnable(GL_BLEND);
370                 glBlendFunc(GL_ONE, GL_ONE);
371                 glDisable(GL_LIGHTING);
372                 glEnable(GL_POLYGON_OFFSET_FILL);
373
374                 glUseProgram(0);
375
376                 glPolygonOffset(-1, 1);
377                 glDepthMask(0);
378
379                 glColor3f(0.3, 0.08, 0.01);
380                 mscn->walk_mesh->draw();
381
382                 glDepthMask(1);
383
384                 glPopAttrib();
385         }
386
387         print_text(Vec2(9 * win_width / 10, 20), Vec3(1, 1, 0), "fps: %.1f", framerate);
388         draw_ui();
389 }
390
391
392 void app_reshape(int x, int y)
393 {
394         glViewport(0, 0, x, y);
395         goatvr_set_fb_size(x, y, 1.0f);
396 }
397
398 void app_keyboard(int key, bool pressed)
399 {
400         unsigned int mod = app_get_modifiers();
401
402         if(pressed) {
403                 switch(key) {
404                 case 27:
405                         app_quit();
406                         break;
407
408                 case '\n':
409                 case '\r':
410                         if(mod & MOD_ALT) {
411                                 app_toggle_fullscreen();
412                         }
413                         break;
414
415                 case '`':
416                         app_toggle_grab_mouse();
417                         show_message("mouse %s", app_is_mouse_grabbed() ? "grabbed" : "released");
418                         break;
419
420                 case 'w':
421                         if(mod & MOD_CTRL) {
422                                 show_walk_mesh = !show_walk_mesh;
423                                 show_message("walk mesh: %s", show_walk_mesh ? "on" : "off");
424                         }
425                         break;
426
427                 case 'c':
428                         if(mod & MOD_CTRL) {
429                                 noclip = !noclip;
430                                 show_message(noclip ? "no clip" : "clip");
431                         }
432                         break;
433
434                 case 'f':
435                         toggle_flight();
436                         break;
437
438                 case 'p':
439                         if(mod & MOD_CTRL) {
440                                 fb_srgb = !fb_srgb;
441                                 show_message("gamma correction for non-sRGB framebuffers: %s\n", fb_srgb ? "off" : "on");
442                         }
443                         break;
444
445                 case '=':
446                         walk_speed *= 1.25;
447                         show_message("walk speed: %g", walk_speed);
448                         break;
449
450                 case '-':
451                         walk_speed *= 0.75;
452                         show_message("walk speed: %g", walk_speed);
453                         break;
454
455                 case ']':
456                         mouse_speed *= 1.2;
457                         show_message("mouse speed: %g", mouse_speed);
458                         break;
459
460                 case '[':
461                         mouse_speed *= 0.8;
462                         show_message("mouse speed: %g", mouse_speed);
463                         break;
464
465                 case 'b':
466                         show_blobs = !show_blobs;
467                         show_message("blobs: %s\n", show_blobs ? "on" : "off");
468                         break;
469                 }
470         }
471
472         if(key < 256 && !(mod & (MOD_CTRL | MOD_ALT))) {
473                 keystate[key] = pressed;
474         }
475 }
476
477 void app_mouse_button(int bn, bool pressed, int x, int y)
478 {
479         prev_mx = x;
480         prev_my = y;
481         bnstate[bn] = pressed;
482 }
483
484 static inline void mouse_look(float dx, float dy)
485 {
486         float scrsz = (float)win_height;
487         cam_theta += dx * 512.0 / scrsz;
488         cam_phi += dy * 512.0 / scrsz;
489
490         if(cam_phi < -90) cam_phi = -90;
491         if(cam_phi > 90) cam_phi = 90;
492 }
493
494 static void mouse_zoom(float dx, float dy)
495 {
496         cam_dist += dy * 0.1;
497         if(cam_dist < 0.0) cam_dist = 0.0;
498 }
499
500 void app_mouse_motion(int x, int y)
501 {
502         int dx = x - prev_mx;
503         int dy = y - prev_my;
504         prev_mx = x;
505         prev_my = y;
506
507         if(!dx && !dy) return;
508
509         if(bnstate[0]) {
510                 mouse_look(dx, dy);
511         }
512         if(bnstate[2]) {
513                 mouse_zoom(dx, dy);
514         }
515 }
516
517 void app_mouse_delta(int dx, int dy)
518 {
519         if(bnstate[2]) {
520                 mouse_zoom(dx * mouse_speed, dy * mouse_speed);
521         } else {
522                 mouse_look(dx * mouse_speed, dy * mouse_speed);
523         }
524 }
525
526 void app_gamepad_axis(int axis, float val)
527 {
528         switch(axis) {
529         case 0:
530                 joy_move.x = val;
531                 break;
532         case 1:
533                 joy_move.y = val;
534                 break;
535
536         case 2:
537                 joy_look.x = val;
538                 break;
539         case 3:
540                 joy_look.y = val;
541                 break;
542         }
543 }
544
545 void app_gamepad_button(int bn, bool pressed)
546 {
547         gpad_bnstate[bn] = pressed;
548
549         if(pressed) {
550                 switch(bn) {
551                 case GPAD_LSTICK:
552                         toggle_flight();
553                         break;
554
555                 case GPAD_X:
556                         show_blobs = !show_blobs;
557                         show_message("blobs: %s\n", show_blobs ? "on" : "off");
558                         break;
559
560                 default:
561                         break;
562                 }
563         }
564 }
565
566 static void toggle_flight()
567 {
568         static float prev_walk_speed = -1.0;
569         if(prev_walk_speed < 0.0) {
570                 noclip = true;
571                 prev_walk_speed = walk_speed;
572                 walk_speed = 10.0;
573                 show_message("fly mode\n");
574         } else {
575                 noclip = false;
576                 walk_speed = prev_walk_speed;
577                 prev_walk_speed = -1.0;
578                 show_message("walk mode\n");
579         }
580 }
581
582 static void calc_framerate()
583 {
584         //static int ncalc;
585         static int nframes;
586         static long prev_upd;
587
588         long elapsed = time_msec - prev_upd;
589         if(elapsed >= 1000) {
590                 framerate = (float)nframes / (float)(elapsed * 0.001);
591                 nframes = 1;
592                 prev_upd = time_msec;
593
594                 /*if(++ncalc >= 5) {
595                         printf("fps: %f\n", framerate);
596                         ncalc = 0;
597                 }*/
598         } else {
599                 ++nframes;
600         }
601 }
602
603 static void prebake_shaders()
604 {
605         /*
606         unsigned int prog;
607
608         uber_clear();
609         uber_enable_light(0);
610         uber_enable_light(1);
611         uber_enable_light(2);
612         if((prog = uber_program())) {
613         }
614
615         uber_enable_texmap();
616         if((prog = uber_program())) {
617                 set_uniform_int(prog, "texmap", MTL_TEX_DIFFUSE);
618         }
619
620         uber_clear();
621         */
622 }