implemented a laser pointer
[vrfileman] / src / app.cc
1 #include <stdio.h>
2 #include <assert.h>
3 #include <limits.h>
4 #include "opengl.h"
5 #include "app.h"
6 #include "gmath/gmath.h"
7 #include "mesh.h"
8 #include "meshgen.h"
9 #include "backdrop.h"
10 #include "goatvr.h"
11 #include "opt.h"
12 #include "fs.h"
13 #include "rtarg.h"
14 #include "texture.h"
15 #include "sdr.h"
16
17 #define LASER_TIMEOUT   2000
18 #define PTIME_INVAL             -LASER_TIMEOUT
19
20 static void draw_scene();
21 static void draw_laser();
22
23 int win_width, win_height;
24 float win_aspect;
25 long time_msec;
26 double time_sec;
27 Mat4 view_matrix, mouse_view_matrix, proj_matrix;
28
29 float cam_height = 1.65;
30
31 static Ray mray;
32 static float mtheta, mphi;
33 static long last_pointer_time = PTIME_INVAL;
34 static float laser_alpha;
35
36 static float cam_theta, cam_phi = 15;
37 static bool should_swap;
38
39 static bool bnstate[16];
40 static int mouse_x, mouse_y;
41
42 static float fov = 60.0;
43
44 static bool have_headtracking;
45
46 static RenderTarget *rtarg;
47 static bool rtarg_valid;
48 static unsigned int post_sdr;
49
50
51 bool app_init(int argc, char **argv)
52 {
53         if(!init_options(argc, argv, "vrfileman.conf")) {
54                 return false;
55         }
56         app_resize(opt.width, opt.height);
57         app_fullscreen(opt.fullscreen);
58
59         if(init_opengl() == -1) {
60                 return false;
61         }
62
63         glEnable(GL_MULTISAMPLE);
64
65         int aasamples = 0;
66         glGetIntegerv(GL_SAMPLES, &aasamples);
67         printf("got %d samples per pixel\n", aasamples);
68
69         printf("Max anisotropy: %d\n", glcaps.max_aniso);
70
71         glEnable(GL_CULL_FACE);
72         glEnable(GL_DEPTH_TEST);
73
74         if(opt.srgb && GLEW_ARB_framebuffer_sRGB) {
75                 printf("enabling sRGB framebuffer\n");
76                 glEnable(GL_FRAMEBUFFER_SRGB);
77         }
78
79         rtarg = new RenderTarget;
80         rtarg->create(GL_RGB16F);
81
82         if(opt.vr) {
83                 if(goatvr_init() == -1) {
84                         return false;
85                 }
86                 goatvr_set_origin_mode(GOATVR_HEAD);
87
88                 goatvr_startvr();
89                 should_swap = goatvr_should_swap() != 0;
90                 cam_height = goatvr_get_eye_height();
91                 have_headtracking = goatvr_have_headtracking();
92
93                 goatvr_recenter();
94                 RenderTarget::default_fbo = goatvr_get_fbo();
95         }
96
97         if(opt.srgb) {
98                 add_shader_header(GL_FRAGMENT_SHADER, "#define set_pixel set_pixel_linear");
99         } else {
100                 add_shader_header(GL_FRAGMENT_SHADER, "#define set_pixel set_pixel_srgb");
101         }
102         if(!(post_sdr = create_program_load("sdr/post.v.glsl", "sdr/post.p.glsl"))) {
103                 return false;
104         }
105         clear_shader_header(0);
106
107         Mesh::use_custom_sdr_attr = false;
108
109         if(!init_backdrop()) {
110                 return false;
111         }
112
113         if(!init_fs(opt.path)) {
114                 return false;
115         }
116
117         if(opt.vr || opt.fullscreen) {
118                 app_grab_mouse(true);
119         }
120         return true;
121 }
122
123 void app_cleanup()
124 {
125         app_grab_mouse(false);
126         if(opt.vr) {
127                 goatvr_shutdown();
128         }
129         delete rtarg;
130         free_program(post_sdr);
131         cleanup_backdrop();
132 }
133
134 static void update()
135 {
136         if(!rtarg_valid) {
137                 rtarg->resize(win_width, win_height);
138         }
139
140         /* calculate the mouselook view matrix */
141         mouse_view_matrix = Mat4::identity;
142         if(!have_headtracking) {
143                 mouse_view_matrix.pre_rotate_x(deg_to_rad(cam_phi));
144         }
145         mouse_view_matrix.pre_rotate_y(deg_to_rad(cam_theta));
146         mouse_view_matrix.pre_translate(0, -cam_height, 0);
147
148         long interval = time_msec - last_pointer_time;
149         if(interval < LASER_TIMEOUT) {
150                 Vec3 target;
151
152                 if(have_headtracking) {
153                         target.x = sin(deg_to_rad(mtheta)) * cos(deg_to_rad(mphi)) * 200.0;
154                         target.y = sin(deg_to_rad(mphi)) * 200.0;
155                         target.z = -cos(deg_to_rad(mtheta)) * cos(deg_to_rad(mphi)) * 200.0;
156
157                         mray.origin = inverse(view_matrix) * Vec3(0.2, -0.4, 0.0);
158                 } else {
159                         /* pick on a distant sphere to find where the mouse is pointing to
160                          * and use that as the other end of the mouse ray
161                          */
162                         float px = 2.0 * (float)mouse_x / win_width - 1.0;
163                         float py = 1.0 - (2.0 * (float)mouse_y / win_height);
164
165                         Mat4 mvp_inv = inverse(mouse_view_matrix * proj_matrix);
166                         Vec4 vfar = mvp_inv * Vec4(px, py, 1, 1);
167
168                         target = vfar.xyz() / vfar.w;
169
170                         mray.origin = Vec3(0, cam_height, 0);
171                 }
172
173                 mray.dir = target - mray.origin;
174
175                 laser_alpha = 1.0 - std::max(4.0f * interval / LASER_TIMEOUT - 3.0f, 0.0f);
176         } else {
177                 laser_alpha = 0.0f;
178         }
179 }
180
181 void app_draw()
182 {
183         update();
184
185         if(opt.vr) {
186                 // VR mode
187                 goatvr_draw_start();
188                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
189
190                 for(int i=0; i<2; i++) {
191                         goatvr_draw_eye(i);
192
193                         proj_matrix = goatvr_projection_matrix(i, 0.1, 200.0);
194                         glMatrixMode(GL_PROJECTION);
195                         glLoadMatrixf(proj_matrix[0]);
196
197                         view_matrix = mouse_view_matrix * Mat4(goatvr_view_matrix(i));
198
199                         glMatrixMode(GL_MODELVIEW);
200                         glLoadMatrixf(view_matrix[0]);
201
202                         draw_scene();
203                 }
204                 goatvr_draw_done();
205
206                 if(should_swap) {
207                         app_swap_buffers();
208                 }
209                 app_redraw();   // in VR mode, force continuous redraw
210
211         } else {
212                 // regular monoscopic mode
213                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
214
215                 proj_matrix.perspective(deg_to_rad(fov), win_aspect, 0.1, 200.0);
216                 glMatrixMode(GL_PROJECTION);
217                 glLoadMatrixf(proj_matrix[0]);
218
219                 view_matrix = mouse_view_matrix;
220
221                 glMatrixMode(GL_MODELVIEW);
222                 glLoadMatrixf(view_matrix[0]);
223
224                 draw_scene();
225
226                 app_swap_buffers();
227                 app_redraw();   // since we added animation we need to redisplay even in non-VR mode
228         }
229         assert(glGetError() == GL_NO_ERROR);
230 }
231
232 static void draw_scene()
233 {
234         if(!opt.vr) {
235                 set_render_target(rtarg);
236                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
237         }
238
239         draw_backdrop();
240         draw_fs();
241         if(laser_alpha > 0.0) {
242                 draw_laser();
243         }
244
245         if(!opt.vr) {
246                 set_render_target(0);
247
248                 glPushAttrib(GL_ENABLE_BIT);
249                 glDisable(GL_DEPTH_TEST);
250
251                 glUseProgram(post_sdr);
252                 set_uniform_matrix4(post_sdr, "texmat", rtarg->get_texture_matrix()[0]);
253
254                 bind_texture(rtarg->get_texture());
255
256                 glBegin(GL_QUADS);
257                 glTexCoord2f(0, 0);
258                 glVertex2f(-1, -1);
259                 glTexCoord2f(1, 0);
260                 glVertex2f(1, -1);
261                 glTexCoord2f(1, 1);
262                 glVertex2f(1, 1);
263                 glTexCoord2f(0, 1);
264                 glVertex2f(-1, 1);
265                 glEnd();
266
267                 bind_texture(0);
268                 glUseProgram(0);
269
270                 glPopAttrib();
271         }
272 }
273
274 static void draw_laser()
275 {
276         glPushAttrib(GL_ENABLE_BIT);
277         glEnable(GL_BLEND);
278         glBlendFunc(GL_SRC_ALPHA, GL_ONE);
279
280         glDepthMask(0);
281         glUseProgram(0);
282
283         glLineWidth(2.0);
284         glBegin(GL_LINES);
285         glColor4f(1.0, 0.3, 0.2, laser_alpha);
286         glVertex3f(mray.origin.x, mray.origin.y, mray.origin.z);
287         Vec3 end = mray.origin + mray.dir;
288         glVertex3f(end.x, end.y, end.z);
289         glEnd();
290         glLineWidth(1.0);
291
292         glDepthMask(1);
293         glPopAttrib();
294 }
295
296 void app_reshape(int x, int y)
297 {
298         glViewport(0, 0, x, y);
299         rtarg_valid = false;
300
301         if(opt.vr) {
302                 goatvr_set_fb_size(x, y, 1.0);
303         }
304 }
305
306 void app_keyboard(int key, bool pressed)
307 {
308         if(pressed) {
309                 switch(key) {
310                 case 27:
311                         app_quit();
312                         break;
313
314                 case 'f':
315                         if(!opt.vr || should_swap) {
316                                 /* we take the need to swap as a signal that our window is not managed
317                                  * by some VR compositor, and therefore it's safe to fullscreen without
318                                  * upsetting the VR rendering output
319                                  */
320                                 opt.fullscreen = !opt.fullscreen;
321                                 app_fullscreen(opt.fullscreen);
322                         }
323                         break;
324
325                 case ' ':
326                         if(opt.vr) {
327                                 goatvr_recenter();
328                         }
329                         break;
330
331                 case '-':
332                         fov += 1.0;
333                         if(fov > 160.0) fov = 160.0;
334                         break;
335
336                 case '=':
337                         fov -= 1.0;
338                         if(fov < 0.0) fov = 0.0;
339                         break;
340
341                 case '`':
342                         app_toggle_grab_mouse();
343                         mtheta = mphi = 0;
344                         break;
345                 }
346         }
347 }
348
349 void app_mouse_button(int bn, bool pressed, int x, int y)
350 {
351         bnstate[bn] = pressed;
352         mouse_x = x;
353         mouse_y = y;
354 }
355
356 void app_mouse_motion(int x, int y)
357 {
358         int dx = x - mouse_x;
359         int dy = y - mouse_y;
360
361         app_mouse_delta(dx, dy);
362
363         mouse_x = x;
364         mouse_y = y;
365 }
366
367 template <typename T>
368 static T clamp(T x, T a, T b)
369 {
370         return x < a ? a : (b < x ? b : x);
371 }
372
373 void app_mouse_delta(int dx, int dy)
374 {
375         if(!dx && !dy) return;
376
377         mouse_x = clamp(mouse_x + dx, 0, win_width);
378         mouse_y = clamp(mouse_y + dy, 0, win_height);
379
380         if(bnstate[0]) {
381                 cam_theta += dx * 0.5;
382                 cam_phi = clamp(cam_phi + dy * 0.5f, -90.0f, 90.0f);
383
384                 mtheta = mphi = 0;
385                 last_pointer_time = PTIME_INVAL;
386
387         } else {
388                 mtheta += dx * 0.05;
389                 mphi = clamp(mphi - dy * 0.05f, -90.0f, 90.0f);
390
391                 last_pointer_time = time_msec;
392         }
393
394         //app_redraw();
395 }