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