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/>.
25 #include <sys/select.h>
40 void post_redisplay();
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);
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;
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;
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)
92 if(proc_args(argc, argv) == -1) {
100 int xfd = ConnectionNumber(dpy);
102 // run once through pending events before going into the select loop
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)) {
119 if(FD_ISSET(fakefd, &rd)) {
124 draw_pending = false;
131 void post_redisplay()
139 if((fakefd = start_dev(fake_devpath)) == -1) {
144 if(!(dpy = XOpenDisplay(0))) {
145 fprintf(stderr, "failed to connect to the X server!\n");
149 if(!(win = create_window("equeue device emulator", 512, 512))) {
156 if(!scn->load("data/device.obj")) {
157 fprintf(stderr, "failed to load device 3D model\n");
161 for(int i=0; i<NUM_BUTTONS; i++) {
162 button_obj[i] = scn->get_object(button_names[i]);
164 fprintf(stderr, "invalid 3D model\n");
167 BSphere &bs = button_obj[i]->get_mesh()->get_bounds();
168 bs.set_radius(bs.get_radius() * 1.5);
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");
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");
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);
202 glClearColor(0.1, 0.1, 0.1, 1);
207 static void cleanup()
216 XDestroyWindow(dpy, win);
221 #define DIGIT_USZ (1.0 / 11.0)
222 #define MIN_REDRAW_INTERVAL (1000 / 40) /* 40fps */
224 static void display()
226 glMatrixMode(GL_MODELVIEW);
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);
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);
247 glClearColor(0.05, 0.05, 0.05, 1);
248 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
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);
262 if(get_led_state(0)) {
263 // continuously redraw until the left LED times out
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);
277 prev_msec = get_msec();
280 static void draw_scene(int pass)
282 if(pass != GLOW_PASS) {
286 // shift the textures and modify the materials to make the display match our state
287 for(int i=0; i<2; i++) {
289 int digit = get_display_number();
290 for(int j=0; j<i; j++) {
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();
301 if(get_led_state(i)) {
302 led_obj[i]->mtl.emissive = led_on_emissive;
304 led_obj[i]->mtl.emissive = Vector3(0, 0, 0);
306 led_obj[i]->render();
310 static void post_glow(void)
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);
319 glDisable(GL_CULL_FACE);
320 glDisable(GL_LIGHTING);
321 glDisable(GL_DEPTH_TEST);
323 glMatrixMode(GL_MODELVIEW);
326 glMatrixMode(GL_PROJECTION);
330 glEnable(GL_TEXTURE_2D);
331 glBindTexture(GL_TEXTURE_2D, glow_tex);
334 glColor4f(1, 1, 1, 1);
337 glTexCoord2f(max_s, 0);
339 glTexCoord2f(max_s, max_t);
341 glTexCoord2f(0, max_t);
346 glMatrixMode(GL_MODELVIEW);
353 static void reshape(int x, int y)
355 glViewport(0, 0, x, y);
357 glMatrixMode(GL_PROJECTION);
359 gluPerspective(50.0, (float)x / (float)y, 1.0, 1000.0);
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);
379 static void keyb(int key, bool pressed)
389 static bool bnstate[32];
390 static int prev_x, prev_y;
392 static void mouse(int bn, bool pressed, int x, int y)
394 bnstate[bn] = pressed;
398 if(bn == 0 && pressed) {
400 Ray ray = calc_pick_ray(x, win_height - y);
406 for(int i=0; i<NUM_BUTTONS; i++) {
408 if(button_obj[i]->get_mesh()->get_bounds().intersect(ray, &hit) && hit.t < minhit.t) {
414 if(hit_found != -1) {
429 static void motion(int x, int y)
437 cam_theta += dx * 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;
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;
455 static Ray calc_pick_ray(int x, int y)
457 double mv[16], proj[16];
459 double resx, resy, resz;
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);
475 static int next_pow2(int x)
486 static Window create_window(const char *title, int xsz, int ysz)
488 int scr = DefaultScreen(dpy);
489 Window root = RootWindow(dpy, scr);
498 #if defined(GLX_VERSION_1_4) || defined(GLX_ARB_multisample)
499 GLX_SAMPLE_BUFFERS_ARB, 1,
505 XVisualInfo *vis = glXChooseVisual(dpy, scr, glxattr);
507 fprintf(stderr, "failed to find a suitable visual\n");
511 if(!(ctx = glXCreateContext(dpy, vis, 0, True))) {
512 fprintf(stderr, "failed to create OpenGL context\n");
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);
525 fprintf(stderr, "failed to create window\n");
526 glXDestroyContext(dpy, ctx);
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);
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);
557 static void process_events()
561 while(XPending(dpy)) {
562 XNextEvent(dpy, &ev);
573 if(win_mapped && ev.xexpose.count == 0) {
579 motion(ev.xmotion.x, ev.xmotion.y);
583 mouse(ev.xbutton.button - 1, true, ev.xbutton.x, ev.xbutton.y);
587 mouse(ev.xbutton.button - 1, false, ev.xbutton.x, ev.xbutton.y);
592 KeySym sym = XLookupKeysym(&ev.xkey, 0);
593 keyb(translate_keysym(sym), true);
599 KeySym sym = XLookupKeysym(&ev.xkey, 0);
600 keyb(translate_keysym(sym), false);
604 case ConfigureNotify:
606 int xsz = ev.xconfigure.width;
607 int ysz = ev.xconfigure.height;
609 if(xsz != win_width || ysz != win_height) {
618 if(ev.xclient.message_type == xa_wm_prot) {
619 if((Atom)ev.xclient.data.l[0] == xa_wm_del_win) {
626 if(ev.xcrossing.mode == NotifyNormal) {
627 cam_theta = cam_phi = 0;
639 static int translate_keysym(KeySym sym)
658 static int proc_args(int argc, char **argv)
660 for(int i=1; i<argc; i++) {
661 if(argv[i][0] == '-') {
662 fprintf(stderr, "unexpected option: %s\n", argv[i]);
667 fprintf(stderr, "unexpected argument: %s\n", argv[i]);
670 fake_devpath = argv[i];
674 fprintf(stderr, "no device path specified, running standalone\n");