added a non-interactive blobs exhibit
[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(0.2, 0.2, 0.2, 1.0);
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(-250, 150, 250));
123         blobs->node->set_scaling(Vec3(20, 20, 20));
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
130         if(!(sdr_ltmap = create_program_load("sdr/lightmap.v.glsl", "sdr/lightmap-tex.p.glsl"))) {
131                 return false;
132         }
133         set_uniform_int(sdr_ltmap, "texmap", 0);
134         set_uniform_int(sdr_ltmap, "lightmap", 1);
135
136         if(!fb_srgb) {
137                 sdr_post_gamma = create_program_load("sdr/post_gamma.v.glsl", "sdr/post_gamma.p.glsl");
138         }
139
140         glUseProgram(0);
141
142         if(opt.vr || opt.fullscreen) {
143                 app_grab_mouse(true);
144         }
145         return true;
146 }
147
148 void app_cleanup()
149 {
150         app_grab_mouse(false);
151         if(opt.vr) {
152                 goatvr_shutdown();
153         }
154
155         blobs->destroy();
156         delete blobs->node;
157         delete blobs;
158
159         texman.clear();
160         sceneman.clear();
161 }
162
163 static bool constrain_walk_mesh(const Vec3 &v, Vec3 *newv)
164 {
165         Mesh *wm = mscn->walk_mesh;
166         if(!wm) {
167                 *newv = v;
168                 return true;
169         }
170
171         Ray downray = Ray(v, Vec3(0, -1, 0));
172         HitPoint hit;
173         if(mscn->walk_mesh->intersect(downray, &hit)) {
174                 *newv = hit.pos;
175                 newv->y += user_eye_height;
176                 return true;
177         }
178         return false;
179 }
180
181 static void update(float dt)
182 {
183         texman.update();
184         sceneman.update();
185
186         mscn->update(dt);
187         if(show_blobs) {
188                 blobs->update(dt);
189         }
190
191         float speed = walk_speed * dt;
192         Vec3 dir;
193
194         // joystick
195         float jdeadsq = joy_deadzone * joy_deadzone;
196         float jmove_lensq = length_sq(joy_move);
197         float jlook_lensq = length_sq(joy_look);
198
199         if(jmove_lensq > jdeadsq) {
200                 float len = sqrt(jmove_lensq);
201                 jmove_lensq -= jdeadsq;
202
203                 float mag = len * len;
204                 dir.x += mag * joy_move.x / len * 2.0 * speed;
205                 dir.z += mag * joy_move.y / len * 2.0 * speed;
206         }
207         if(jlook_lensq > jdeadsq) {
208                 float len = sqrt(jlook_lensq);
209                 jlook_lensq -= jdeadsq;
210
211                 float mag = len * len;
212                 cam_theta += mag * joy_look.x / len * 200.0 * dt;
213                 cam_phi += mag * joy_look.y / len * 100.0 * dt;
214                 if(cam_phi < -90.0f) cam_phi = -90.0f;
215                 if(cam_phi > 90.0f) cam_phi = 90.0f;
216         }
217
218         // keyboard move
219         if(keystate[(int)'w']) {
220                 dir.z -= speed;
221         }
222         if(keystate[(int)'s']) {
223                 dir.z += speed;
224         }
225         if(keystate[(int)'d']) {
226                 dir.x += speed;
227         }
228         if(keystate[(int)'a']) {
229                 dir.x -= speed;
230         }
231         if(keystate[(int)'q'] || gpad_bnstate[GPAD_UP]) {
232                 cam_pos.y += speed;
233         }
234         if(keystate[(int)'z'] || gpad_bnstate[GPAD_DOWN]) {
235                 cam_pos.y -= speed;
236         }
237
238         float theta = M_PI * cam_theta / 180.0f;
239         Vec3 newpos;
240         newpos.x = cam_pos.x + cos(theta) * dir.x - sin(theta) * dir.z;
241         newpos.y = cam_pos.y;
242         newpos.z = cam_pos.z + sin(theta) * dir.x + cos(theta) * dir.z;
243
244         if(noclip) {
245                 cam_pos = newpos;
246         } else {
247                 if(!constrain_walk_mesh(newpos, &cam_pos)) {
248                         float dtheta = M_PI / 32.0;
249                         float theta = dtheta;
250                         Vec2 dir2d = newpos.xz() - cam_pos.xz();
251
252                         for(int i=0; i<16; i++) {
253                                 Vec2 dvec = rotate(dir2d, theta);
254                                 Vec3 pos = cam_pos + Vec3(dvec.x, 0, dvec.y);
255                                 if(constrain_walk_mesh(pos, &cam_pos)) {
256                                         break;
257                                 }
258                                 dvec = rotate(dir2d, -theta);
259                                 pos = cam_pos + Vec3(dvec.x, 0, dvec.y);
260                                 if(constrain_walk_mesh(pos, &cam_pos)) {
261                                         break;
262                                 }
263                                 theta += dtheta;
264                         }
265                 }
266                 floor_y = cam_pos.y - user_eye_height;
267         }
268
269         // calculate mouselook view matrix
270         mouse_view_matrix = Mat4::identity;
271         mouse_view_matrix.pre_translate(0, 0, -cam_dist);
272         if(!have_headtracking) {
273                 mouse_view_matrix.pre_rotate_x(deg_to_rad(cam_phi));
274         }
275         mouse_view_matrix.pre_rotate_y(deg_to_rad(cam_theta));
276         mouse_view_matrix.pre_translate(-cam_pos.x, -cam_pos.y, -cam_pos.z);
277 }
278
279 static void set_light(int idx, const Vec3 &pos, const Vec3 &color)
280 {
281         unsigned int lt = GL_LIGHT0 + idx;
282         float posv[] = { pos.x, pos.y, pos.z, 1 };
283         float colv[] = { color.x, color.y, color.z, 1 };
284
285         glEnable(lt);
286         glLightfv(lt, GL_POSITION, posv);
287         glLightfv(lt, GL_DIFFUSE, colv);
288         glLightfv(lt, GL_SPECULAR, colv);
289 }
290
291 void app_display()
292 {
293         float dt = (float)(time_msec - prev_msec) / 1000.0f;
294         prev_msec = time_msec;
295
296         update(dt);
297
298         if(opt.vr) {
299                 // VR mode
300                 goatvr_draw_start();
301                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
302
303                 for(int i=0; i<2; i++) {
304                         // for each eye
305                         goatvr_draw_eye(i);
306
307                         proj_matrix = goatvr_projection_matrix(i, NEAR_CLIP, FAR_CLIP);
308                         glMatrixMode(GL_PROJECTION);
309                         glLoadMatrixf(proj_matrix[0]);
310
311                         view_matrix = mouse_view_matrix * Mat4(goatvr_view_matrix(i));
312                         glMatrixMode(GL_MODELVIEW);
313                         glLoadMatrixf(view_matrix[0]);
314
315                         draw_scene();
316                 }
317                 goatvr_draw_done();
318
319                 if(should_swap) {
320                         app_swap_buffers();
321                 }
322
323         } else {
324                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
325
326                 proj_matrix.perspective(deg_to_rad(50.0), win_aspect, NEAR_CLIP, FAR_CLIP);
327                 glMatrixMode(GL_PROJECTION);
328                 glLoadMatrixf(proj_matrix[0]);
329
330                 view_matrix = mouse_view_matrix;
331                 glMatrixMode(GL_MODELVIEW);
332                 glLoadMatrixf(view_matrix[0]);
333
334                 draw_scene();
335
336                 if(!fb_srgb && sdr_post_gamma) {
337                         slow_post(sdr_post_gamma);
338                         glUseProgram(0);
339                 }
340                 app_swap_buffers();
341         }
342         assert(glGetError() == GL_NO_ERROR);
343
344         calc_framerate();
345 }
346
347
348 static void draw_scene()
349 {
350         static const Vec3 lpos[] = { Vec3(-50, 75, 100), Vec3(100, 0, 30), Vec3(-10, -10, 60) };
351         set_light(0, lpos[0], Vec3(1.0, 0.8, 0.7) * 0.8);
352         set_light(1, lpos[1], Vec3(0.6, 0.7, 1.0) * 0.6);
353         set_light(2, lpos[2], Vec3(0.8, 1.0, 0.8) * 0.3);
354
355         mscn->draw();
356         if(show_blobs) {
357                 blobs->draw();
358         }
359
360         if(show_walk_mesh && mscn->walk_mesh) {
361                 glPushAttrib(GL_ENABLE_BIT);
362                 glEnable(GL_BLEND);
363                 glBlendFunc(GL_ONE, GL_ONE);
364                 glDisable(GL_LIGHTING);
365                 glEnable(GL_POLYGON_OFFSET_FILL);
366
367                 glUseProgram(0);
368
369                 glPolygonOffset(-1, 1);
370                 glDepthMask(0);
371
372                 glColor3f(0.3, 0.08, 0.01);
373                 mscn->walk_mesh->draw();
374
375                 glDepthMask(1);
376
377                 glPopAttrib();
378         }
379
380         print_text(Vec2(9 * win_width / 10, 20), Vec3(1, 1, 0), "fps: %.1f", framerate);
381         draw_ui();
382 }
383
384
385 void app_reshape(int x, int y)
386 {
387         glViewport(0, 0, x, y);
388         goatvr_set_fb_size(x, y, 1.0f);
389 }
390
391 void app_keyboard(int key, bool pressed)
392 {
393         unsigned int mod = app_get_modifiers();
394
395         if(pressed) {
396                 switch(key) {
397                 case 27:
398                         app_quit();
399                         break;
400
401                 case '\n':
402                 case '\r':
403                         if(mod & MOD_ALT) {
404                                 app_toggle_fullscreen();
405                         }
406                         break;
407
408                 case '`':
409                         app_toggle_grab_mouse();
410                         show_message("mouse %s", app_is_mouse_grabbed() ? "grabbed" : "released");
411                         break;
412
413                 case 'w':
414                         if(mod & MOD_CTRL) {
415                                 show_walk_mesh = !show_walk_mesh;
416                                 show_message("walk mesh: %s", show_walk_mesh ? "on" : "off");
417                         }
418                         break;
419
420                 case 'c':
421                         if(mod & MOD_CTRL) {
422                                 noclip = !noclip;
423                                 show_message(noclip ? "no clip" : "clip");
424                         }
425                         break;
426
427                 case 'f':
428                         toggle_flight();
429                         break;
430
431                 case 'p':
432                         if(mod & MOD_CTRL) {
433                                 fb_srgb = !fb_srgb;
434                                 show_message("gamma correction for non-sRGB framebuffers: %s\n", fb_srgb ? "off" : "on");
435                         }
436                         break;
437
438                 case '=':
439                         walk_speed *= 1.25;
440                         show_message("walk speed: %g", walk_speed);
441                         break;
442
443                 case '-':
444                         walk_speed *= 0.75;
445                         show_message("walk speed: %g", walk_speed);
446                         break;
447
448                 case ']':
449                         mouse_speed *= 1.2;
450                         show_message("mouse speed: %g", mouse_speed);
451                         break;
452
453                 case '[':
454                         mouse_speed *= 0.8;
455                         show_message("mouse speed: %g", mouse_speed);
456                         break;
457
458                 case 'b':
459                         show_blobs = !show_blobs;
460                         show_message("blobs: %s\n", show_blobs ? "on" : "off");
461                         break;
462                 }
463         }
464
465         if(key < 256 && !(mod & (MOD_CTRL | MOD_ALT))) {
466                 keystate[key] = pressed;
467         }
468 }
469
470 void app_mouse_button(int bn, bool pressed, int x, int y)
471 {
472         prev_mx = x;
473         prev_my = y;
474         bnstate[bn] = pressed;
475 }
476
477 static inline void mouse_look(float dx, float dy)
478 {
479         float scrsz = (float)win_height;
480         cam_theta += dx * 512.0 / scrsz;
481         cam_phi += dy * 512.0 / scrsz;
482
483         if(cam_phi < -90) cam_phi = -90;
484         if(cam_phi > 90) cam_phi = 90;
485 }
486
487 static void mouse_zoom(float dx, float dy)
488 {
489         cam_dist += dy * 0.1;
490         if(cam_dist < 0.0) cam_dist = 0.0;
491 }
492
493 void app_mouse_motion(int x, int y)
494 {
495         int dx = x - prev_mx;
496         int dy = y - prev_my;
497         prev_mx = x;
498         prev_my = y;
499
500         if(!dx && !dy) return;
501
502         if(bnstate[0]) {
503                 mouse_look(dx, dy);
504         }
505         if(bnstate[2]) {
506                 mouse_zoom(dx, dy);
507         }
508 }
509
510 void app_mouse_delta(int dx, int dy)
511 {
512         if(bnstate[2]) {
513                 mouse_zoom(dx * mouse_speed, dy * mouse_speed);
514         } else {
515                 mouse_look(dx * mouse_speed, dy * mouse_speed);
516         }
517 }
518
519 void app_gamepad_axis(int axis, float val)
520 {
521         switch(axis) {
522         case 0:
523                 joy_move.x = val;
524                 break;
525         case 1:
526                 joy_move.y = val;
527                 break;
528
529         case 2:
530                 joy_look.x = val;
531                 break;
532         case 3:
533                 joy_look.y = val;
534                 break;
535         }
536 }
537
538 void app_gamepad_button(int bn, bool pressed)
539 {
540         gpad_bnstate[bn] = pressed;
541
542         if(pressed) {
543                 switch(bn) {
544                 case GPAD_LSTICK:
545                         toggle_flight();
546                         break;
547
548                 case GPAD_X:
549                         show_blobs = !show_blobs;
550                         show_message("blobs: %s\n", show_blobs ? "on" : "off");
551                         break;
552
553                 default:
554                         break;
555                 }
556         }
557 }
558
559 static void toggle_flight()
560 {
561         static float prev_walk_speed = -1.0;
562         if(prev_walk_speed < 0.0) {
563                 noclip = true;
564                 prev_walk_speed = walk_speed;
565                 walk_speed = 1000.0;
566                 show_message("fly mode\n");
567         } else {
568                 noclip = false;
569                 walk_speed = prev_walk_speed;
570                 prev_walk_speed = -1.0;
571                 show_message("walk mode\n");
572         }
573 }
574
575 static void calc_framerate()
576 {
577         static int nframes;
578         static long prev_upd;
579
580         long elapsed = time_msec - prev_upd;
581         if(elapsed >= 1000) {
582                 framerate = (float)nframes / (float)(elapsed * 0.001);
583                 nframes = 1;
584                 prev_upd = time_msec;
585         } else {
586                 ++nframes;
587         }
588 }