added VR recenter
[laserbrain_demo] / 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       5.0
19 #define FAR_CLIP        10000.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_ltmap, sdr_ltmap_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 = 165;
41
42 static float walk_speed = 300.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;
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                 goatvr_set_units_scale(100.0f);
81
82                 goatvr_startvr();
83                 should_swap = goatvr_should_swap() != 0;
84                 user_eye_height = goatvr_get_eye_height();
85                 have_headtracking = goatvr_have_headtracking();
86
87                 goatvr_recenter();
88         }
89
90         int srgb_capable;
91         glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT, &srgb_capable);
92         printf("Framebuffer %s sRGB-capable\n", srgb_capable ? "is" : "is not");
93         fb_srgb = srgb_capable != 0;
94         glEnable(GL_FRAMEBUFFER_SRGB);
95
96         glEnable(GL_MULTISAMPLE);
97         glEnable(GL_DEPTH_TEST);
98         glEnable(GL_CULL_FACE);
99         glEnable(GL_LIGHTING);
100         glEnable(GL_NORMALIZE);
101
102         Mesh::use_custom_sdr_attr = false;
103
104         float ambient[] = {0.0, 0.0, 0.0, 0.0};
105         glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
106
107         glClearColor(1, 1, 1, 1);
108
109         mscn = new MetaScene;
110         if(!mscn->load(opt.scenefile ? opt.scenefile : "data/museum.scene")) {
111                 return false;
112         }
113
114         cam_pos = mscn->start_pos;
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(-680, 160, -100));
123         blobs->node->set_scaling(Vec3(28, 28, 28));
124         blobs->node->update(0);
125
126         if(!(sdr_ltmap_notex = create_program_load("sdr/lightmap.v.glsl", "sdr/lightmap-notex.p.glsl"))) {
127                 return false;
128         }
129         set_uniform_int(sdr_ltmap_notex, "texmap", MTL_TEX_DIFFUSE);
130         set_uniform_int(sdr_ltmap_notex, "lightmap", MTL_TEX_LIGHTMAP);
131
132         if(!(sdr_ltmap = create_program_load("sdr/lightmap.v.glsl", "sdr/lightmap-tex.p.glsl"))) {
133                 return false;
134         }
135         set_uniform_int(sdr_ltmap, "texmap", MTL_TEX_DIFFUSE);
136         set_uniform_int(sdr_ltmap, "lightmap", MTL_TEX_LIGHTMAP);
137
138         if(!fb_srgb) {
139                 sdr_post_gamma = create_program_load("sdr/post_gamma.v.glsl", "sdr/post_gamma.p.glsl");
140         }
141
142         glUseProgram(0);
143
144         if(opt.vr || opt.fullscreen) {
145                 app_grab_mouse(true);
146         }
147         return true;
148 }
149
150 void app_cleanup()
151 {
152         app_grab_mouse(false);
153         if(opt.vr) {
154                 goatvr_shutdown();
155         }
156
157         blobs->destroy();
158         delete blobs->node;
159         delete blobs;
160
161         texman.clear();
162         sceneman.clear();
163 }
164
165 static bool constrain_walk_mesh(const Vec3 &v, Vec3 *newv)
166 {
167         Mesh *wm = mscn->walk_mesh;
168         if(!wm) {
169                 *newv = v;
170                 return true;
171         }
172
173         Ray downray = Ray(v, Vec3(0, -1, 0));
174         HitPoint hit;
175         if(mscn->walk_mesh->intersect(downray, &hit)) {
176                 *newv = hit.pos;
177                 newv->y += user_eye_height;
178                 return true;
179         }
180         return false;
181 }
182
183 static void update(float dt)
184 {
185         texman.update();
186         sceneman.update();
187
188         mscn->update(dt);
189         if(show_blobs) {
190                 blobs->update(dt);
191         }
192
193         float speed = walk_speed * dt;
194         Vec3 dir;
195
196         // joystick
197         float jdeadsq = joy_deadzone * joy_deadzone;
198         float jmove_lensq = length_sq(joy_move);
199         float jlook_lensq = length_sq(joy_look);
200
201         if(jmove_lensq > jdeadsq) {
202                 float len = sqrt(jmove_lensq);
203                 jmove_lensq -= jdeadsq;
204
205                 float mag = len * len;
206                 dir.x += mag * joy_move.x / len * 2.0 * speed;
207                 dir.z += mag * joy_move.y / len * 2.0 * speed;
208         }
209         if(jlook_lensq > jdeadsq) {
210                 float len = sqrt(jlook_lensq);
211                 jlook_lensq -= jdeadsq;
212
213                 float mag = len * len;
214                 cam_theta += mag * joy_look.x / len * 200.0 * dt;
215                 cam_phi += mag * joy_look.y / len * 100.0 * dt;
216                 if(cam_phi < -90.0f) cam_phi = -90.0f;
217                 if(cam_phi > 90.0f) cam_phi = 90.0f;
218         }
219
220         // keyboard move
221         if(keystate[(int)'w']) {
222                 dir.z -= speed;
223         }
224         if(keystate[(int)'s']) {
225                 dir.z += speed;
226         }
227         if(keystate[(int)'d']) {
228                 dir.x += speed;
229         }
230         if(keystate[(int)'a']) {
231                 dir.x -= speed;
232         }
233         if(keystate[(int)'q'] || gpad_bnstate[GPAD_UP]) {
234                 cam_pos.y += speed;
235         }
236         if(keystate[(int)'z'] || gpad_bnstate[GPAD_DOWN]) {
237                 cam_pos.y -= speed;
238         }
239
240         float theta = M_PI * cam_theta / 180.0f;
241         Vec3 newpos;
242         newpos.x = cam_pos.x + cos(theta) * dir.x - sin(theta) * dir.z;
243         newpos.y = cam_pos.y;
244         newpos.z = cam_pos.z + sin(theta) * dir.x + cos(theta) * dir.z;
245
246         if(noclip) {
247                 cam_pos = newpos;
248         } else {
249                 if(!constrain_walk_mesh(newpos, &cam_pos)) {
250                         float dtheta = M_PI / 32.0;
251                         float theta = dtheta;
252                         Vec2 dir2d = newpos.xz() - cam_pos.xz();
253
254                         for(int i=0; i<16; i++) {
255                                 Vec2 dvec = rotate(dir2d, theta);
256                                 Vec3 pos = cam_pos + Vec3(dvec.x, 0, dvec.y);
257                                 if(constrain_walk_mesh(pos, &cam_pos)) {
258                                         break;
259                                 }
260                                 dvec = rotate(dir2d, -theta);
261                                 pos = cam_pos + Vec3(dvec.x, 0, dvec.y);
262                                 if(constrain_walk_mesh(pos, &cam_pos)) {
263                                         break;
264                                 }
265                                 theta += dtheta;
266                         }
267                 }
268                 floor_y = cam_pos.y - user_eye_height;
269         }
270
271         // calculate mouselook view matrix
272         mouse_view_matrix = Mat4::identity;
273         mouse_view_matrix.pre_translate(0, 0, -cam_dist);
274         if(!have_headtracking) {
275                 mouse_view_matrix.pre_rotate_x(deg_to_rad(cam_phi));
276         }
277         mouse_view_matrix.pre_rotate_y(deg_to_rad(cam_theta));
278         mouse_view_matrix.pre_translate(-cam_pos.x, -cam_pos.y, -cam_pos.z);
279 }
280
281 static void set_light(int idx, const Vec3 &pos, const Vec3 &color)
282 {
283         unsigned int lt = GL_LIGHT0 + idx;
284         float posv[] = { pos.x, pos.y, pos.z, 1 };
285         float colv[] = { color.x, color.y, color.z, 1 };
286
287         glEnable(lt);
288         glLightfv(lt, GL_POSITION, posv);
289         glLightfv(lt, GL_DIFFUSE, colv);
290         glLightfv(lt, GL_SPECULAR, colv);
291 }
292
293 void app_display()
294 {
295         float dt = (float)(time_msec - prev_msec) / 1000.0f;
296         prev_msec = time_msec;
297
298         update(dt);
299
300         if(opt.vr) {
301                 // VR mode
302                 goatvr_draw_start();
303                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
304
305                 for(int i=0; i<2; i++) {
306                         // for each eye
307                         goatvr_draw_eye(i);
308
309                         proj_matrix = goatvr_projection_matrix(i, NEAR_CLIP, FAR_CLIP);
310                         glMatrixMode(GL_PROJECTION);
311                         glLoadMatrixf(proj_matrix[0]);
312
313                         view_matrix = mouse_view_matrix * Mat4(goatvr_view_matrix(i));
314                         glMatrixMode(GL_MODELVIEW);
315                         glLoadMatrixf(view_matrix[0]);
316
317                         draw_scene();
318                 }
319                 goatvr_draw_done();
320
321                 if(should_swap) {
322                         app_swap_buffers();
323                 }
324
325         } else {
326                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
327
328                 proj_matrix.perspective(deg_to_rad(50.0), win_aspect, NEAR_CLIP, FAR_CLIP);
329                 glMatrixMode(GL_PROJECTION);
330                 glLoadMatrixf(proj_matrix[0]);
331
332                 view_matrix = mouse_view_matrix;
333                 glMatrixMode(GL_MODELVIEW);
334                 glLoadMatrixf(view_matrix[0]);
335
336                 draw_scene();
337
338                 if(!fb_srgb && sdr_post_gamma) {
339                         slow_post(sdr_post_gamma);
340                         glUseProgram(0);
341                 }
342                 app_swap_buffers();
343         }
344         assert(glGetError() == GL_NO_ERROR);
345
346         calc_framerate();
347 }
348
349
350 static void draw_scene()
351 {
352         static const Vec3 lpos[] = { Vec3(-50, 75, 100), Vec3(100, 0, 30), Vec3(-10, -10, 60) };
353         set_light(0, lpos[0], Vec3(1.0, 0.8, 0.7) * 0.8);
354         set_light(1, lpos[1], Vec3(0.6, 0.7, 1.0) * 0.6);
355         set_light(2, lpos[2], Vec3(0.8, 1.0, 0.8) * 0.3);
356
357         mscn->draw();
358         if(show_blobs) {
359                 blobs->draw();
360         }
361
362         if(show_walk_mesh && mscn->walk_mesh) {
363                 glPushAttrib(GL_ENABLE_BIT);
364                 glEnable(GL_BLEND);
365                 glBlendFunc(GL_ONE, GL_ONE);
366                 glDisable(GL_LIGHTING);
367                 glEnable(GL_POLYGON_OFFSET_FILL);
368
369                 glUseProgram(0);
370
371                 glPolygonOffset(-1, 1);
372                 glDepthMask(0);
373
374                 glColor3f(0.3, 0.08, 0.01);
375                 mscn->walk_mesh->draw();
376
377                 glDepthMask(1);
378
379                 glPopAttrib();
380         }
381
382         print_text(Vec2(9 * win_width / 10, 20), Vec3(1, 1, 0), "fps: %.1f", framerate);
383         draw_ui();
384 }
385
386
387 void app_reshape(int x, int y)
388 {
389         glViewport(0, 0, x, y);
390         goatvr_set_fb_size(x, y, 1.0f);
391 }
392
393 void app_keyboard(int key, bool pressed)
394 {
395         unsigned int mod = app_get_modifiers();
396
397         if(pressed) {
398                 switch(key) {
399                 case 27:
400                         app_quit();
401                         break;
402
403                 case '\n':
404                 case '\r':
405                         if(mod & MOD_ALT) {
406                                 app_toggle_fullscreen();
407                         }
408                         break;
409
410                 case '`':
411                         app_toggle_grab_mouse();
412                         show_message("mouse %s", app_is_mouse_grabbed() ? "grabbed" : "released");
413                         break;
414
415                 case 'w':
416                         if(mod & MOD_CTRL) {
417                                 show_walk_mesh = !show_walk_mesh;
418                                 show_message("walk mesh: %s", show_walk_mesh ? "on" : "off");
419                         }
420                         break;
421
422                 case 'c':
423                         if(mod & MOD_CTRL) {
424                                 noclip = !noclip;
425                                 show_message(noclip ? "no clip" : "clip");
426                         }
427                         break;
428
429                 case 'f':
430                         toggle_flight();
431                         break;
432
433                 case 'p':
434                         if(mod & MOD_CTRL) {
435                                 fb_srgb = !fb_srgb;
436                                 show_message("gamma correction for non-sRGB framebuffers: %s\n", fb_srgb ? "off" : "on");
437                         }
438                         break;
439
440                 case '=':
441                         walk_speed *= 1.25;
442                         show_message("walk speed: %g", walk_speed);
443                         break;
444
445                 case '-':
446                         walk_speed *= 0.75;
447                         show_message("walk speed: %g", walk_speed);
448                         break;
449
450                 case ']':
451                         mouse_speed *= 1.2;
452                         show_message("mouse speed: %g", mouse_speed);
453                         break;
454
455                 case '[':
456                         mouse_speed *= 0.8;
457                         show_message("mouse speed: %g", mouse_speed);
458                         break;
459
460                 case 'b':
461                         show_blobs = !show_blobs;
462                         show_message("blobs: %s\n", show_blobs ? "on" : "off");
463                         break;
464
465                 case ' ':
466                         goatvr_recenter();
467                         show_message("VR recenter\n");
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                 case GPAD_START:
561                         goatvr_recenter();
562                         show_message("VR recenter\n");
563                         break;
564
565                 default:
566                         break;
567                 }
568         }
569 }
570
571 static void toggle_flight()
572 {
573         static float prev_walk_speed = -1.0;
574         if(prev_walk_speed < 0.0) {
575                 noclip = true;
576                 prev_walk_speed = walk_speed;
577                 walk_speed = 1000.0;
578                 show_message("fly mode\n");
579         } else {
580                 noclip = false;
581                 walk_speed = prev_walk_speed;
582                 prev_walk_speed = -1.0;
583                 show_message("walk mode\n");
584         }
585 }
586
587 static void calc_framerate()
588 {
589         static int ncalc;
590         static int nframes;
591         static long prev_upd;
592
593         long elapsed = time_msec - prev_upd;
594         if(elapsed >= 1000) {
595                 framerate = (float)nframes / (float)(elapsed * 0.001);
596                 nframes = 1;
597                 prev_upd = time_msec;
598
599                 /*if(++ncalc >= 5) {
600                         printf("fps: %f\n", framerate);
601                         ncalc = 0;
602                 }*/
603         } else {
604                 ++nframes;
605         }
606 }