eqemu

view src/main.cc @ 11:2b559dc24c7b

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