eqemu

view src/main.cc @ 12:2656099aff12

added copyright notices and license
author John Tsiombikas <nuclear@member.fsf.org>
date Fri, 18 Jul 2014 07:04:21 +0300
parents 2b559dc24c7b
children
line source
1 /*
2 eqemu - electronic queue system emulator
3 Copyright (C) 2014 John Tsiombikas <nuclear@member.fsf.org>,
4 Eleni-Maria Stea <eleni@mutantstargoat.com>
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.
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.
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"
35 enum {
36 REGULAR_PASS,
37 GLOW_PASS
38 };
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);
52 static Window create_window(const char *title, int xsz, int ysz);
53 static void process_events();
54 static int translate_keysym(KeySym sym);
56 static int proc_args(int argc, char **argv);
58 static Display *dpy;
59 static Window win;
60 static GLXContext ctx;
61 static Atom xa_wm_prot, xa_wm_del_win;
63 static int win_width, win_height;
65 static bool draw_pending;
66 static bool win_mapped;
68 static int fakefd = -1;
69 static char *fake_devpath;
71 static float cam_theta, cam_phi, cam_dist = 140;
72 static Scene *scn;
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;
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;
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);
100 int xfd = ConnectionNumber(dpy);
102 // run once through pending events before going into the select loop
103 process_events();
105 for(;;) {
106 fd_set rd;
107 FD_ZERO(&rd);
109 FD_SET(xfd, &rd);
110 FD_SET(fakefd, &rd);
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);
116 if(XPending(dpy) || FD_ISSET(xfd, &rd)) {
117 process_events();
118 }
119 if(FD_ISSET(fakefd, &rd)) {
120 proc_dev_input();
121 }
123 if(draw_pending) {
124 draw_pending = false;
125 display();
126 }
127 }
128 return 0;
129 }
131 void post_redisplay()
132 {
133 draw_pending = true;
134 }
136 static bool init()
137 {
138 if(fake_devpath) {
139 if((fakefd = start_dev(fake_devpath)) == -1) {
140 return false;
141 }
142 }
144 if(!(dpy = XOpenDisplay(0))) {
145 fprintf(stderr, "failed to connect to the X server!\n");
146 return false;
147 }
149 if(!(win = create_window("equeue device emulator", 512, 512))) {
150 return false;
151 }
153 glewInit();
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 }
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 }
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]);
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;
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);
197 glEnable(GL_DEPTH_TEST);
198 glEnable(GL_CULL_FACE);
199 glEnable(GL_LIGHTING);
200 glEnable(GL_LIGHT0);
202 glClearColor(0.1, 0.1, 0.1, 1);
204 return true;
205 }
207 static void cleanup()
208 {
209 delete scn;
211 stop_dev();
213 if(!dpy) return;
215 if(win) {
216 XDestroyWindow(dpy, win);
217 }
218 XCloseDisplay(dpy);
219 }
221 #define DIGIT_USZ (1.0 / 11.0)
222 #define MIN_REDRAW_INTERVAL (1000 / 40) /* 40fps */
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);
232 float lpos[] = {-7, 5, 10, 0};
233 glLightfv(GL_LIGHT0, GL_POSITION, lpos);
235 if(opt_use_glow) {
236 glViewport(0, 0, glow_xsz, glow_ysz);
238 glClearColor(0, 0, 0, 1);
239 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
241 draw_scene(GLOW_PASS);
243 glReadPixels(0, 0, glow_xsz, glow_ysz, GL_RGBA, GL_UNSIGNED_BYTE, glow_framebuf);
244 glViewport(0, 0, win_width, win_height);
245 }
247 glClearColor(0.05, 0.05, 0.05, 1);
248 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
250 draw_scene();
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);
258 post_glow();
259 }
260 }
262 if(get_led_state(0)) {
263 // continuously redraw until the left LED times out
264 draw_pending = true;
265 }
267 glXSwapBuffers(dpy, win);
268 assert(glGetError() == GL_NO_ERROR);
270 static long prev_msec;
271 long msec = get_msec();
272 long dt = msec - prev_msec;
274 if(dt < MIN_REDRAW_INTERVAL) {
275 wait_for(MIN_REDRAW_INTERVAL - dt);
276 }
277 prev_msec = get_msec();
278 }
280 static void draw_scene(int pass)
281 {
282 if(pass != GLOW_PASS) {
283 scn->render();
284 }
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;
295 float uoffs = DIGIT_USZ + DIGIT_USZ * digit;
297 disp_obj[i]->mtl.tex_offset[TEX_DIFFUSE] = Vector2(uoffs, 0);
298 disp_obj[i]->render();
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 }
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;
315 glPushAttrib(GL_ENABLE_BIT);
317 glBlendFunc(GL_ONE, GL_ONE);
318 glEnable(GL_BLEND);
319 glDisable(GL_CULL_FACE);
320 glDisable(GL_LIGHTING);
321 glDisable(GL_DEPTH_TEST);
323 glMatrixMode(GL_MODELVIEW);
324 glPushMatrix();
325 glLoadIdentity();
326 glMatrixMode(GL_PROJECTION);
327 glPushMatrix();
328 glLoadIdentity();
330 glEnable(GL_TEXTURE_2D);
331 glBindTexture(GL_TEXTURE_2D, glow_tex);
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();
345 glPopMatrix();
346 glMatrixMode(GL_MODELVIEW);
347 glPopMatrix();
349 glPopAttrib();
350 }
353 static void reshape(int x, int y)
354 {
355 glViewport(0, 0, x, y);
357 glMatrixMode(GL_PROJECTION);
358 glLoadIdentity();
359 gluPerspective(50.0, (float)x / (float)y, 1.0, 1000.0);
361 win_width = x;
362 win_height = y;
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);
369 delete [] glow_framebuf;
370 glow_framebuf = new unsigned char[glow_xsz * glow_ysz * 4];
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 }
379 static void keyb(int key, bool pressed)
380 {
381 if(pressed) {
382 switch(key) {
383 case 27:
384 exit(0);
385 }
386 }
387 }
389 static bool bnstate[32];
390 static int prev_x, prev_y;
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;
398 if(bn == 0 && pressed) {
399 // do picking
400 Ray ray = calc_pick_ray(x, win_height - y);
402 HitPoint minhit;
403 minhit.t = FLT_MAX;
404 int hit_found = -1;
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 }
414 if(hit_found != -1) {
415 switch(hit_found) {
416 case BN_TICKET:
417 issue_ticket();
418 break;
420 case BN_NEXT:
421 next_customer();
422 break;
423 }
424 draw_pending = true;
425 }
426 }
427 }
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;
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;
442 } else if(bnstate[2]) {
443 cam_dist += dy * 0.5;
444 if(cam_dist < 0.0) cam_dist = 0.0;
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 }
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;
462 glGetDoublev(GL_MODELVIEW_MATRIX, mv);
463 glGetDoublev(GL_PROJECTION_MATRIX, proj);
464 glGetIntegerv(GL_VIEWPORT, vp);
466 gluUnProject(x, y, 0, mv, proj, vp, &resx, &resy, &resz);
467 ray.origin = Vector3(resx, resy, resz);
469 gluUnProject(x, y, 1, mv, proj, vp, &resx, &resy, &resz);
470 ray.dir = normalize(Vector3(resx, resy, resz) - ray.origin);
472 return ray;
473 }
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 }
486 static Window create_window(const char *title, int xsz, int ysz)
487 {
488 int scr = DefaultScreen(dpy);
489 Window root = RootWindow(dpy, scr);
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 };
505 XVisualInfo *vis = glXChooseVisual(dpy, scr, glxattr);
506 if(!vis) {
507 fprintf(stderr, "failed to find a suitable visual\n");
508 return 0;
509 }
511 if(!(ctx = glXCreateContext(dpy, vis, 0, True))) {
512 fprintf(stderr, "failed to create OpenGL context\n");
513 XFree(vis);
514 return -1;
515 }
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;
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);
532 unsigned int evmask = StructureNotifyMask | VisibilityChangeMask | ExposureMask |
533 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
534 PointerMotionMask | LeaveWindowMask;
535 XSelectInput(dpy, win, evmask);
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);
541 XClassHint hint;
542 hint.res_name = hint.res_class = (char*)"equeue_win";
543 XSetClassHint(dpy, win, &hint);
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);
551 XMapWindow(dpy, win);
552 glXMakeCurrent(dpy, win, ctx);
554 return win;
555 }
557 static void process_events()
558 {
559 XEvent ev;
561 while(XPending(dpy)) {
562 XNextEvent(dpy, &ev);
563 switch(ev.type) {
564 case MapNotify:
565 win_mapped = true;
566 break;
568 case UnmapNotify:
569 win_mapped = false;
570 break;
572 case Expose:
573 if(win_mapped && ev.xexpose.count == 0) {
574 draw_pending = true;
575 }
576 break;
578 case MotionNotify:
579 motion(ev.xmotion.x, ev.xmotion.y);
580 break;
582 case ButtonPress:
583 mouse(ev.xbutton.button - 1, true, ev.xbutton.x, ev.xbutton.y);
584 break;
586 case ButtonRelease:
587 mouse(ev.xbutton.button - 1, false, ev.xbutton.x, ev.xbutton.y);
588 break;
590 case KeyPress:
591 {
592 KeySym sym = XLookupKeysym(&ev.xkey, 0);
593 keyb(translate_keysym(sym), true);
594 }
595 break;
597 case KeyRelease:
598 {
599 KeySym sym = XLookupKeysym(&ev.xkey, 0);
600 keyb(translate_keysym(sym), false);
601 }
602 break;
604 case ConfigureNotify:
605 {
606 int xsz = ev.xconfigure.width;
607 int ysz = ev.xconfigure.height;
609 if(xsz != win_width || ysz != win_height) {
610 win_width = xsz;
611 win_height = ysz;
612 reshape(xsz, ysz);
613 }
614 }
615 break;
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;
625 case LeaveNotify:
626 if(ev.xcrossing.mode == NotifyNormal) {
627 cam_theta = cam_phi = 0;
628 draw_pending = true;
629 }
630 break;
632 default:
633 break;
634 }
636 }
637 }
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 }
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;
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 }