initial commit, eq circuit emulator
[eqemu] / src / main.cc
1 /*
2 eqemu - electronic queue system emulator
3 Copyright (C) 2014  John Tsiombikas <nuclear@member.fsf.org>,
4                     Eleni-Maria Stea <eleni@mutantstargoat.com>
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <float.h>
22 #include <assert.h>
23 #include <errno.h>
24 #include <unistd.h>
25 #include <sys/select.h>
26 #include <GL/glew.h>
27 #include <X11/Xlib.h>
28 #include <GL/glx.h>
29 #include "dev.h"
30 #include "scene.h"
31 #include "timer.h"
32 #include "fblur.h"
33
34
35 enum {
36         REGULAR_PASS,
37         GLOW_PASS
38 };
39
40 void post_redisplay();
41 static bool init();
42 static void cleanup();
43 static void display();
44 static void draw_scene(int pass = REGULAR_PASS);
45 static void post_glow(void);
46 static void keyb(int key, bool pressed);
47 static void mouse(int bn, bool pressed, int x, int y);
48 static void motion(int x, int y);
49 static Ray calc_pick_ray(int x, int y);
50 static int next_pow2(int x);
51
52 static Window create_window(const char *title, int xsz, int ysz);
53 static void process_events();
54 static int translate_keysym(KeySym sym);
55
56 static int proc_args(int argc, char **argv);
57
58 static Display *dpy;
59 static Window win;
60 static GLXContext ctx;
61 static Atom xa_wm_prot, xa_wm_del_win;
62
63 static int win_width, win_height;
64
65 static bool draw_pending;
66 static bool win_mapped;
67
68 static int fakefd = -1;
69 static char *fake_devpath;
70
71 static float cam_theta, cam_phi, cam_dist = 140;
72 static Scene *scn;
73
74 enum { BN_TICKET, BN_NEXT, NUM_BUTTONS };
75 static const char *button_names[] = { "button1", "button2" };
76 static Object *button_obj[NUM_BUTTONS];
77 static Object *disp_obj[2];
78 static Object *led_obj[2];
79 static Vector3 led_on_emissive;
80
81 static bool opt_use_glow = true;
82 #define GLOW_SZ_DIV             3
83 static unsigned int glow_tex;
84 static int glow_tex_xsz, glow_tex_ysz, glow_xsz, glow_ysz;
85 static int glow_iter = 1;
86 static int blur_size = 5;
87 unsigned char *glow_framebuf;
88
89
90 int main(int argc, char **argv)
91 {
92         if(proc_args(argc, argv) == -1) {
93                 return 1;
94         }
95         if(!init()) {
96                 return 1;
97         }
98         atexit(cleanup);
99
100         int xfd = ConnectionNumber(dpy);
101
102         // run once through pending events before going into the select loop
103         process_events();
104
105         for(;;) {
106                 fd_set rd;
107                 FD_ZERO(&rd);
108
109                 FD_SET(xfd, &rd);
110                 FD_SET(fakefd, &rd);
111
112                 struct timeval noblock = {0, 0};
113                 int maxfd = xfd > fakefd ? xfd : fakefd;
114                 while(!XPending(dpy) && select(maxfd + 1, &rd, 0, 0, draw_pending ? &noblock : 0) == -1 && errno == EINTR);
115
116                 if(XPending(dpy) || FD_ISSET(xfd, &rd)) {
117                         process_events();
118                 }
119                 if(FD_ISSET(fakefd, &rd)) {
120                         proc_dev_input();
121                 }
122
123                 if(draw_pending) {
124                         draw_pending = false;
125                         display();
126                 }
127         }
128         return 0;
129 }
130
131 void post_redisplay()
132 {
133         draw_pending = true;
134 }
135
136 static bool init()
137 {
138         if(fake_devpath) {
139                 if((fakefd = start_dev(fake_devpath)) == -1) {
140                         return false;
141                 }
142         }
143
144         if(!(dpy = XOpenDisplay(0))) {
145                 fprintf(stderr, "failed to connect to the X server!\n");
146                 return false;
147         }
148
149         if(!(win = create_window("equeue device emulator", 512, 512))) {
150                 return false;
151         }
152
153         glewInit();
154
155         scn = new Scene;
156         if(!scn->load("data/device.obj")) {
157                 fprintf(stderr, "failed to load device 3D model\n");
158                 return false;
159         }
160
161         for(int i=0; i<NUM_BUTTONS; i++) {
162                 button_obj[i] = scn->get_object(button_names[i]);
163                 if(!button_obj[i]) {
164                         fprintf(stderr, "invalid 3D model\n");
165                         return false;
166                 }
167                 BSphere &bs = button_obj[i]->get_mesh()->get_bounds();
168                 bs.set_radius(bs.get_radius() * 1.5);
169         }
170
171         disp_obj[0] = scn->get_object("7seg0");
172         disp_obj[1] = scn->get_object("7seg1");
173         if(!disp_obj[0] || !disp_obj[1]) {
174                 fprintf(stderr, "invalid 3D model\n");
175                 return false;
176         }
177         scn->remove_object(disp_obj[0]);
178         scn->remove_object(disp_obj[1]);
179
180         led_obj[0] = scn->get_object("led1");
181         led_obj[1] = scn->get_object("led2");
182         if(!led_obj[0] || !led_obj[1]) {
183                 fprintf(stderr, "invalid 3D model\n");
184                 return false;
185         }
186         scn->remove_object(led_obj[0]);
187         scn->remove_object(led_obj[1]);
188         led_on_emissive = led_obj[0]->mtl.emissive;
189
190         // create the glow texture
191         glGenTextures(1, &glow_tex);
192         glBindTexture(GL_TEXTURE_2D, glow_tex);
193         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
194         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
195         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
196
197         glEnable(GL_DEPTH_TEST);
198         glEnable(GL_CULL_FACE);
199         glEnable(GL_LIGHTING);
200         glEnable(GL_LIGHT0);
201
202         glClearColor(0.1, 0.1, 0.1, 1);
203
204         return true;
205 }
206
207 static void cleanup()
208 {
209         delete scn;
210
211         stop_dev();
212
213         if(!dpy) return;
214
215         if(win) {
216                 XDestroyWindow(dpy, win);
217         }
218         XCloseDisplay(dpy);
219 }
220
221 #define DIGIT_USZ       (1.0 / 11.0)
222 #define MIN_REDRAW_INTERVAL             (1000 / 40)             /* 40fps */
223
224 static void display()
225 {
226         glMatrixMode(GL_MODELVIEW);
227         glLoadIdentity();
228         glTranslatef(0, 0, -cam_dist);
229         glRotatef(cam_phi, 1, 0, 0);
230         glRotatef(cam_theta, 0, 1, 0);
231
232         float lpos[] = {-7, 5, 10, 0};
233         glLightfv(GL_LIGHT0, GL_POSITION, lpos);
234
235         if(opt_use_glow) {
236                 glViewport(0, 0, glow_xsz, glow_ysz);
237
238                 glClearColor(0, 0, 0, 1);
239                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
240
241                 draw_scene(GLOW_PASS);
242
243                 glReadPixels(0, 0, glow_xsz, glow_ysz, GL_RGBA, GL_UNSIGNED_BYTE, glow_framebuf);
244                 glViewport(0, 0, win_width, win_height);
245         }
246
247         glClearColor(0.05, 0.05, 0.05, 1);
248         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
249
250         draw_scene();
251
252         if(opt_use_glow) {
253                 for(int i=0; i<glow_iter; i++) {
254                         fast_blur(BLUR_BOTH, blur_size, (uint32_t*)glow_framebuf, glow_xsz, glow_ysz);
255                         glBindTexture(GL_TEXTURE_2D, glow_tex);
256                         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, glow_xsz, glow_ysz, GL_RGBA, GL_UNSIGNED_BYTE, glow_framebuf);
257
258                         post_glow();
259                 }
260         }
261
262         if(get_led_state(0)) {
263                 // continuously redraw until the left LED times out
264                 draw_pending = true;
265         }
266
267         glXSwapBuffers(dpy, win);
268         assert(glGetError() == GL_NO_ERROR);
269
270         static long prev_msec;
271         long msec = get_msec();
272         long dt = msec - prev_msec;
273
274         if(dt < MIN_REDRAW_INTERVAL) {
275                 wait_for(MIN_REDRAW_INTERVAL - dt);
276         }
277         prev_msec = get_msec();
278 }
279
280 static void draw_scene(int pass)
281 {
282         if(pass != GLOW_PASS) {
283                 scn->render();
284         }
285
286         // shift the textures and modify the materials to make the display match our state
287         for(int i=0; i<2; i++) {
288                 // 7seg
289                 int digit = get_display_number();
290                 for(int j=0; j<i; j++) {
291                         digit /= 10;
292                 }
293                 digit %= 10;
294
295                 float uoffs = DIGIT_USZ + DIGIT_USZ * digit;
296
297                 disp_obj[i]->mtl.tex_offset[TEX_DIFFUSE] = Vector2(uoffs, 0);
298                 disp_obj[i]->render();
299
300                 // LEDs
301                 if(get_led_state(i)) {
302                         led_obj[i]->mtl.emissive = led_on_emissive;
303                 } else {
304                         led_obj[i]->mtl.emissive = Vector3(0, 0, 0);
305                 }
306                 led_obj[i]->render();
307         }
308 }
309
310 static void post_glow(void)
311 {
312         float max_s = (float)glow_xsz / (float)glow_tex_xsz;
313         float max_t = (float)glow_ysz / (float)glow_tex_ysz;
314
315         glPushAttrib(GL_ENABLE_BIT);
316
317         glBlendFunc(GL_ONE, GL_ONE);
318         glEnable(GL_BLEND);
319         glDisable(GL_CULL_FACE);
320         glDisable(GL_LIGHTING);
321         glDisable(GL_DEPTH_TEST);
322
323         glMatrixMode(GL_MODELVIEW);
324         glPushMatrix();
325         glLoadIdentity();
326         glMatrixMode(GL_PROJECTION);
327         glPushMatrix();
328         glLoadIdentity();
329
330         glEnable(GL_TEXTURE_2D);
331         glBindTexture(GL_TEXTURE_2D, glow_tex);
332
333         glBegin(GL_QUADS);
334         glColor4f(1, 1, 1, 1);
335         glTexCoord2f(0, 0);
336         glVertex2f(-1, -1);
337         glTexCoord2f(max_s, 0);
338         glVertex2f(1, -1);
339         glTexCoord2f(max_s, max_t);
340         glVertex2f(1, 1);
341         glTexCoord2f(0, max_t);
342         glVertex2f(-1, 1);
343         glEnd();
344
345         glPopMatrix();
346         glMatrixMode(GL_MODELVIEW);
347         glPopMatrix();
348
349         glPopAttrib();
350 }
351
352
353 static void reshape(int x, int y)
354 {
355         glViewport(0, 0, x, y);
356
357         glMatrixMode(GL_PROJECTION);
358         glLoadIdentity();
359         gluPerspective(50.0, (float)x / (float)y, 1.0, 1000.0);
360
361         win_width = x;
362         win_height = y;
363
364         if(opt_use_glow) {
365                 glow_xsz = x / GLOW_SZ_DIV;
366                 glow_ysz = y / GLOW_SZ_DIV;
367                 printf("glow image size: %dx%d\n", glow_xsz, glow_ysz);
368
369                 delete [] glow_framebuf;
370                 glow_framebuf = new unsigned char[glow_xsz * glow_ysz * 4];
371
372                 glow_tex_xsz = next_pow2(glow_xsz);
373                 glow_tex_ysz = next_pow2(glow_ysz);
374                 glBindTexture(GL_TEXTURE_2D, glow_tex);
375                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, glow_tex_xsz, glow_tex_ysz, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
376         }
377 }
378
379 static void keyb(int key, bool pressed)
380 {
381         if(pressed) {
382                 switch(key) {
383                 case 27:
384                         exit(0);
385                 }
386         }
387 }
388
389 static bool bnstate[32];
390 static int prev_x, prev_y;
391
392 static void mouse(int bn, bool pressed, int x, int y)
393 {
394         bnstate[bn] = pressed;
395         prev_x = x;
396         prev_y = y;
397
398         if(bn == 0 && pressed) {
399                 // do picking
400                 Ray ray = calc_pick_ray(x, win_height - y);
401
402                 HitPoint minhit;
403                 minhit.t = FLT_MAX;
404                 int hit_found = -1;
405
406                 for(int i=0; i<NUM_BUTTONS; i++) {
407                         HitPoint hit;
408                         if(button_obj[i]->get_mesh()->get_bounds().intersect(ray, &hit) && hit.t < minhit.t) {
409                                 minhit = hit;
410                                 hit_found = i;
411                         }
412                 }
413
414                 if(hit_found != -1) {
415                         switch(hit_found) {
416                         case BN_TICKET:
417                                 issue_ticket();
418                                 break;
419
420                         case BN_NEXT:
421                                 next_customer();
422                                 break;
423                         }
424                         draw_pending = true;
425                 }
426         }
427 }
428
429 static void motion(int x, int y)
430 {
431         int dx = x - prev_x;
432         int dy = y - prev_y;
433         prev_x = x;
434         prev_y = y;
435
436         if(bnstate[0]) {
437                 cam_theta += dx * 0.5;
438                 cam_phi += dy * 0.5;
439                 if(cam_phi < -90) cam_phi = -90;
440                 if(cam_phi > 90) cam_phi = 90;
441
442         } else if(bnstate[2]) {
443                 cam_dist += dy * 0.5;
444                 if(cam_dist < 0.0) cam_dist = 0.0;
445
446         } else {
447                 float xoffs = 2.0 * x / win_width - 1.0;
448                 float yoffs = 2.0 * y / win_height - 1.0;
449                 cam_theta = -xoffs * 15.0 * (win_width / win_height);
450                 cam_phi = -yoffs * 15.0;
451         }
452         draw_pending = true;
453 }
454
455 static Ray calc_pick_ray(int x, int y)
456 {
457         double mv[16], proj[16];
458         int vp[4];
459         double resx, resy, resz;
460         Ray ray;
461
462         glGetDoublev(GL_MODELVIEW_MATRIX, mv);
463         glGetDoublev(GL_PROJECTION_MATRIX, proj);
464         glGetIntegerv(GL_VIEWPORT, vp);
465
466         gluUnProject(x, y, 0, mv, proj, vp, &resx, &resy, &resz);
467         ray.origin = Vector3(resx, resy, resz);
468
469         gluUnProject(x, y, 1, mv, proj, vp, &resx, &resy, &resz);
470         ray.dir = normalize(Vector3(resx, resy, resz) - ray.origin);
471
472         return ray;
473 }
474
475 static int next_pow2(int x)
476 {
477         x--;
478         x = (x >> 1) | x;
479         x = (x >> 2) | x;
480         x = (x >> 4) | x;
481         x = (x >> 8) | x;
482         x = (x >> 16) | x;
483         return x + 1;
484 }
485
486 static Window create_window(const char *title, int xsz, int ysz)
487 {
488         int scr = DefaultScreen(dpy);
489         Window root = RootWindow(dpy, scr);
490
491         int glxattr[] = {
492                 GLX_RGBA,
493                 GLX_RED_SIZE, 8,
494                 GLX_GREEN_SIZE, 8,
495                 GLX_BLUE_SIZE, 8,
496                 GLX_DEPTH_SIZE, 24,
497                 GLX_DOUBLEBUFFER,
498 #if defined(GLX_VERSION_1_4) || defined(GLX_ARB_multisample)
499                 GLX_SAMPLE_BUFFERS_ARB, 1,
500                 GLX_SAMPLES_ARB, 1,
501 #endif
502                 None
503         };
504
505         XVisualInfo *vis = glXChooseVisual(dpy, scr, glxattr);
506         if(!vis) {
507                 fprintf(stderr, "failed to find a suitable visual\n");
508                 return 0;
509         }
510
511         if(!(ctx = glXCreateContext(dpy, vis, 0, True))) {
512                 fprintf(stderr, "failed to create OpenGL context\n");
513                 XFree(vis);
514                 return -1;
515         }
516
517         XSetWindowAttributes xattr;
518         xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, scr);
519         xattr.colormap = XCreateColormap(dpy, root, vis->visual, AllocNone);
520         unsigned int xattr_mask = CWColormap | CWBackPixel | CWBorderPixel;
521
522         Window win = XCreateWindow(dpy, root, 0, 0, xsz, ysz, 0, vis->depth, InputOutput,
523                         vis->visual, xattr_mask, &xattr);
524         if(!win) {
525                 fprintf(stderr, "failed to create window\n");
526                 glXDestroyContext(dpy, ctx);
527                 XFree(vis);
528                 return -1;
529         }
530         XFree(vis);
531
532         unsigned int evmask = StructureNotifyMask | VisibilityChangeMask | ExposureMask |
533                 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
534                 PointerMotionMask | LeaveWindowMask;
535         XSelectInput(dpy, win, evmask);
536
537         xa_wm_prot = XInternAtom(dpy, "WM_PROTOCOLS", False);
538         xa_wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
539         XSetWMProtocols(dpy, win, &xa_wm_del_win, 1);
540
541         XClassHint hint;
542         hint.res_name = hint.res_class = (char*)"equeue_win";
543         XSetClassHint(dpy, win, &hint);
544
545         XTextProperty wm_name;
546         XStringListToTextProperty((char**)&title, 1, &wm_name);
547         XSetWMName(dpy, win, &wm_name);
548         XSetWMIconName(dpy, win, &wm_name);
549         XFree(wm_name.value);
550
551         XMapWindow(dpy, win);
552         glXMakeCurrent(dpy, win, ctx);
553
554         return win;
555 }
556
557 static void process_events()
558 {
559         XEvent ev;
560
561         while(XPending(dpy)) {
562                 XNextEvent(dpy, &ev);
563                 switch(ev.type) {
564                 case MapNotify:
565                         win_mapped = true;
566                         break;
567
568                 case UnmapNotify:
569                         win_mapped = false;
570                         break;
571
572                 case Expose:
573                         if(win_mapped && ev.xexpose.count == 0) {
574                                 draw_pending = true;
575                         }
576                         break;
577
578                 case MotionNotify:
579                         motion(ev.xmotion.x, ev.xmotion.y);
580                         break;
581
582                 case ButtonPress:
583                         mouse(ev.xbutton.button - 1, true, ev.xbutton.x, ev.xbutton.y);
584                         break;
585
586                 case ButtonRelease:
587                         mouse(ev.xbutton.button - 1, false, ev.xbutton.x, ev.xbutton.y);
588                         break;
589
590                 case KeyPress:
591                         {
592                                 KeySym sym = XLookupKeysym(&ev.xkey, 0);
593                                 keyb(translate_keysym(sym), true);
594                         }
595                         break;
596
597                 case KeyRelease:
598                         {
599                                 KeySym sym = XLookupKeysym(&ev.xkey, 0);
600                                 keyb(translate_keysym(sym), false);
601                         }
602                         break;
603
604                 case ConfigureNotify:
605                         {
606                                 int xsz = ev.xconfigure.width;
607                                 int ysz = ev.xconfigure.height;
608
609                                 if(xsz != win_width || ysz != win_height) {
610                                         win_width = xsz;
611                                         win_height = ysz;
612                                         reshape(xsz, ysz);
613                                 }
614                         }
615                         break;
616
617                 case ClientMessage:
618                         if(ev.xclient.message_type == xa_wm_prot) {
619                                 if((Atom)ev.xclient.data.l[0] == xa_wm_del_win) {
620                                         exit(0);
621                                 }
622                         }
623                         break;
624
625                 case LeaveNotify:
626                         if(ev.xcrossing.mode == NotifyNormal) {
627                                 cam_theta = cam_phi = 0;
628                                 draw_pending = true;
629                         }
630                         break;
631
632                 default:
633                         break;
634                 }
635
636         }
637 }
638
639 static int translate_keysym(KeySym sym)
640 {
641         switch(sym) {
642         case XK_BackSpace:
643                 return '\b';
644         case XK_Tab:
645                 return '\t';
646         case XK_Linefeed:
647                 return '\r';
648         case XK_Return:
649                 return '\n';
650         case XK_Escape:
651                 return 27;
652         default:
653                 break;
654         }
655         return (int)sym;
656 }
657
658 static int proc_args(int argc, char **argv)
659 {
660         for(int i=1; i<argc; i++) {
661                 if(argv[i][0] == '-') {
662                         fprintf(stderr, "unexpected option: %s\n", argv[i]);
663                         return -1;
664
665                 } else {
666                         if(fake_devpath) {
667                                 fprintf(stderr, "unexpected argument: %s\n", argv[i]);
668                                 return -1;
669                         }
670                         fake_devpath = argv[i];
671                 }
672         }
673         if(!fake_devpath) {
674                 fprintf(stderr, "no device path specified, running standalone\n");
675         }
676         return 0;
677 }