88650127400fac125079e63d9cee5ec0467529a5
[laserbrain_demo] / src / app.cc
1 #include <stdio.h>
2 #include <limits.h>
3 #include <assert.h>
4 #include <goatvr.h>
5 #include "app.h"
6 #include "opengl.h"
7 #include "sdr.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 "renderer.h"
18 #include "avatar.h"
19 #include "vrinput.h"
20 #include "exman.h"
21 #include "blob_exhibit.h"
22 #include "dbg_gui.h"
23
24 #define NEAR_CLIP       5.0
25 #define FAR_CLIP        10000.0
26
27 static void draw_scene();
28 static void toggle_flight();
29 static void calc_framerate();
30 static Ray calc_pick_ray(int x, int y);
31
32 long time_msec;
33 int win_width, win_height;
34 float win_aspect;
35 bool fb_srgb;
36 bool opt_gear_wireframe;
37
38 TextureSet texman;
39 SceneSet sceneman;
40
41 unsigned int sdr_ltmap, sdr_ltmap_notex;
42
43 int fpexcept_enabled;
44
45 static Avatar avatar;
46
47 static float cam_dist = 0.0;
48 static float floor_y;   // last floor height
49 static float user_eye_height = 165;
50
51 static float walk_speed = 300.0f;
52 static float mouse_speed = 0.5f;
53 static bool show_walk_mesh, noclip = false;
54
55 static bool have_headtracking, have_handtracking, should_swap;
56
57 static int prev_mx, prev_my;
58 static bool bnstate[8];
59 static bool keystate[256];
60 static bool gpad_bnstate[64];
61 static Vec2 joy_move, joy_look;
62 static float joy_deadzone = 0.01;
63
64 static float framerate;
65
66 static Mat4 view_matrix, mouse_view_matrix, proj_matrix;
67 static MetaScene *mscn;
68 static unsigned int sdr_post_gamma;
69
70 static long prev_msec;
71
72 static ExhibitManager *exman;
73 static BlobExhibit *blobs;
74 static bool show_blobs;
75
76 ExSelection exsel_active, exsel_hover;
77 ExSelection exsel_grab_left, exsel_grab_right;
78 #define exsel_grab_mouse exsel_grab_right
79 static ExhibitSlot exslot_left, exslot_right;
80 #define exslot_mouse exslot_right
81
82 static Renderer *rend;
83
84 static Ray last_pick_ray;
85
86
87 bool app_init(int argc, char **argv)
88 {
89         set_log_file("demo.log");
90
91         char *env = getenv("FPEXCEPT");
92         if(env && atoi(env)) {
93                 info_log("enabling floating point exceptions\n");
94                 fpexcept_enabled = 1;
95                 enable_fpexcept();
96         }
97
98         if(init_opengl() == -1) {
99                 return false;
100         }
101
102         if(!init_options(argc, argv, "demo.conf")) {
103                 return false;
104         }
105         app_resize(opt.width, opt.height);
106         app_fullscreen(opt.fullscreen);
107
108         if(opt.vr) {
109                 if(goatvr_init() == -1) {
110                         return false;
111                 }
112                 goatvr_set_origin_mode(GOATVR_HEAD);
113                 goatvr_set_units_scale(100.0f);
114
115                 goatvr_startvr();
116                 should_swap = goatvr_should_swap() != 0;
117                 user_eye_height = goatvr_get_eye_height();
118                 have_headtracking = goatvr_have_headtracking();
119                 have_handtracking = goatvr_have_handtracking();
120
121                 goatvr_recenter();
122         }
123
124         if(fb_srgb) {
125                 int srgb_capable;
126                 glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT, &srgb_capable);
127                 printf("Framebuffer %s sRGB-capable\n", srgb_capable ? "is" : "is not");
128                 if(srgb_capable) {
129                         glEnable(GL_FRAMEBUFFER_SRGB);
130                 } else {
131                         fb_srgb = 0;
132                 }
133         }
134
135         glEnable(GL_MULTISAMPLE);
136         glEnable(GL_DEPTH_TEST);
137         glEnable(GL_CULL_FACE);
138         glEnable(GL_NORMALIZE);
139
140         if(!init_debug_gui()) {
141                 return false;
142         }
143
144         Mesh::use_custom_sdr_attr = false;
145
146         float ambient[] = {0.0, 0.0, 0.0, 0.0};
147         glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
148
149         glClearColor(1, 1, 1, 1);
150
151         init_audio();
152
153         if(!init_vrhands()) {
154                 return false;
155         }
156
157         mscn = new MetaScene;
158         if(!mscn->load(opt.scenefile ? opt.scenefile : "data/museum.scene")) {
159                 return false;
160         }
161
162         avatar.pos = mscn->start_pos;
163         Vec3 dir = rotate(Vec3(0, 0, 1), mscn->start_rot);
164         dir.y = 0;
165         avatar.body_rot = rad_to_deg(acos(dot(dir, Vec3(0, 0, 1))));
166
167         exman = new ExhibitManager;
168         /*
169         if(!exman->load(mscn, "data/exhibits")) {
170                 //return false;
171         }
172         */
173
174         blobs = new BlobExhibit;
175         blobs->node = new SceneNode;
176         blobs->init();
177         blobs->node->set_position(Vec3(-680, 160, -100));
178         blobs->node->set_scaling(Vec3(28, 28, 28));
179         blobs->node->update(0);
180
181         exman->add(blobs);
182
183         if(!(sdr_ltmap_notex = create_program_load("sdr/lightmap.v.glsl", "sdr/lightmap-notex.p.glsl"))) {
184                 return false;
185         }
186         set_uniform_int(sdr_ltmap_notex, "texmap", MTL_TEX_DIFFUSE);
187         set_uniform_int(sdr_ltmap_notex, "lightmap", MTL_TEX_LIGHTMAP);
188
189         if(!(sdr_ltmap = create_program_load("sdr/lightmap.v.glsl", "sdr/lightmap-tex.p.glsl"))) {
190                 return false;
191         }
192         set_uniform_int(sdr_ltmap, "texmap", MTL_TEX_DIFFUSE);
193         set_uniform_int(sdr_ltmap, "lightmap", MTL_TEX_LIGHTMAP);
194
195         if(!fb_srgb) {
196                 sdr_post_gamma = create_program_load("sdr/post_gamma.v.glsl", "sdr/post_gamma.p.glsl");
197         }
198
199         rend = new Renderer;
200         rend->set_scene(mscn);
201
202         glUseProgram(0);
203
204         if(opt.vr || opt.fullscreen) {
205                 app_grab_mouse(true);
206         }
207
208         if(mscn->music && opt.music) {
209                 mscn->music->play(AUDIO_PLAYMODE_LOOP);
210         }
211         return true;
212 }
213
214 void app_cleanup()
215 {
216         if(mscn->music) {
217                 mscn->music->stop();
218         }
219         destroy_audio();
220
221         app_grab_mouse(false);
222         if(opt.vr) {
223                 goatvr_shutdown();
224         }
225         destroy_vrhands();
226
227         delete rend;
228
229         /* this must be destroyed before the scene graph to detach exhibit nodes
230          * before the scene tries to delete them recursively
231          */
232         delete exman;
233
234         texman.clear();
235         sceneman.clear();
236
237         cleanup_debug_gui();
238 }
239
240 static bool constrain_walk_mesh(const Vec3 &v, Vec3 *newv)
241 {
242         Mesh *wm = mscn->walk_mesh;
243         if(!wm) {
244                 *newv = v;
245                 return true;
246         }
247
248         Ray downray = Ray(v, Vec3(0, -1, 0));
249         HitPoint hit;
250         if(mscn->walk_mesh->intersect(downray, &hit)) {
251                 *newv = hit.pos;
252                 newv->y += user_eye_height;
253                 return true;
254         }
255         return false;
256 }
257
258 static void update(float dt)
259 {
260         texman.update();
261         sceneman.update();
262
263         mscn->update(dt);
264         exman->update(dt);
265
266         float speed = walk_speed * dt;
267         Vec3 dir;
268
269         // joystick
270         float jdeadsq = joy_deadzone * joy_deadzone;
271         float jmove_lensq = length_sq(joy_move);
272         float jlook_lensq = length_sq(joy_look);
273
274         if(jmove_lensq > jdeadsq) {
275                 float len = sqrt(jmove_lensq);
276                 jmove_lensq -= jdeadsq;
277
278                 float mag = len * len;
279                 dir.x += mag * joy_move.x / len * 2.0 * speed;
280                 dir.z += mag * joy_move.y / len * 2.0 * speed;
281         }
282         if(jlook_lensq > jdeadsq) {
283                 float len = sqrt(jlook_lensq);
284                 jlook_lensq -= jdeadsq;
285
286                 float mag = len * len;
287                 avatar.body_rot += mag * joy_look.x / len * 200.0 * dt;
288                 avatar.head_alt += mag * joy_look.y / len * 100.0 * dt;
289                 if(avatar.head_alt < -90.0f) avatar.head_alt = -90.0f;
290                 if(avatar.head_alt > 90.0f) avatar.head_alt = 90.0f;
291         }
292
293         // keyboard move
294         if(keystate[(int)'w']) {
295                 dir.z -= speed;
296         }
297         if(keystate[(int)'s']) {
298                 dir.z += speed;
299         }
300         if(keystate[(int)'d']) {
301                 dir.x += speed;
302         }
303         if(keystate[(int)'a']) {
304                 dir.x -= speed;
305         }
306         if(keystate[(int)'q'] || gpad_bnstate[GPAD_UP]) {
307                 avatar.pos.y += speed;
308         }
309         if(keystate[(int)'z'] || gpad_bnstate[GPAD_DOWN]) {
310                 avatar.pos.y -= speed;
311         }
312
313         float theta = M_PI * avatar.body_rot / 180.0f;
314         Vec3 newpos;
315         newpos.x = avatar.pos.x + cos(theta) * dir.x - sin(theta) * dir.z;
316         newpos.y = avatar.pos.y;
317         newpos.z = avatar.pos.z + sin(theta) * dir.x + cos(theta) * dir.z;
318
319         if(noclip) {
320                 avatar.pos = newpos;
321         } else {
322                 if(!constrain_walk_mesh(newpos, &avatar.pos)) {
323                         float dtheta = M_PI / 32.0;
324                         float theta = dtheta;
325                         Vec2 dir2d = newpos.xz() - avatar.pos.xz();
326
327                         for(int i=0; i<16; i++) {
328                                 Vec2 dvec = rotate(dir2d, theta);
329                                 Vec3 pos = avatar.pos + Vec3(dvec.x, 0, dvec.y);
330                                 if(constrain_walk_mesh(pos, &avatar.pos)) {
331                                         break;
332                                 }
333                                 dvec = rotate(dir2d, -theta);
334                                 pos = avatar.pos + Vec3(dvec.x, 0, dvec.y);
335                                 if(constrain_walk_mesh(pos, &avatar.pos)) {
336                                         break;
337                                 }
338                                 theta += dtheta;
339                         }
340                 }
341                 floor_y = avatar.pos.y - user_eye_height;
342         }
343
344         // TODO move to the avatar system
345         // calculate mouselook view matrix
346         mouse_view_matrix = Mat4::identity;
347         mouse_view_matrix.pre_translate(0, 0, -cam_dist);
348         if(!have_headtracking) {
349                 mouse_view_matrix.pre_rotate_x(deg_to_rad(avatar.head_alt));
350         }
351         mouse_view_matrix.pre_rotate_y(deg_to_rad(avatar.body_rot));
352         mouse_view_matrix.pre_translate(-avatar.pos.x, -avatar.pos.y, -avatar.pos.z);
353
354         // check if an exhibit is hovered-over by mouse or 6dof (only if we don't have one grabbed)
355         if(!exsel_grab_mouse) {
356                 // XXX note: using previous view/proj matrix lattency shouldn't be an issue but
357                 //           make sure state-creep doesn't get us
358                 // XXX also this mouse-picking probably should only be active in non-VR mode
359                 Ray ray = calc_pick_ray(prev_mx, prev_my);
360                 exsel_hover = exman->select(ray);
361         }
362
363         if(!exslot_left.empty()) exslot_left.node.update(dt);
364         if(!exslot_right.empty()) exslot_right.node.update(dt);
365
366         // update hand-tracking
367         if(have_handtracking) {
368                 update_vrhands(&avatar);
369         }
370 }
371
372 void app_display()
373 {
374         float dt = (float)(time_msec - prev_msec) / 1000.0f;
375         prev_msec = time_msec;
376
377         if(debug_gui) {
378                 ImGui::GetIOPtr()->DeltaTime = dt;
379                 ImGui::NewFrame();
380
381                 ImGui::ShowTestWindow();
382         }
383
384         if(opt.vr) {
385                 // VR mode
386                 goatvr_draw_start();
387                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
388
389                 update(dt);
390
391                 for(int i=0; i<2; i++) {
392                         // for each eye
393                         goatvr_draw_eye(i);
394
395                         proj_matrix = goatvr_projection_matrix(i, NEAR_CLIP, FAR_CLIP);
396                         glMatrixMode(GL_PROJECTION);
397                         glLoadMatrixf(proj_matrix[0]);
398
399                         view_matrix = mouse_view_matrix * Mat4(goatvr_view_matrix(i));
400                         glMatrixMode(GL_MODELVIEW);
401                         glLoadMatrixf(view_matrix[0]);
402
403                         draw_scene();
404                         if(have_handtracking) {
405                                 draw_vrhands();
406                         }
407
408                         if(debug_gui) {
409                                 ImGui::Render();
410                         }
411                 }
412                 goatvr_draw_done();
413
414                 if(should_swap) {
415                         app_swap_buffers();
416                 }
417
418         } else {
419                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
420
421                 update(dt);
422
423                 proj_matrix.perspective(deg_to_rad(50.0), win_aspect, NEAR_CLIP, FAR_CLIP);
424                 glMatrixMode(GL_PROJECTION);
425                 glLoadMatrixf(proj_matrix[0]);
426
427                 view_matrix = mouse_view_matrix;
428                 glMatrixMode(GL_MODELVIEW);
429                 glLoadMatrixf(view_matrix[0]);
430
431                 draw_scene();
432
433                 if(!fb_srgb && sdr_post_gamma) {
434                         slow_post(sdr_post_gamma);
435                         glUseProgram(0);
436                 }
437
438                 if(debug_gui) {
439                         ImGui::Render();
440                 }
441                 app_swap_buffers();
442         }
443         assert(glGetError() == GL_NO_ERROR);
444
445         calc_framerate();
446 }
447
448
449 static void draw_scene()
450 {
451         rend->draw();
452         exman->draw();
453
454         /*
455         if(have_handtracking) {
456                 Mat4 head_xform = inverse(mouse_view_matrix);//goatvr_head_matrix();
457                 Mat4 head_dir_xform = head_xform.upper3x3();
458
459                 glUseProgram(0);
460                 glPushAttrib(GL_ENABLE_BIT);
461                 glDisable(GL_LIGHTING);
462                 glBegin(GL_LINES);
463                 for(int i=0; i<2; i++) {
464                         if(hand[i].valid) {
465                                 glColor3f(i, 1 - i, i);
466                         } else {
467                                 glColor3f(0.5, 0.5, 0.5);
468                         }
469                         Vec3 v = head_xform * hand[i].pos;
470                         Vec3 dir = head_dir_xform * rotate(Vec3(0, 0, -1), hand[i].rot) * 20.0f;
471                         Vec3 up = head_dir_xform * rotate(Vec3(0, 1, 0), hand[i].rot) * 10.0f;
472                         Vec3 right = head_dir_xform * rotate(Vec3(1, 0, 0), hand[i].rot) * 10.0f;
473
474                         glVertex3f(v.x, v.y, v.z);
475                         glVertex3f(v.x + dir.x, v.y + dir.y, v.z + dir.z);
476                         glVertex3f(v.x - right.x, v.y - right.y, v.z - right.z);
477                         glVertex3f(v.x + right.x, v.y + right.y, v.z + right.z);
478                         glVertex3f(v.x - up.x, v.y - up.y, v.z - up.z);
479                         glVertex3f(v.x + up.x, v.y + up.y, v.z + up.z);
480                 }
481                 glEnd();
482                 glPopAttrib();
483         }
484         */
485
486         if(show_walk_mesh && mscn->walk_mesh) {
487                 glPushAttrib(GL_ENABLE_BIT);
488                 glEnable(GL_BLEND);
489                 glBlendFunc(GL_ONE, GL_ONE);
490                 glEnable(GL_POLYGON_OFFSET_FILL);
491
492                 glUseProgram(0);
493
494                 glPolygonOffset(-1, 1);
495                 glDepthMask(0);
496
497                 glColor3f(0.3, 0.08, 0.01);
498                 mscn->walk_mesh->draw();
499
500                 glDepthMask(1);
501
502                 glPopAttrib();
503         }
504
505         print_text(Vec2(9 * win_width / 10, 20), Vec3(1, 1, 0), "fps: %.1f", framerate);
506         draw_ui();
507 }
508
509
510 void app_reshape(int x, int y)
511 {
512         glViewport(0, 0, x, y);
513         goatvr_set_fb_size(x, y, 1.0f);
514         debug_gui_reshape(x, y);
515 }
516
517 void app_keyboard(int key, bool pressed)
518 {
519         unsigned int mod = app_get_modifiers();
520
521         if(debug_gui && !(pressed && (key == '`' || key == 27))) {
522                 debug_gui_key(key, pressed, mod);
523                 return; // ignore all keystrokes when GUI is visible
524         }
525
526         if(pressed) {
527                 switch(key) {
528                 case 27:
529                         app_quit();
530                         break;
531
532                 case '\n':
533                 case '\r':
534                         if(mod & MOD_ALT) {
535                                 app_toggle_fullscreen();
536                         }
537                         break;
538
539                 case '`':
540                         debug_gui = !debug_gui;
541                         show_message("debug gui %s", debug_gui ? "enabled" : "disabled");
542                         break;
543
544                 case 'm':
545                         app_toggle_grab_mouse();
546                         show_message("mouse %s", app_is_mouse_grabbed() ? "grabbed" : "released");
547                         break;
548
549                 case 'w':
550                         if(mod & MOD_CTRL) {
551                                 show_walk_mesh = !show_walk_mesh;
552                                 show_message("walk mesh: %s", show_walk_mesh ? "on" : "off");
553                         }
554                         break;
555
556                 case 'c':
557                         if(mod & MOD_CTRL) {
558                                 noclip = !noclip;
559                                 show_message(noclip ? "no clip" : "clip");
560                         }
561                         break;
562
563                 case 'f':
564                         toggle_flight();
565                         break;
566
567                 case 'p':
568                         if(mod & MOD_CTRL) {
569                                 fb_srgb = !fb_srgb;
570                                 show_message("gamma correction for non-sRGB framebuffers: %s\n", fb_srgb ? "off" : "on");
571                         }
572                         break;
573
574                 case '=':
575                         walk_speed *= 1.25;
576                         show_message("walk speed: %g", walk_speed);
577                         break;
578
579                 case '-':
580                         walk_speed *= 0.75;
581                         show_message("walk speed: %g", walk_speed);
582                         break;
583
584                 case ']':
585                         mouse_speed *= 1.2;
586                         show_message("mouse speed: %g", mouse_speed);
587                         break;
588
589                 case '[':
590                         mouse_speed *= 0.8;
591                         show_message("mouse speed: %g", mouse_speed);
592                         break;
593
594                 case 'b':
595                         show_blobs = !show_blobs;
596                         show_message("blobs: %s\n", show_blobs ? "on" : "off");
597                         break;
598
599                 case ' ':
600                         goatvr_recenter();
601                         show_message("VR recenter\n");
602                         break;
603
604                 case 'x':
605                         exman->load(mscn, "data/exhibits");
606                         break;
607                 }
608         }
609
610         if(key < 256 && !(mod & (MOD_CTRL | MOD_ALT))) {
611                 keystate[key] = pressed;
612         }
613 }
614
615 void app_mouse_button(int bn, bool pressed, int x, int y)
616 {
617         static int press_x, press_y;
618
619         if(debug_gui) {
620                 debug_gui_mbutton(bn, pressed, x, y);
621                 return; // ignore mouse events while GUI is visible
622         }
623
624         prev_mx = x;
625         prev_my = y;
626         bnstate[bn] = pressed;
627
628         if(bn == 0) {
629                 ExSelection sel;
630                 Ray ray = calc_pick_ray(x, y);
631                 sel = exman->select(ray);
632
633                 if(pressed) {
634                         if(sel && (app_get_modifiers() & MOD_CTRL)) {
635                                 exsel_grab_mouse = sel;
636                                 Vec3 pos = sel.ex->node->get_position();
637                                 debug_log("grabbing... (%g %g %g)\n", pos.x, pos.y, pos.z);
638                                 exslot_mouse.node.set_position(pos);
639                                 exslot_mouse.node.set_rotation(sel.ex->node->get_rotation());
640                                 exslot_mouse.attach_exhibit(sel.ex, EXSLOT_ATTACH_TRANSIENT);
641                                 if(exsel_active) {
642                                         exsel_active = ExSelection::null;       // cancel active on grab
643                                 }
644                         }
645                         press_x = x;
646                         press_y = y;
647
648                 } else {
649                         if(exsel_grab_mouse) {
650                                 // cancel grab on mouse release
651                                 debug_log("releasing...\n");
652                                 Exhibit *ex = exsel_grab_mouse.ex;
653                                 Vec3 pos = exslot_mouse.node.get_position();
654                                 debug_log("release location: %g %g %g\n", pos.x, pos.y, pos.z);
655
656                                 ExhibitSlot *slot = exman->nearest_empty_slot(pos);
657                                 if(!slot) {
658                                         debug_log("no nearby slot\n");
659                                         if(ex->prev_slot && ex->prev_slot->empty()) {
660                                                 slot = ex->prev_slot;
661                                                 debug_log("previous slot available though\n");
662                                         }
663                                 }
664
665                                 if(slot) {
666                                         slot->attach_exhibit(ex);
667                                 } else {
668                                         // nowhere to put it, so stash it for later
669                                         exslot_mouse.detach_exhibit();
670                                         exman->stash_exhibit(ex);
671                                         debug_log("no slots available, stashing\n");
672                                 }
673
674                                 exsel_grab_mouse = ExSelection::null;
675                         }
676
677                         if(abs(press_x - x) < 5 && abs(press_y - y) < 5) {
678                                 exsel_active = sel;     // select or deselect active exhibit
679                                 if(sel) {
680                                         debug_log("selecting...\n");
681                                 } else {
682                                         debug_log("deselecting...\n");
683                                 }
684                         }
685
686                         press_x = press_y = INT_MIN;
687                 }
688         }
689 }
690
691 static inline void mouse_look(float dx, float dy)
692 {
693         float scrsz = (float)win_height;
694         avatar.body_rot += dx * 512.0 / scrsz;
695         avatar.head_alt += dy * 512.0 / scrsz;
696
697         if(avatar.head_alt < -90) avatar.head_alt = -90;
698         if(avatar.head_alt > 90) avatar.head_alt = 90;
699 }
700
701 static void mouse_zoom(float dx, float dy)
702 {
703         cam_dist += dy * 0.1;
704         if(cam_dist < 0.0) cam_dist = 0.0;
705 }
706
707 void app_mouse_motion(int x, int y)
708 {
709         if(debug_gui) {
710                 debug_gui_mmotion(x, y);
711                 return; // ignore mouse events while GUI is visible
712         }
713
714         int dx = x - prev_mx;
715         int dy = y - prev_my;
716         prev_mx = x;
717         prev_my = y;
718
719         if(!dx && !dy) return;
720
721         if(exsel_grab_mouse) {
722                 Vec3 pos = exslot_mouse.node.get_node_position();
723                 Vec3 dir = transpose(view_matrix.upper3x3()) * Vec3(dx * 1.0, dy * -1.0, 0);
724
725                 exslot_mouse.node.set_position(pos + dir);
726         }
727
728         if(bnstate[2]) {
729                 mouse_look(dx, dy);
730         }
731 }
732
733 void app_mouse_delta(int dx, int dy)
734 {
735         if(bnstate[2]) {
736                 mouse_zoom(dx * mouse_speed, dy * mouse_speed);
737         } else {
738                 mouse_look(dx * mouse_speed, dy * mouse_speed);
739         }
740 }
741
742 void app_mouse_wheel(int dir)
743 {
744         if(debug_gui) {
745                 debug_gui_wheel(dir);
746         }
747 }
748
749 void app_gamepad_axis(int axis, float val)
750 {
751         switch(axis) {
752         case 0:
753                 joy_move.x = val;
754                 break;
755         case 1:
756                 joy_move.y = val;
757                 break;
758
759         case 2:
760                 joy_look.x = val;
761                 break;
762         case 3:
763                 joy_look.y = val;
764                 break;
765         }
766 }
767
768 void app_gamepad_button(int bn, bool pressed)
769 {
770         gpad_bnstate[bn] = pressed;
771
772         if(pressed) {
773                 switch(bn) {
774                 case GPAD_LSTICK:
775                         toggle_flight();
776                         break;
777
778                 case GPAD_X:
779                         show_blobs = !show_blobs;
780                         show_message("blobs: %s\n", show_blobs ? "on" : "off");
781                         break;
782
783                 case GPAD_START:
784                         goatvr_recenter();
785                         show_message("VR recenter\n");
786                         break;
787
788                 default:
789                         break;
790                 }
791         }
792 }
793
794 static void toggle_flight()
795 {
796         static float prev_walk_speed = -1.0;
797         if(prev_walk_speed < 0.0) {
798                 noclip = true;
799                 prev_walk_speed = walk_speed;
800                 walk_speed = 1000.0;
801                 show_message("fly mode\n");
802         } else {
803                 noclip = false;
804                 walk_speed = prev_walk_speed;
805                 prev_walk_speed = -1.0;
806                 show_message("walk mode\n");
807         }
808 }
809
810 static void calc_framerate()
811 {
812         //static int ncalc;
813         static int nframes;
814         static long prev_upd;
815
816         long elapsed = time_msec - prev_upd;
817         if(elapsed >= 1000) {
818                 framerate = (float)nframes / (float)(elapsed * 0.001);
819                 nframes = 1;
820                 prev_upd = time_msec;
821
822                 /*if(++ncalc >= 5) {
823                         printf("fps: %f\n", framerate);
824                         ncalc = 0;
825                 }*/
826         } else {
827                 ++nframes;
828         }
829 }
830
831 static Ray calc_pick_ray(int x, int y)
832 {
833         float nx = (float)x / (float)win_width;
834         float ny = (float)(win_height - y) / (float)win_height;
835
836         last_pick_ray = mouse_pick_ray(nx, ny, view_matrix, proj_matrix);
837         return last_pick_ray;
838 }