2 MiniGLUT - minimal GLUT subset without dependencies
3 Copyright (C) 2020-2022 John Tsiombikas <nuclear@member.fsf.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
18 #if defined(unix) || defined(__unix__)
21 #include <X11/keysym.h>
22 #include <X11/cursorfont.h>
26 #ifndef GLX_SAMPLE_BUFFERS_ARB
27 #define GLX_SAMPLE_BUFFERS_ARB 100000
28 #define GLX_SAMPLES_ARB 100001
30 #ifndef GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB
31 #define GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20b2
35 static Window win, root;
38 static GLXContext ctx;
39 static Atom xa_wm_proto, xa_wm_del_win;
40 static Atom xa_net_wm_state, xa_net_wm_state_fullscr;
41 static Atom xa_motif_wm_hints;
42 static Atom xa_motion_event, xa_button_press_event, xa_button_release_event, xa_command_event;
43 static unsigned int evmask;
44 static Cursor blank_cursor;
46 static int have_netwm_fullscr(void);
53 static LRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam);
55 static HINSTANCE hinst;
62 /*#error unsupported platform*/
70 #pragma warning (disable: 4244 4305)
75 int rsize, gsize, bsize, asize;
83 static void cleanup(void);
84 static void create_window(const char *title);
85 static void get_window_pos(int *x, int *y);
86 static void get_window_size(int *w, int *h);
87 static void get_screen_size(int *scrw, int *scrh);
89 static long get_msec(void);
90 static void panic(const char *msg);
91 static void sys_exit(int status);
92 static int sys_write(int fd, const void *buf, int count);
95 static int init_x = -1, init_y, init_width = 256, init_height = 256;
96 static unsigned int init_mode;
98 static struct ctx_info ctx_info;
99 static int cur_cursor = GLUT_CURSOR_INHERIT;
100 static int ignore_key_repeat;
102 static glut_cb cb_display;
103 static glut_cb cb_idle;
104 static glut_cb_reshape cb_reshape;
105 static glut_cb_state cb_vis, cb_entry;
106 static glut_cb_keyb cb_keydown, cb_keyup;
107 static glut_cb_special cb_skeydown, cb_skeyup;
108 static glut_cb_mouse cb_mouse;
109 static glut_cb_motion cb_motion, cb_passive;
110 static glut_cb_sbmotion cb_sball_motion, cb_sball_rotate;
111 static glut_cb_sbbutton cb_sball_button;
113 static int fullscreen;
114 static int prev_win_x, prev_win_y, prev_win_width, prev_win_height;
116 static int win_width, win_height;
119 static int upd_pending;
122 void glutInit(int *argc, char **argv)
128 if(!(dpy = XOpenDisplay(0))) {
129 panic("Failed to connect to the X server\n");
131 scr = DefaultScreen(dpy);
132 root = RootWindow(dpy, scr);
133 xa_wm_proto = XInternAtom(dpy, "WM_PROTOCOLS", False);
134 xa_wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
135 xa_motif_wm_hints = XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
136 xa_net_wm_state_fullscr = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
137 if(have_netwm_fullscr()) {
138 xa_net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
141 xa_motion_event = XInternAtom(dpy, "MotionEvent", True);
142 xa_button_press_event = XInternAtom(dpy, "ButtonPressEvent", True);
143 xa_button_release_event = XInternAtom(dpy, "ButtonReleaseEvent", True);
144 xa_command_event = XInternAtom(dpy, "CommandEvent", True);
146 evmask = ExposureMask | StructureNotifyMask;
148 if((blankpix = XCreateBitmapFromData(dpy, root, (char*)&blankpix, 1, 1))) {
149 blank_cursor = XCreatePixmapCursor(dpy, blankpix, blankpix, &xcol, &xcol, 0, 0);
150 XFreePixmap(dpy, blankpix);
157 hinst = GetModuleHandle(0);
159 wc.cbSize = sizeof wc;
160 wc.hbrBackground = GetStockObject(BLACK_BRUSH);
161 wc.hCursor = LoadCursor(0, IDC_ARROW);
162 wc.hIcon = wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
163 wc.hInstance = hinst;
164 wc.lpfnWndProc = handle_message;
165 wc.lpszClassName = "MiniGLUT";
166 wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
167 if(!RegisterClassEx(&wc)) {
168 panic("Failed to register \"MiniGLUT\" window class\n");
172 get_screen_size(&init_x, &init_y);
179 void glutInitWindowPosition(int x, int y)
185 void glutInitWindowSize(int xsz, int ysz)
191 void glutInitDisplayMode(unsigned int mode)
196 void glutCreateWindow(const char *title)
198 create_window(title);
206 void glutMainLoop(void)
213 void glutPostRedisplay(void)
218 void glutIgnoreKeyRepeat(int ignore)
220 ignore_key_repeat = ignore;
223 #define UPD_EVMASK(x) \
230 if(win) XSelectInput(dpy, win, evmask); \
234 void glutIdleFunc(glut_cb func)
239 void glutDisplayFunc(glut_cb func)
244 void glutReshapeFunc(glut_cb_reshape func)
249 void glutVisibilityFunc(glut_cb_state func)
253 UPD_EVMASK(VisibilityChangeMask);
257 void glutEntryFunc(glut_cb_state func)
261 UPD_EVMASK(EnterWindowMask | LeaveWindowMask);
265 void glutKeyboardFunc(glut_cb_keyb func)
269 UPD_EVMASK(KeyPressMask);
273 void glutKeyboardUpFunc(glut_cb_keyb func)
277 UPD_EVMASK(KeyReleaseMask);
281 void glutSpecialFunc(glut_cb_special func)
285 UPD_EVMASK(KeyPressMask);
289 void glutSpecialUpFunc(glut_cb_special func)
293 UPD_EVMASK(KeyReleaseMask);
297 void glutMouseFunc(glut_cb_mouse func)
301 UPD_EVMASK(ButtonPressMask | ButtonReleaseMask);
305 void glutMotionFunc(glut_cb_motion func)
309 UPD_EVMASK(ButtonMotionMask);
313 void glutPassiveMotionFunc(glut_cb_motion func)
317 UPD_EVMASK(PointerMotionMask);
321 void glutSpaceballMotionFunc(glut_cb_sbmotion func)
323 cb_sball_motion = func;
326 void glutSpaceballRotateFunc(glut_cb_sbmotion func)
328 cb_sball_rotate = func;
331 void glutSpaceballButtonFunc(glut_cb_sbbutton func)
333 cb_sball_button = func;
336 int glutGet(unsigned int s)
341 get_window_pos(&x, &y);
344 get_window_pos(&x, &y);
346 case GLUT_WINDOW_WIDTH:
347 get_window_size(&x, &y);
349 case GLUT_WINDOW_HEIGHT:
350 get_window_size(&x, &y);
352 case GLUT_WINDOW_BUFFER_SIZE:
353 return ctx_info.rsize + ctx_info.gsize + ctx_info.bsize + ctx_info.asize;
354 case GLUT_WINDOW_STENCIL_SIZE:
355 return ctx_info.ssize;
356 case GLUT_WINDOW_DEPTH_SIZE:
357 return ctx_info.zsize;
358 case GLUT_WINDOW_RED_SIZE:
359 return ctx_info.rsize;
360 case GLUT_WINDOW_GREEN_SIZE:
361 return ctx_info.gsize;
362 case GLUT_WINDOW_BLUE_SIZE:
363 return ctx_info.bsize;
364 case GLUT_WINDOW_ALPHA_SIZE:
365 return ctx_info.asize;
366 case GLUT_WINDOW_DOUBLEBUFFER:
367 return ctx_info.dblbuf;
368 case GLUT_WINDOW_RGBA:
370 case GLUT_WINDOW_NUM_SAMPLES:
371 return ctx_info.samples;
372 case GLUT_WINDOW_STEREO:
373 return ctx_info.stereo;
374 case GLUT_WINDOW_SRGB:
375 return ctx_info.srgb;
376 case GLUT_WINDOW_CURSOR:
378 case GLUT_WINDOW_COLORMAP_SIZE:
380 case GLUT_SCREEN_WIDTH:
381 get_screen_size(&x, &y);
383 case GLUT_SCREEN_HEIGHT:
384 get_screen_size(&x, &y);
386 case GLUT_INIT_DISPLAY_MODE:
388 case GLUT_INIT_WINDOW_X:
390 case GLUT_INIT_WINDOW_Y:
392 case GLUT_INIT_WINDOW_WIDTH:
394 case GLUT_INIT_WINDOW_HEIGHT:
396 case GLUT_ELAPSED_TIME:
404 int glutGetModifiers(void)
409 static int is_space(int c)
411 return c == ' ' || c == '\t' || c == '\v' || c == '\n' || c == '\r';
414 static const char *skip_space(const char *s)
416 while(*s && is_space(*s)) s++;
420 int glutExtensionSupported(char *ext)
422 const char *str, *eptr;
424 if(!(str = (const char*)glGetString(GL_EXTENSIONS))) {
429 str = skip_space(str);
430 eptr = skip_space(ext);
431 while(*str && !is_space(*str) && *eptr && *str == *eptr) {
435 if((!*str || is_space(*str)) && !*eptr) {
438 while(*str && !is_space(*str)) str++;
445 /* --------------- UNIX/X11 implementation ----------------- */
448 SPNAV_EVENT_ANY, /* used by spnav_remove_events() */
450 SPNAV_EVENT_BUTTON /* includes both press and release */
453 struct spnav_event_motion {
461 struct spnav_event_button {
469 struct spnav_event_motion motion;
470 struct spnav_event_button button;
474 static void handle_event(XEvent *ev);
476 static int spnav_window(Window win);
477 static int spnav_event(const XEvent *xev, union spnav_event *event);
478 static int spnav_remove_events(int type);
481 void glutMainLoopEvent(void)
486 panic("display callback not set");
489 if(!upd_pending && !cb_idle) {
490 XNextEvent(dpy, &ev);
494 while(XPending(dpy)) {
495 XNextEvent(dpy, &ev);
504 if(upd_pending && mapped) {
515 static void cleanup(void)
519 glXMakeCurrent(dpy, 0, 0);
520 XDestroyWindow(dpy, win);
524 static KeySym translate_keysym(KeySym sym)
545 static void handle_event(XEvent *ev)
548 union spnav_event sev;
557 case ConfigureNotify:
558 if(cb_reshape && (ev->xconfigure.width != win_width || ev->xconfigure.height != win_height)) {
559 win_width = ev->xconfigure.width;
560 win_height = ev->xconfigure.height;
561 cb_reshape(ev->xconfigure.width, ev->xconfigure.height);
566 if(ev->xclient.message_type == xa_wm_proto) {
567 if(ev->xclient.data.l[0] == xa_wm_del_win) {
571 if(spnav_event(ev, &sev)) {
573 case SPNAV_EVENT_MOTION:
574 if(cb_sball_motion) {
575 cb_sball_motion(sev.motion.x, sev.motion.y, sev.motion.z);
577 if(cb_sball_rotate) {
578 cb_sball_rotate(sev.motion.rx, sev.motion.ry, sev.motion.rz);
580 spnav_remove_events(SPNAV_EVENT_MOTION);
583 case SPNAV_EVENT_BUTTON:
584 if(cb_sball_button) {
585 cb_sball_button(sev.button.bnum + 1, sev.button.press ? GLUT_DOWN : GLUT_UP);
602 if(ignore_key_repeat && XEventsQueued(dpy, QueuedAfterReading)) {
604 XPeekEvent(dpy, &next);
606 if(next.type == KeyPress && next.xkey.keycode == ev->xkey.keycode &&
607 next.xkey.time == ev->xkey.time) {
608 /* this is a key-repeat event, ignore the release and consume
609 * the following press
611 XNextEvent(dpy, &next);
616 modstate = ev->xkey.state & (ShiftMask | ControlMask | Mod1Mask);
617 if(!(sym = XLookupKeysym(&ev->xkey, 0))) {
620 sym = translate_keysym(sym);
622 if(ev->type == KeyPress) {
623 if(cb_keydown) cb_keydown((unsigned char)sym, ev->xkey.x, ev->xkey.y);
625 if(cb_keyup) cb_keyup((unsigned char)sym, ev->xkey.x, ev->xkey.y);
628 if(ev->type == KeyPress) {
629 if(cb_skeydown) cb_skeydown(sym, ev->xkey.x, ev->xkey.y);
631 if(cb_skeyup) cb_skeyup(sym, ev->xkey.x, ev->xkey.y);
638 modstate = ev->xbutton.state & (ShiftMask | ControlMask | Mod1Mask);
640 int bn = ev->xbutton.button - Button1;
641 cb_mouse(bn, ev->type == ButtonPress ? GLUT_DOWN : GLUT_UP,
642 ev->xbutton.x, ev->xbutton.y);
647 if(ev->xmotion.state & (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask)) {
648 if(cb_motion) cb_motion(ev->xmotion.x, ev->xmotion.y);
650 if(cb_passive) cb_passive(ev->xmotion.x, ev->xmotion.y);
654 case VisibilityNotify:
656 cb_vis(ev->xvisibility.state == VisibilityFullyObscured ? GLUT_NOT_VISIBLE : GLUT_VISIBLE);
660 if(cb_entry) cb_entry(GLUT_ENTERED);
663 if(cb_entry) cb_entry(GLUT_LEFT);
668 void glutSwapBuffers(void)
670 glXSwapBuffers(dpy, win);
674 * set_fullscreen_mwm removes the decorations with MotifWM hints, and then it
675 * needs to resize the window to make it fullscreen. The way it does this is by
676 * querying the size of the root window (see get_screen_size), which in the
677 * case of multi-monitor setups will be the combined size of all monitors.
678 * This is problematic; the way to solve it is to use the XRandR extension, or
679 * the Xinerama extension, to figure out the dimensions of the correct video
680 * output, which would add potentially two extension support libraries to our
682 * Moreover, any X installation modern enough to support XR&R will almost
683 * certainly be running a window manager supporting the EHWM
684 * _NET_WM_STATE_FULLSCREEN method (set_fullscreen_ewmh), which does not rely
685 * on manual resizing, and is used in preference if available, making this
686 * whole endeavor pointless.
687 * So I'll just leave it with set_fullscreen_mwm covering the entire
688 * multi-monitor area for now.
693 unsigned long functions;
694 unsigned long decorations;
696 unsigned long status;
699 #define MWM_HINTS_DECORATIONS 2
700 #define MWM_DECOR_ALL 1
702 static void set_fullscreen_mwm(int fs)
704 struct mwm_hints hints;
705 int scr_width, scr_height;
708 get_window_pos(&prev_win_x, &prev_win_y);
709 get_window_size(&prev_win_width, &prev_win_height);
710 get_screen_size(&scr_width, &scr_height);
712 hints.decorations = 0;
713 hints.flags = MWM_HINTS_DECORATIONS;
714 XChangeProperty(dpy, win, xa_motif_wm_hints, xa_motif_wm_hints, 32,
715 PropModeReplace, (unsigned char*)&hints, 5);
717 XMoveResizeWindow(dpy, win, 0, 0, scr_width, scr_height);
719 XDeleteProperty(dpy, win, xa_motif_wm_hints);
720 XMoveResizeWindow(dpy, win, prev_win_x, prev_win_y, prev_win_width, prev_win_height);
724 static int have_netwm_fullscr(void)
728 unsigned long i, count, rem;
730 Atom xa_net_supported = XInternAtom(dpy, "_NET_SUPPORTED", False);
733 XGetWindowProperty(dpy, root, xa_net_supported, offs, 8, False, AnyPropertyType,
734 &type, &fmt, &count, &rem, (unsigned char**)&prop);
736 for(i=0; i<count; i++) {
737 if(prop[i] == xa_net_wm_state_fullscr) {
749 static void set_fullscreen_ewmh(int fs)
751 XClientMessageEvent msg = {0};
753 msg.type = ClientMessage;
755 msg.message_type = xa_net_wm_state; /* _NET_WM_STATE */
757 msg.data.l[0] = fs ? 1 : 0;
758 msg.data.l[1] = xa_net_wm_state_fullscr; /* _NET_WM_STATE_FULLSCREEN */
760 msg.data.l[3] = 1; /* source regular application */
761 XSendEvent(dpy, root, False, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent*)&msg);
764 static void set_fullscreen(int fs)
766 if(fullscreen == fs) return;
768 if(xa_net_wm_state && xa_net_wm_state_fullscr) {
769 set_fullscreen_ewmh(fs);
771 } else if(xa_motif_wm_hints) {
772 set_fullscreen_mwm(fs);
777 void glutPositionWindow(int x, int y)
780 XMoveWindow(dpy, win, x, y);
783 void glutReshapeWindow(int xsz, int ysz)
786 XResizeWindow(dpy, win, xsz, ysz);
789 void glutFullScreen(void)
794 void glutSetWindowTitle(const char *title)
797 if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
800 XSetWMName(dpy, win, &tprop);
804 void glutSetIconTitle(const char *title)
807 if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
810 XSetWMIconName(dpy, win, &tprop);
814 void glutSetCursor(int cidx)
819 case GLUT_CURSOR_LEFT_ARROW:
820 cur = XCreateFontCursor(dpy, XC_left_ptr);
822 case GLUT_CURSOR_INHERIT:
824 case GLUT_CURSOR_NONE:
831 XDefineCursor(dpy, win, cur);
835 void glutWarpPointer(int x, int y)
837 XWarpPointer(dpy, None, win, 0, 0, 0, 0, x, y);
840 void glutSetColor(int idx, float r, float g, float b)
844 if(idx >= 0 && idx < cmap_size) {
846 color.red = (unsigned short)(r * 65535.0f);
847 color.green = (unsigned short)(g * 65535.0f);
848 color.blue = (unsigned short)(b * 65535.0f);
849 color.flags = DoRed | DoGreen | DoBlue;
850 XStoreColor(dpy, cmap, &color);
854 float glutGetColor(int idx, int comp)
858 if(idx < 0 || idx >= cmap_size) {
863 XQueryColor(dpy, cmap, &color);
866 return color.red / 65535.0f;
868 return color.green / 65535.0f;
870 return color.blue / 65535.0f;
877 void glutSetKeyRepeat(int repmode)
886 static XVisualInfo *choose_visual(unsigned int mode)
893 if(mode & GLUT_DOUBLE) {
894 *aptr++ = GLX_DOUBLEBUFFER;
897 if(mode & GLUT_INDEX) {
898 *aptr++ = GLX_BUFFER_SIZE;
902 *aptr++ = GLX_RED_SIZE; *aptr++ = 1;
903 *aptr++ = GLX_GREEN_SIZE; *aptr++ = 1;
904 *aptr++ = GLX_BLUE_SIZE; *aptr++ = 1;
906 if(mode & GLUT_ALPHA) {
907 *aptr++ = GLX_ALPHA_SIZE;
910 if(mode & GLUT_DEPTH) {
911 *aptr++ = GLX_DEPTH_SIZE;
914 if(mode & GLUT_STENCIL) {
915 *aptr++ = GLX_STENCIL_SIZE;
918 if(mode & GLUT_ACCUM) {
919 *aptr++ = GLX_ACCUM_RED_SIZE; *aptr++ = 1;
920 *aptr++ = GLX_ACCUM_GREEN_SIZE; *aptr++ = 1;
921 *aptr++ = GLX_ACCUM_BLUE_SIZE; *aptr++ = 1;
923 if(mode & GLUT_STEREO) {
924 *aptr++ = GLX_STEREO;
926 if(mode & GLUT_SRGB) {
927 *aptr++ = GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB;
929 if(mode & GLUT_MULTISAMPLE) {
930 *aptr++ = GLX_SAMPLE_BUFFERS_ARB;
932 *aptr++ = GLX_SAMPLES_ARB;
939 return glXChooseVisual(dpy, scr, attr);
941 while(!(vi = glXChooseVisual(dpy, scr, attr)) && *samples) {
950 static void create_window(const char *title)
952 XSetWindowAttributes xattr = {0};
954 unsigned int xattr_mask;
955 unsigned int mode = init_mode;
957 if(!(vi = choose_visual(mode))) {
959 if(!(vi = choose_visual(mode))) {
960 panic("Failed to find compatible visual\n");
964 if(!(ctx = glXCreateContext(dpy, vi, 0, True))) {
966 panic("Failed to create OpenGL context\n");
969 glXGetConfig(dpy, vi, GLX_RED_SIZE, &ctx_info.rsize);
970 glXGetConfig(dpy, vi, GLX_GREEN_SIZE, &ctx_info.gsize);
971 glXGetConfig(dpy, vi, GLX_BLUE_SIZE, &ctx_info.bsize);
972 glXGetConfig(dpy, vi, GLX_ALPHA_SIZE, &ctx_info.asize);
973 glXGetConfig(dpy, vi, GLX_DEPTH_SIZE, &ctx_info.zsize);
974 glXGetConfig(dpy, vi, GLX_STENCIL_SIZE, &ctx_info.ssize);
975 glXGetConfig(dpy, vi, GLX_DOUBLEBUFFER, &ctx_info.dblbuf);
976 glXGetConfig(dpy, vi, GLX_STEREO, &ctx_info.stereo);
977 glXGetConfig(dpy, vi, GLX_SAMPLES_ARB, &ctx_info.samples);
978 glXGetConfig(dpy, vi, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &ctx_info.srgb);
980 if(!(cmap = XCreateColormap(dpy, root, vi->visual, mode & GLUT_INDEX ? AllocAll : AllocNone))) {
982 glXDestroyContext(dpy, ctx);
983 panic("Failed to create colormap\n");
985 cmap_size = GLUT_INDEX ? vi->colormap_size : 0;
987 xattr.background_pixel = BlackPixel(dpy, scr);
988 xattr.colormap = cmap;
989 xattr_mask = CWBackPixel | CWColormap | CWBackPixmap | CWBorderPixel;
990 if(!(win = XCreateWindow(dpy, root, init_x, init_y, init_width, init_height, 0,
991 vi->depth, InputOutput, vi->visual, xattr_mask, &xattr))) {
993 glXDestroyContext(dpy, ctx);
994 XFreeColormap(dpy, cmap);
995 panic("Failed to create window\n");
999 XSelectInput(dpy, win, evmask);
1003 glutSetWindowTitle(title);
1004 glutSetIconTitle(title);
1005 XSetWMProtocols(dpy, win, &xa_wm_del_win, 1);
1006 XMapWindow(dpy, win);
1008 glXMakeCurrent(dpy, win, ctx);
1011 static void get_window_pos(int *x, int *y)
1014 XTranslateCoordinates(dpy, win, root, 0, 0, x, y, &child);
1017 static void get_window_size(int *w, int *h)
1019 XWindowAttributes wattr;
1020 XGetWindowAttributes(dpy, win, &wattr);
1025 static void get_screen_size(int *scrw, int *scrh)
1027 XWindowAttributes wattr;
1028 XGetWindowAttributes(dpy, root, &wattr);
1029 *scrw = wattr.width;
1030 *scrh = wattr.height;
1036 CMD_APP_WINDOW = 27695,
1040 static Window get_daemon_window(Display *dpy);
1041 static int catch_badwin(Display *dpy, XErrorEvent *err);
1043 #define SPNAV_INITIALIZED (xa_motion_event)
1045 static int spnav_window(Window win)
1047 int (*prev_xerr_handler)(Display*, XErrorEvent*);
1051 if(!SPNAV_INITIALIZED) {
1055 if(!(daemon_win = get_daemon_window(dpy))) {
1059 prev_xerr_handler = XSetErrorHandler(catch_badwin);
1061 xev.type = ClientMessage;
1062 xev.xclient.send_event = False;
1063 xev.xclient.display = dpy;
1064 xev.xclient.window = win;
1065 xev.xclient.message_type = xa_command_event;
1066 xev.xclient.format = 16;
1067 xev.xclient.data.s[0] = ((unsigned int)win & 0xffff0000) >> 16;
1068 xev.xclient.data.s[1] = (unsigned int)win & 0xffff;
1069 xev.xclient.data.s[2] = CMD_APP_WINDOW;
1071 XSendEvent(dpy, daemon_win, False, 0, &xev);
1074 XSetErrorHandler(prev_xerr_handler);
1078 static Bool match_events(Display *dpy, XEvent *xev, char *arg)
1080 int evtype = *(int*)arg;
1082 if(xev->type != ClientMessage) {
1086 if(xev->xclient.message_type == xa_motion_event) {
1087 return !evtype || evtype == SPNAV_EVENT_MOTION ? True : False;
1089 if(xev->xclient.message_type == xa_button_press_event ||
1090 xev->xclient.message_type == xa_button_release_event) {
1091 return !evtype || evtype == SPNAV_EVENT_BUTTON ? True : False;
1096 static int spnav_remove_events(int type)
1100 while(XCheckIfEvent(dpy, &xev, match_events, (char*)&type)) {
1106 static int spnav_event(const XEvent *xev, union spnav_event *event)
1111 xmsg_type = xev->xclient.message_type;
1113 if(xmsg_type != xa_motion_event && xmsg_type != xa_button_press_event &&
1114 xmsg_type != xa_button_release_event) {
1118 if(xmsg_type == xa_motion_event) {
1119 event->type = SPNAV_EVENT_MOTION;
1120 event->motion.data = &event->motion.x;
1122 for(i=0; i<6; i++) {
1123 event->motion.data[i] = xev->xclient.data.s[i + 2];
1125 event->motion.period = xev->xclient.data.s[8];
1127 event->type = SPNAV_EVENT_BUTTON;
1128 event->button.press = xmsg_type == xa_button_press_event ? 1 : 0;
1129 event->button.bnum = xev->xclient.data.s[2];
1134 static int mglut_strcmp(const char *s1, const char *s2)
1136 while(*s1 && *s1 == *s2) {
1143 static Window get_daemon_window(Display *dpy)
1146 XTextProperty wname;
1149 unsigned long nitems, bytes_after;
1150 unsigned char *prop;
1152 XGetWindowProperty(dpy, root, xa_command_event, 0, 1, False, AnyPropertyType,
1153 &type, &fmt, &nitems, &bytes_after, &prop);
1158 win = *(Window*)prop;
1162 if(!XGetWMName(dpy, win, &wname) || mglut_strcmp("Magellan Window", (char*)wname.value) != 0) {
1170 static int catch_badwin(Display *dpy, XErrorEvent *err)
1177 #endif /* BUILD_X11 */
1180 /* --------------- windows implementation ----------------- */
1182 static int reshape_pending;
1184 static void update_modkeys(void);
1185 static int translate_vkey(int vkey);
1186 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam);
1188 #ifdef MINIGLUT_WINMAIN
1189 int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprev, char *cmdline, int showcmd)
1192 char *argv[] = { "miniglut.exe", 0 };
1193 return main(argc, argv);
1197 void glutMainLoopEvent(void)
1202 panic("display callback not set");
1205 if(reshape_pending && cb_reshape) {
1206 reshape_pending = 0;
1207 get_window_size(&win_width, &win_height);
1208 cb_reshape(win_width, win_height);
1211 if(!upd_pending && !cb_idle) {
1212 GetMessage(&msg, 0, 0, 0);
1213 TranslateMessage(&msg);
1214 DispatchMessage(&msg);
1217 while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
1218 TranslateMessage(&msg);
1219 DispatchMessage(&msg);
1227 if(upd_pending && mapped) {
1233 static void cleanup(void)
1236 wglMakeCurrent(dc, 0);
1237 wglDeleteContext(ctx);
1238 UnregisterClass("MiniGLUT", hinst);
1242 void glutSwapBuffers(void)
1247 void glutPositionWindow(int x, int y)
1250 unsigned int flags = SWP_SHOWWINDOW;
1253 rect.left = prev_win_x;
1254 rect.top = prev_win_y;
1255 rect.right = rect.left + prev_win_width;
1256 rect.bottom = rect.top + prev_win_height;
1257 SetWindowLong(win, GWL_STYLE, WS_OVERLAPPEDWINDOW);
1259 flags |= SWP_FRAMECHANGED;
1261 GetWindowRect(win, &rect);
1263 SetWindowPos(win, HWND_NOTOPMOST, x, y, rect.right - rect.left, rect.bottom - rect.top, flags);
1266 static void calc_win_rect(RECT *rect, int x, int y, int w, int h)
1270 rect->right = x + w;
1271 rect->bottom = y + h;
1272 AdjustWindowRect(rect, WS_OVERLAPPEDWINDOW, 0);
1275 void glutReshapeWindow(int xsz, int ysz)
1278 unsigned int flags = SWP_SHOWWINDOW;
1281 calc_win_rect(&rect, prev_win_x, prev_win_y, xsz, ysz);
1282 SetWindowLong(win, GWL_STYLE, WS_OVERLAPPEDWINDOW);
1284 flags |= SWP_FRAMECHANGED;
1286 GetWindowRect(win, &rect);
1287 calc_win_rect(&rect, rect.left, rect.top, xsz, ysz);
1290 xsz = rect.right - rect.left;
1291 ysz = rect.bottom - rect.top;
1292 SetWindowPos(win, HWND_NOTOPMOST, rect.left, rect.top, xsz, ysz, flags);
1295 void glutFullScreen(void)
1298 int scr_width, scr_height;
1300 if(fullscreen) return;
1302 GetWindowRect(win, &rect);
1303 prev_win_x = rect.left;
1304 prev_win_y = rect.top;
1305 prev_win_width = rect.right - rect.left;
1306 prev_win_height = rect.bottom - rect.top;
1308 get_screen_size(&scr_width, &scr_height);
1310 SetWindowLong(win, GWL_STYLE, 0);
1311 SetWindowPos(win, HWND_TOPMOST, 0, 0, scr_width, scr_height, SWP_SHOWWINDOW);
1316 void glutSetWindowTitle(const char *title)
1318 SetWindowText(win, title);
1321 void glutSetIconTitle(const char *title)
1325 void glutSetCursor(int cidx)
1328 case GLUT_CURSOR_NONE:
1331 case GLUT_CURSOR_INHERIT:
1332 case GLUT_CURSOR_LEFT_ARROW:
1334 SetCursor(LoadCursor(0, IDC_ARROW));
1339 void glutWarpPointer(int x, int y)
1345 ClientToScreen(win, &pt);
1346 SetCursorPos(pt.x, pt.y);
1349 void glutSetColor(int idx, float r, float g, float b)
1353 if(idx < 0 || idx >= 256 || !cmap) {
1357 col.peRed = (int)(r * 255.0f);
1358 col.peGreen = (int)(g * 255.0f);
1359 col.peBlue = (int)(b * 255.0f);
1360 col.peFlags = PC_NOCOLLAPSE;
1362 SetPaletteEntries(cmap, idx, 1, &col);
1365 UnrealizeObject(cmap);
1366 SelectPalette(dc, cmap, 0);
1371 float glutGetColor(int idx, int comp)
1375 if(idx < 0 || idx >= 256 || !cmap) {
1379 if(!GetPaletteEntries(cmap, idx, 1, &col)) {
1385 return col.peRed / 255.0f;
1387 return col.peGreen / 255.0f;
1389 return col.peBlue / 255.0f;
1396 void glutSetKeyRepeat(int repmode)
1400 #define WGL_DRAW_TO_WINDOW 0x2001
1401 #define WGL_ACCELERATION 0x2003
1402 #define WGL_SUPPORT_OPENGL 0x2010
1403 #define WGL_DOUBLE_BUFFER 0x2011
1404 #define WGL_STEREO 0x2012
1405 #define WGL_PIXEL_TYPE 0x2013
1406 #define WGL_COLOR_BITS 0x2014
1407 #define WGL_RED_BITS 0x2015
1408 #define WGL_GREEN_BITS 0x2017
1409 #define WGL_BLUE_BITS 0x2019
1410 #define WGL_ALPHA_BITS 0x201b
1411 #define WGL_ACCUM_BITS 0x201d
1412 #define WGL_DEPTH_BITS 0x2022
1413 #define WGL_STENCIL_BITS 0x2023
1414 #define WGL_FULL_ACCELERATION 0x2027
1416 #define WGL_TYPE_RGBA 0x202b
1417 #define WGL_TYPE_COLORINDEX 0x202c
1419 #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9
1420 #define WGL_SAMPLE_BUFFERS_ARB 0x2041
1421 #define WGL_SAMPLES_ARB 0x2042
1423 static PROC wglChoosePixelFormat;
1424 static PROC wglGetPixelFormatAttribiv;
1426 #define ATTR(a, v) \
1427 do { *aptr++ = (a); *aptr++ = (v); } while(0)
1429 static unsigned int choose_pixfmt(unsigned int mode)
1431 unsigned int num_pixfmt, pixfmt = 0;
1432 int attr[32] = { WGL_DRAW_TO_WINDOW, 1, WGL_SUPPORT_OPENGL, 1,
1433 WGL_ACCELERATION, WGL_FULL_ACCELERATION };
1434 float fattr[2] = {0, 0};
1436 int *aptr = attr + 6;
1439 if(mode & GLUT_DOUBLE) {
1440 ATTR(WGL_DOUBLE_BUFFER, 1);
1443 ATTR(WGL_PIXEL_TYPE, mode & GLUT_INDEX ? WGL_TYPE_COLORINDEX : WGL_TYPE_RGBA);
1444 ATTR(WGL_COLOR_BITS, mode & GLUT_INDEX ? 8 : 24);
1445 if(mode & GLUT_ALPHA) {
1446 ATTR(WGL_ALPHA_BITS, 4);
1448 if(mode & GLUT_DEPTH) {
1449 ATTR(WGL_DEPTH_BITS, 16);
1451 if(mode & GLUT_STENCIL) {
1452 ATTR(WGL_STENCIL_BITS, 1);
1454 if(mode & GLUT_ACCUM) {
1455 ATTR(WGL_ACCUM_BITS, 1);
1457 if(mode & GLUT_STEREO) {
1458 ATTR(WGL_STEREO, 1);
1460 if(mode & GLUT_SRGB) {
1461 ATTR(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, 1);
1463 if(mode & GLUT_MULTISAMPLE) {
1464 ATTR(WGL_SAMPLE_BUFFERS_ARB, 1);
1465 *aptr++ = WGL_SAMPLES_ARB;
1471 while((!wglChoosePixelFormat(dc, attr, fattr, 1, &pixfmt, &num_pixfmt) || !num_pixfmt) && samples && *samples) {
1480 static PIXELFORMATDESCRIPTOR pfd;
1481 static PIXELFORMATDESCRIPTOR tmppfd = {
1482 sizeof tmppfd, 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
1483 PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 8, 0,
1484 PFD_MAIN_PLANE, 0, 0, 0, 0
1486 #define TMPCLASS "TempMiniGLUT"
1488 #define GETATTR(attr, vptr) \
1491 wglGetPixelFormatAttribiv(dc, pixfmt, 0, 1, &gattr, vptr); \
1494 static int create_window_wglext(const char *title, int width, int height)
1496 WNDCLASSEX wc = {0};
1502 /* create a temporary window and GL context, just to query and retrieve
1503 * the wglChoosePixelFormatEXT function
1505 wc.cbSize = sizeof wc;
1506 wc.hbrBackground = GetStockObject(BLACK_BRUSH);
1507 wc.hCursor = LoadCursor(0, IDC_ARROW);
1508 wc.hIcon = wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
1509 wc.hInstance = hinst;
1510 wc.lpfnWndProc = DefWindowProc;
1511 wc.lpszClassName = TMPCLASS;
1512 wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
1513 if(!RegisterClassEx(&wc)) {
1516 if(!(tmpwin = CreateWindow(TMPCLASS, "temp", WS_OVERLAPPEDWINDOW, 0, 0,
1517 width, height, 0, 0, hinst, 0))) {
1520 tmpdc = GetDC(tmpwin);
1522 if(!(pixfmt = ChoosePixelFormat(tmpdc, &tmppfd)) ||
1523 !SetPixelFormat(tmpdc, pixfmt, &tmppfd) ||
1524 !(tmpctx = wglCreateContext(tmpdc))) {
1527 wglMakeCurrent(tmpdc, tmpctx);
1529 if(!(wglChoosePixelFormat = wglGetProcAddress("wglChoosePixelFormatARB"))) {
1530 if(!(wglChoosePixelFormat = wglGetProcAddress("wglChoosePixelFormatEXT"))) {
1533 if(!(wglGetPixelFormatAttribiv = wglGetProcAddress("wglGetPixelFormatAttribivEXT"))) {
1537 if(!(wglGetPixelFormatAttribiv = wglGetProcAddress("wglGetPixelFormatAttribivARB"))) {
1541 wglMakeCurrent(0, 0);
1542 wglDeleteContext(tmpctx);
1543 DestroyWindow(tmpwin);
1544 UnregisterClass(TMPCLASS, hinst);
1546 /* create the real window and context */
1547 if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW, init_x,
1548 init_y, width, height, 0, 0, hinst, 0))) {
1549 panic("Failed to create window\n");
1553 if(!(pixfmt = choose_pixfmt(init_mode))) {
1554 panic("Failed to find suitable pixel format\n");
1556 if(!SetPixelFormat(dc, pixfmt, &pfd)) {
1557 panic("Failed to set the selected pixel format\n");
1559 if(!(ctx = wglCreateContext(dc))) {
1560 panic("Failed to create the OpenGL context\n");
1562 wglMakeCurrent(dc, ctx);
1564 GETATTR(WGL_RED_BITS, &ctx_info.rsize);
1565 GETATTR(WGL_GREEN_BITS, &ctx_info.gsize);
1566 GETATTR(WGL_BLUE_BITS, &ctx_info.bsize);
1567 GETATTR(WGL_ALPHA_BITS, &ctx_info.asize);
1568 GETATTR(WGL_DEPTH_BITS, &ctx_info.zsize);
1569 GETATTR(WGL_STENCIL_BITS, &ctx_info.ssize);
1570 GETATTR(WGL_DOUBLE_BUFFER, &ctx_info.dblbuf);
1571 GETATTR(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, &ctx_info.srgb);
1572 GETATTR(WGL_SAMPLES_ARB, &ctx_info.samples);
1578 wglMakeCurrent(0, 0);
1579 wglDeleteContext(tmpctx);
1582 DestroyWindow(tmpwin);
1584 UnregisterClass(TMPCLASS, hinst);
1588 static void create_window(const char *title)
1591 int i, pixfmt, width, height;
1592 char palbuf[sizeof(LOGPALETTE) + 255 * sizeof(PALETTEENTRY)];
1595 calc_win_rect(&rect, init_x, init_y, init_width, init_height);
1596 width = rect.right - rect.left;
1597 height = rect.bottom - rect.top;
1599 memset(&pfd, 0, sizeof pfd);
1600 pfd.nSize = sizeof pfd;
1602 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
1603 if(init_mode & GLUT_STEREO) {
1604 pfd.dwFlags |= PFD_STEREO;
1606 if(init_mode & GLUT_INDEX) {
1607 pfd.iPixelType = PFD_TYPE_COLORINDEX;
1610 pfd.iPixelType = PFD_TYPE_RGBA;
1611 pfd.cColorBits = 24;
1613 if(init_mode & GLUT_ALPHA) {
1616 if(init_mode & GLUT_ACCUM) {
1617 pfd.cAccumBits = 24;
1619 if(init_mode & GLUT_DEPTH) {
1620 pfd.cDepthBits = 24;
1622 if(init_mode & GLUT_STENCIL) {
1623 pfd.cStencilBits = 8;
1625 pfd.iLayerType = PFD_MAIN_PLANE;
1627 if(init_mode & (GLUT_SRGB | GLUT_MULTISAMPLE)) {
1628 if(create_window_wglext(title, width, height) != -1) {
1633 /* if we don't need sRGB or multisample, or if the wglChoosePixelFormat method
1634 * failed, just use the old-style ChoosePixelFormat method instead
1636 if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW,
1637 rect.left, rect.top, width, height, 0, 0, hinst, 0))) {
1638 panic("Failed to create window\n");
1642 if(!(pixfmt = ChoosePixelFormat(dc, &pfd))) {
1643 panic("Failed to find suitable pixel format\n");
1645 if(!SetPixelFormat(dc, pixfmt, &pfd)) {
1646 panic("Failed to set the selected pixel format\n");
1648 if(!(ctx = wglCreateContext(dc))) {
1649 panic("Failed to create the OpenGL context\n");
1651 wglMakeCurrent(dc, ctx);
1653 DescribePixelFormat(dc, pixfmt, sizeof pfd, &pfd);
1654 ctx_info.rsize = pfd.cRedBits;
1655 ctx_info.gsize = pfd.cGreenBits;
1656 ctx_info.bsize = pfd.cBlueBits;
1657 ctx_info.asize = pfd.cAlphaBits;
1658 ctx_info.zsize = pfd.cDepthBits;
1659 ctx_info.ssize = pfd.cStencilBits;
1660 ctx_info.dblbuf = pfd.dwFlags & PFD_DOUBLEBUFFER ? 1 : 0;
1661 ctx_info.samples = 0;
1666 SetForegroundWindow(win);
1669 if(init_mode & GLUT_INDEX) {
1670 logpal = (LOGPALETTE*)palbuf;
1672 GetSystemPaletteEntries(dc, 0, 256, logpal->palPalEntry);
1674 logpal->palVersion = 0x300;
1675 logpal->palNumEntries = 256;
1677 if(!(cmap = CreatePalette(logpal))) {
1678 panic("Failed to create palette in indexed mode");
1680 SelectPalette(dc, cmap, 0);
1685 if(GetDeviceCaps(dc, BITSPIXEL) * GetDeviceCaps(dc, PLANES) <= 8) {
1686 /* for RGB mode in 8bpp displays we also need to set up a palette
1687 * with RGB 332 colors
1689 logpal = (LOGPALETTE*)palbuf;
1691 logpal->palVersion = 0x300;
1692 logpal->palNumEntries = 256;
1694 for(i=0; i<256; i++) {
1696 int g = (i >> 3) & 7;
1697 int b = (i >> 5) & 3;
1699 logpal->palPalEntry[i].peRed = (r << 5) | (r << 2) | (r >> 1);
1700 logpal->palPalEntry[i].peGreen = (g << 5) | (g << 2) | (g >> 1);
1701 logpal->palPalEntry[i].peBlue = (b << 6) | (b << 4) | (b << 2) | b;
1702 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
1705 if((cmap = CreatePalette(logpal))) {
1706 SelectPalette(dc, cmap, 0);
1714 reshape_pending = 1;
1717 static LRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
1719 static int mouse_x, mouse_y;
1724 if(win) DestroyWindow(win);
1735 ValidateRect(win, 0);
1739 x = lparam & 0xffff;
1741 if(x != win_width && y != win_height) {
1745 reshape_pending = 0;
1746 cb_reshape(win_width, win_height);
1753 if(cb_vis) cb_vis(mapped ? GLUT_VISIBLE : GLUT_NOT_VISIBLE);
1759 key = translate_vkey(wparam);
1762 cb_keydown((unsigned char)key, mouse_x, mouse_y);
1766 cb_skeydown(key, mouse_x, mouse_y);
1774 key = translate_vkey(wparam);
1777 cb_keyup((unsigned char)key, mouse_x, mouse_y);
1781 cb_skeyup(key, mouse_x, mouse_y);
1786 case WM_LBUTTONDOWN:
1787 handle_mbutton(0, 1, wparam, lparam);
1789 case WM_MBUTTONDOWN:
1790 handle_mbutton(1, 1, wparam, lparam);
1792 case WM_RBUTTONDOWN:
1793 handle_mbutton(2, 1, wparam, lparam);
1796 handle_mbutton(0, 0, wparam, lparam);
1799 handle_mbutton(1, 0, wparam, lparam);
1802 handle_mbutton(2, 0, wparam, lparam);
1806 if(wparam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1807 if(cb_motion) cb_motion(lparam & 0xffff, lparam >> 16);
1809 if(cb_passive) cb_passive(lparam & 0xffff, lparam >> 16);
1815 if(wparam == SC_KEYMENU || wparam == SC_SCREENSAVE || wparam == SC_MONITORPOWER) {
1819 return DefWindowProc(win, msg, wparam, lparam);
1825 static void update_modkeys(void)
1827 if(GetKeyState(VK_SHIFT) & 0x8000) {
1828 modstate |= GLUT_ACTIVE_SHIFT;
1830 modstate &= ~GLUT_ACTIVE_SHIFT;
1832 if(GetKeyState(VK_CONTROL) & 0x8000) {
1833 modstate |= GLUT_ACTIVE_CTRL;
1835 modstate &= ~GLUT_ACTIVE_CTRL;
1837 if(GetKeyState(VK_MENU) & 0x8000) {
1838 modstate |= GLUT_ACTIVE_ALT;
1840 modstate &= ~GLUT_ACTIVE_ALT;
1845 #define VK_OEM_1 0xba
1846 #define VK_OEM_2 0xbf
1847 #define VK_OEM_3 0xc0
1848 #define VK_OEM_4 0xdb
1849 #define VK_OEM_5 0xdc
1850 #define VK_OEM_6 0xdd
1851 #define VK_OEM_7 0xde
1854 static int translate_vkey(int vkey)
1857 case VK_PRIOR: return GLUT_KEY_PAGE_UP;
1858 case VK_NEXT: return GLUT_KEY_PAGE_DOWN;
1859 case VK_END: return GLUT_KEY_END;
1860 case VK_HOME: return GLUT_KEY_HOME;
1861 case VK_LEFT: return GLUT_KEY_LEFT;
1862 case VK_UP: return GLUT_KEY_UP;
1863 case VK_RIGHT: return GLUT_KEY_RIGHT;
1864 case VK_DOWN: return GLUT_KEY_DOWN;
1865 case VK_OEM_1: return ';';
1866 case VK_OEM_2: return '/';
1867 case VK_OEM_3: return '`';
1868 case VK_OEM_4: return '[';
1869 case VK_OEM_5: return '\\';
1870 case VK_OEM_6: return ']';
1871 case VK_OEM_7: return '\'';
1876 if(vkey >= 'A' && vkey <= 'Z') {
1878 } else if(vkey >= VK_F1 && vkey <= VK_F12) {
1879 vkey -= VK_F1 + GLUT_KEY_F1;
1885 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam)
1892 x = lparam & 0xffff;
1894 cb_mouse(bn, st ? GLUT_DOWN : GLUT_UP, x, y);
1898 static void get_window_pos(int *x, int *y)
1901 GetWindowRect(win, &rect);
1906 static void get_window_size(int *w, int *h)
1909 GetClientRect(win, &rect);
1910 *w = rect.right - rect.left;
1911 *h = rect.bottom - rect.top;
1914 static void get_screen_size(int *scrw, int *scrh)
1916 *scrw = GetSystemMetrics(SM_CXSCREEN);
1917 *scrh = GetSystemMetrics(SM_CYSCREEN);
1919 #endif /* BUILD_WIN32 */
1921 #if defined(unix) || defined(__unix__) || defined(__APPLE__)
1922 #include <sys/time.h>
1924 #ifdef MINIGLUT_USE_LIBC
1925 #define sys_gettimeofday(tv, tz) gettimeofday(tv, tz)
1927 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz);
1930 static long get_msec(void)
1932 static struct timeval tv0;
1935 sys_gettimeofday(&tv, 0);
1936 if(tv0.tv_sec == 0 && tv0.tv_usec == 0) {
1940 return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
1944 static long get_msec(void)
1949 #ifdef MINIGLUT_NO_WINMM
1950 tm = GetTickCount();
1962 static void panic(const char *msg)
1964 const char *end = msg;
1966 sys_write(2, msg, end - msg);
1971 #ifdef MINIGLUT_USE_LIBC
1979 static void sys_exit(int status)
1984 static int sys_write(int fd, const void *buf, int count)
1986 return write(fd, buf, count);
1989 #else /* !MINIGLUT_USE_LIBC */
1993 static void sys_exit(int status)
1997 :: "a"(60), "D"(status));
1999 static int sys_write(int fd, const void *buf, int count)
2005 : "a"(1), "D"(fd), "S"(buf), "d"(count));
2008 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
2014 : "a"(96), "D"(tv), "S"(tz));
2017 #endif /* __x86_64__ */
2019 static void sys_exit(int status)
2023 :: "a"(1), "b"(status));
2025 static int sys_write(int fd, const void *buf, int count)
2031 : "a"(4), "b"(fd), "c"(buf), "d"(count));
2034 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
2040 : "a"(78), "b"(tv), "c"(tz));
2043 #endif /* __i386__ */
2044 #endif /* __linux__ */
2047 static void sys_exit(int status)
2049 ExitProcess(status);
2051 static int sys_write(int fd, const void *buf, int count)
2053 unsigned long wrsz = 0;
2055 HANDLE out = GetStdHandle(fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
2056 if(!WriteFile(out, buf, count, &wrsz, 0)) {
2062 #endif /* !MINIGLUT_USE_LIBC */
2065 /* ----------------- primitives ------------------ */
2066 #ifdef MINIGLUT_USE_LIBC
2070 void mglut_sincos(float angle, float *sptr, float *cptr)
2076 float mglut_atan(float x)
2081 #else /* !MINIGLUT_USE_LIBC */
2084 void mglut_sincos(float angle, float *sptr, float *cptr)
2091 : "=m"(*sptr), "=m"(*cptr)
2096 float mglut_atan(float x)
2112 void mglut_sincos(float angle, float *sptr, float *cptr)
2125 float mglut_atan(float x)
2139 #pragma aux mglut_sincos = \
2141 "fstp dword ptr [edx]" \
2142 "fstp dword ptr [eax]" \
2143 parm[8087][eax][edx] \
2146 #pragma aux mglut_atan = \
2152 #endif /* __WATCOMC__ */
2154 #endif /* !MINIGLUT_USE_LIBC */
2156 #define PI 3.1415926536f
2158 void glutSolidSphere(float rad, int slices, int stacks)
2161 float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
2162 float du = 1.0f / (float)slices;
2163 float dv = 1.0f / (float)stacks;
2166 for(i=0; i<stacks; i++) {
2168 for(j=0; j<slices; j++) {
2170 for(k=0; k<4; k++) {
2171 gray = k ^ (k >> 1);
2172 s = gray & 1 ? u + du : u;
2173 t = gray & 2 ? v + dv : v;
2174 theta = s * PI * 2.0f;
2176 mglut_sincos(theta, &sintheta, &costheta);
2177 mglut_sincos(phi, &sinphi, &cosphi);
2178 x = sintheta * sinphi;
2179 y = costheta * sinphi;
2184 glNormal3f(x, y, z);
2185 glVertex3f(x * rad, y * rad, z * rad);
2192 void glutWireSphere(float rad, int slices, int stacks)
2194 glPushAttrib(GL_POLYGON_BIT);
2195 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2196 glutSolidSphere(rad, slices, stacks);
2200 void glutSolidCube(float sz)
2202 int i, j, idx, gray, flip, rotx;
2203 float vpos[3], norm[3];
2204 float rad = sz * 0.5f;
2207 for(i=0; i<6; i++) {
2210 idx = (~i & 2) - rotx;
2211 norm[0] = norm[1] = norm[2] = 0.0f;
2212 norm[idx] = flip ^ ((i >> 1) & 1) ? -1 : 1;
2214 vpos[idx] = norm[idx] * rad;
2215 for(j=0; j<4; j++) {
2216 gray = j ^ (j >> 1);
2217 vpos[i & 2] = (gray ^ flip) & 1 ? rad : -rad;
2218 vpos[rotx + 1] = (gray ^ (rotx << 1)) & 2 ? rad : -rad;
2219 glTexCoord2f(gray & 1, gray >> 1);
2226 void glutWireCube(float sz)
2228 glPushAttrib(GL_POLYGON_BIT);
2229 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2234 static void draw_cylinder(float rbot, float rtop, float height, int slices, int stacks)
2237 float x, y, z, s, t, u, v, theta, phi, sintheta, costheta, sinphi, cosphi, rad;
2238 float du = 1.0f / (float)slices;
2239 float dv = 1.0f / (float)stacks;
2242 phi = mglut_atan((rad < 0 ? -rad : rad) / height);
2243 mglut_sincos(phi, &sinphi, &cosphi);
2246 for(i=0; i<stacks; i++) {
2248 for(j=0; j<slices; j++) {
2250 for(k=0; k<4; k++) {
2251 gray = k ^ (k >> 1);
2252 s = gray & 2 ? u + du : u;
2253 t = gray & 1 ? v + dv : v;
2254 rad = rbot + (rtop - rbot) * t;
2255 theta = s * PI * 2.0f;
2256 mglut_sincos(theta, &sintheta, &costheta);
2258 x = sintheta * cosphi;
2259 y = costheta * cosphi;
2264 glNormal3f(x, y, z);
2265 glVertex3f(sintheta * rad, costheta * rad, t * height);
2272 void glutSolidCone(float base, float height, int slices, int stacks)
2274 draw_cylinder(base, 0, height, slices, stacks);
2277 void glutWireCone(float base, float height, int slices, int stacks)
2279 glPushAttrib(GL_POLYGON_BIT);
2280 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2281 glutSolidCone(base, height, slices, stacks);
2285 void glutSolidCylinder(float rad, float height, int slices, int stacks)
2287 draw_cylinder(rad, rad, height, slices, stacks);
2290 void glutWireCylinder(float rad, float height, int slices, int stacks)
2292 glPushAttrib(GL_POLYGON_BIT);
2293 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2294 glutSolidCylinder(rad, height, slices, stacks);
2298 void glutSolidTorus(float inner_rad, float outer_rad, int sides, int rings)
2301 float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
2302 float du = 1.0f / (float)rings;
2303 float dv = 1.0f / (float)sides;
2306 for(i=0; i<rings; i++) {
2308 for(j=0; j<sides; j++) {
2310 for(k=0; k<4; k++) {
2311 gray = k ^ (k >> 1);
2312 s = gray & 1 ? u + du : u;
2313 t = gray & 2 ? v + dv : v;
2314 theta = s * PI * 2.0f;
2315 phi = t * PI * 2.0f;
2316 mglut_sincos(theta, &sintheta, &costheta);
2317 mglut_sincos(phi, &sinphi, &cosphi);
2318 x = sintheta * sinphi;
2319 y = costheta * sinphi;
2324 glNormal3f(x, y, z);
2326 x = x * inner_rad + sintheta * outer_rad;
2327 y = y * inner_rad + costheta * outer_rad;
2329 glVertex3f(x, y, z);
2336 void glutWireTorus(float inner_rad, float outer_rad, int sides, int rings)
2338 glPushAttrib(GL_POLYGON_BIT);
2339 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2340 glutSolidTorus(inner_rad, outer_rad, sides, rings);
2344 #define NUM_TEAPOT_INDICES (sizeof teapot_index / sizeof *teapot_index)
2345 #define NUM_TEAPOT_VERTS (sizeof teapot_verts / sizeof *teapot_verts)
2347 #define NUM_TEAPOT_PATCHES (NUM_TEAPOT_INDICES / 16)
2349 #define PATCH_SUBDIV 7
2351 static float teapot_part_flip[] = {
2352 1, 1, 1, 1, /* rim flip */
2353 1, 1, 1, 1, /* body1 flip */
2354 1, 1, 1, 1, /* body2 flip */
2355 1, 1, 1, 1, /* lid patch 1 flip */
2356 1, 1, 1, 1, /* lid patch 2 flip */
2357 1, -1, /* handle 1 flip */
2358 1, -1, /* handle 2 flip */
2359 1, -1, /* spout 1 flip */
2360 1, -1, /* spout 2 flip */
2361 1, 1, 1, 1 /* bottom flip */
2364 static float teapot_part_rot[] = {
2365 0, 90, 180, 270, /* rim rotations */
2366 0, 90, 180, 270, /* body patch 1 rotations */
2367 0, 90, 180, 270, /* body patch 2 rotations */
2368 0, 90, 180, 270, /* lid patch 1 rotations */
2369 0, 90, 180, 270, /* lid patch 2 rotations */
2370 0, 0, /* handle 1 rotations */
2371 0, 0, /* handle 2 rotations */
2372 0, 0, /* spout 1 rotations */
2373 0, 0, /* spout 2 rotations */
2374 0, 90, 180, 270 /* bottom rotations */
2378 static int teapot_index[] = {
2380 102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
2381 102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
2382 102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
2383 102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
2385 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
2386 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
2387 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
2388 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
2390 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
2391 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
2392 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
2393 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
2395 96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,
2396 96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,
2397 96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,
2398 96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,
2400 0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
2401 0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
2402 0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
2403 0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
2405 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
2406 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
2408 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 28, 65, 66, 67,
2409 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 28, 65, 66, 67,
2411 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
2412 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
2414 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
2415 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
2417 118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37,
2418 118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37,
2419 118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37,
2420 118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37
2424 static float teapot_verts[][3] = {
2425 { 0.2000, 0.0000, 2.70000 }, { 0.2000, -0.1120, 2.70000 },
2426 { 0.1120, -0.2000, 2.70000 }, { 0.0000, -0.2000, 2.70000 },
2427 { 1.3375, 0.0000, 2.53125 }, { 1.3375, -0.7490, 2.53125 },
2428 { 0.7490, -1.3375, 2.53125 }, { 0.0000, -1.3375, 2.53125 },
2429 { 1.4375, 0.0000, 2.53125 }, { 1.4375, -0.8050, 2.53125 },
2430 { 0.8050, -1.4375, 2.53125 }, { 0.0000, -1.4375, 2.53125 },
2431 { 1.5000, 0.0000, 2.40000 }, { 1.5000, -0.8400, 2.40000 },
2432 { 0.8400, -1.5000, 2.40000 }, { 0.0000, -1.5000, 2.40000 },
2433 { 1.7500, 0.0000, 1.87500 }, { 1.7500, -0.9800, 1.87500 },
2434 { 0.9800, -1.7500, 1.87500 }, { 0.0000, -1.7500, 1.87500 },
2435 { 2.0000, 0.0000, 1.35000 }, { 2.0000, -1.1200, 1.35000 },
2436 { 1.1200, -2.0000, 1.35000 }, { 0.0000, -2.0000, 1.35000 },
2437 { 2.0000, 0.0000, 0.90000 }, { 2.0000, -1.1200, 0.90000 },
2438 { 1.1200, -2.0000, 0.90000 }, { 0.0000, -2.0000, 0.90000 },
2439 { -2.0000, 0.0000, 0.90000 }, { 2.0000, 0.0000, 0.45000 },
2440 { 2.0000, -1.1200, 0.45000 }, { 1.1200, -2.0000, 0.45000 },
2441 { 0.0000, -2.0000, 0.45000 }, { 1.5000, 0.0000, 0.22500 },
2442 { 1.5000, -0.8400, 0.22500 }, { 0.8400, -1.5000, 0.22500 },
2443 { 0.0000, -1.5000, 0.22500 }, { 1.5000, 0.0000, 0.15000 },
2444 { 1.5000, -0.8400, 0.15000 }, { 0.8400, -1.5000, 0.15000 },
2445 { 0.0000, -1.5000, 0.15000 }, { -1.6000, 0.0000, 2.02500 },
2446 { -1.6000, -0.3000, 2.02500 }, { -1.5000, -0.3000, 2.25000 },
2447 { -1.5000, 0.0000, 2.25000 }, { -2.3000, 0.0000, 2.02500 },
2448 { -2.3000, -0.3000, 2.02500 }, { -2.5000, -0.3000, 2.25000 },
2449 { -2.5000, 0.0000, 2.25000 }, { -2.7000, 0.0000, 2.02500 },
2450 { -2.7000, -0.3000, 2.02500 }, { -3.0000, -0.3000, 2.25000 },
2451 { -3.0000, 0.0000, 2.25000 }, { -2.7000, 0.0000, 1.80000 },
2452 { -2.7000, -0.3000, 1.80000 }, { -3.0000, -0.3000, 1.80000 },
2453 { -3.0000, 0.0000, 1.80000 }, { -2.7000, 0.0000, 1.57500 },
2454 { -2.7000, -0.3000, 1.57500 }, { -3.0000, -0.3000, 1.35000 },
2455 { -3.0000, 0.0000, 1.35000 }, { -2.5000, 0.0000, 1.12500 },
2456 { -2.5000, -0.3000, 1.12500 }, { -2.6500, -0.3000, 0.93750 },
2457 { -2.6500, 0.0000, 0.93750 }, { -2.0000, -0.3000, 0.90000 },
2458 { -1.9000, -0.3000, 0.60000 }, { -1.9000, 0.0000, 0.60000 },
2459 { 1.7000, 0.0000, 1.42500 }, { 1.7000, -0.6600, 1.42500 },
2460 { 1.7000, -0.6600, 0.60000 }, { 1.7000, 0.0000, 0.60000 },
2461 { 2.6000, 0.0000, 1.42500 }, { 2.6000, -0.6600, 1.42500 },
2462 { 3.1000, -0.6600, 0.82500 }, { 3.1000, 0.0000, 0.82500 },
2463 { 2.3000, 0.0000, 2.10000 }, { 2.3000, -0.2500, 2.10000 },
2464 { 2.4000, -0.2500, 2.02500 }, { 2.4000, 0.0000, 2.02500 },
2465 { 2.7000, 0.0000, 2.40000 }, { 2.7000, -0.2500, 2.40000 },
2466 { 3.3000, -0.2500, 2.40000 }, { 3.3000, 0.0000, 2.40000 },
2467 { 2.8000, 0.0000, 2.47500 }, { 2.8000, -0.2500, 2.47500 },
2468 { 3.5250, -0.2500, 2.49375 }, { 3.5250, 0.0000, 2.49375 },
2469 { 2.9000, 0.0000, 2.47500 }, { 2.9000, -0.1500, 2.47500 },
2470 { 3.4500, -0.1500, 2.51250 }, { 3.4500, 0.0000, 2.51250 },
2471 { 2.8000, 0.0000, 2.40000 }, { 2.8000, -0.1500, 2.40000 },
2472 { 3.2000, -0.1500, 2.40000 }, { 3.2000, 0.0000, 2.40000 },
2473 { 0.0000, 0.0000, 3.15000 }, { 0.8000, 0.0000, 3.15000 },
2474 { 0.8000, -0.4500, 3.15000 }, { 0.4500, -0.8000, 3.15000 },
2475 { 0.0000, -0.8000, 3.15000 }, { 0.0000, 0.0000, 2.85000 },
2476 { 1.4000, 0.0000, 2.40000 }, { 1.4000, -0.7840, 2.40000 },
2477 { 0.7840, -1.4000, 2.40000 }, { 0.0000, -1.4000, 2.40000 },
2478 { 0.4000, 0.0000, 2.55000 }, { 0.4000, -0.2240, 2.55000 },
2479 { 0.2240, -0.4000, 2.55000 }, { 0.0000, -0.4000, 2.55000 },
2480 { 1.3000, 0.0000, 2.55000 }, { 1.3000, -0.7280, 2.55000 },
2481 { 0.7280, -1.3000, 2.55000 }, { 0.0000, -1.3000, 2.55000 },
2482 { 1.3000, 0.0000, 2.40000 }, { 1.3000, -0.7280, 2.40000 },
2483 { 0.7280, -1.3000, 2.40000 }, { 0.0000, -1.3000, 2.40000 },
2484 { 0.0000, 0.0000, 0.00000 }, { 1.4250, -0.7980, 0.00000 },
2485 { 1.5000, 0.0000, 0.07500 }, { 1.4250, 0.0000, 0.00000 },
2486 { 0.7980, -1.4250, 0.00000 }, { 0.0000, -1.5000, 0.07500 },
2487 { 0.0000, -1.4250, 0.00000 }, { 1.5000, -0.8400, 0.07500 },
2488 { 0.8400, -1.5000, 0.07500 }
2491 static void draw_patch(int *index, int flip, float scale);
2492 static float bernstein(int i, float x);
2494 void glutSolidTeapot(float size)
2500 for(i=0; i<NUM_TEAPOT_PATCHES; i++) {
2501 float flip = teapot_part_flip[i];
2502 float rot = teapot_part_rot[i];
2504 glMatrixMode(GL_MODELVIEW);
2506 glTranslatef(0, -3.15 * size * 0.5, 0);
2507 glRotatef(rot, 0, 1, 0);
2508 glScalef(1, 1, flip);
2509 glRotatef(-90, 1, 0, 0);
2511 draw_patch(teapot_index + i * 16, flip < 0.0 ? 1 : 0, size);
2517 void glutWireTeapot(float size)
2519 glPushAttrib(GL_POLYGON_BIT);
2520 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2521 glutSolidTeapot(size);
2526 static void bezier_patch(float *res, float *cp, float u, float v)
2530 res[0] = res[1] = res[2] = 0.0f;
2532 for(j=0; j<4; j++) {
2533 for(i=0; i<4; i++) {
2534 float bu = bernstein(i, u);
2535 float bv = bernstein(j, v);
2537 res[0] += cp[0] * bu * bv;
2538 res[1] += cp[1] * bu * bv;
2539 res[2] += cp[2] * bu * bv;
2546 static float rsqrt(float x)
2548 float xhalf = x * 0.5f;
2550 i = 0x5f3759df - (i >> 1);
2552 x = x * (1.5f - xhalf * x * x);
2557 #define CROSS(res, a, b) \
2559 (res)[0] = (a)[1] * (b)[2] - (a)[2] * (b)[1]; \
2560 (res)[1] = (a)[2] * (b)[0] - (a)[0] * (b)[2]; \
2561 (res)[2] = (a)[0] * (b)[1] - (a)[1] * (b)[0]; \
2564 #define NORMALIZE(v) \
2566 float s = rsqrt((v)[0] * (v)[0] + (v)[1] * (v)[1] + (v)[2] * (v)[2]); \
2574 static void bezier_patch_norm(float *res, float *cp, float u, float v)
2576 float tang[3], bitan[3], tmp[3];
2578 bezier_patch(tang, cp, u + DT, v);
2579 bezier_patch(tmp, cp, u - DT, v);
2584 bezier_patch(bitan, cp, u, v + DT);
2585 bezier_patch(tmp, cp, u, v - DT);
2590 CROSS(res, tang, bitan);
2596 static float bernstein(int i, float x)
2598 float invx = 1.0f - x;
2602 return invx * invx * invx;
2604 return 3.0f * x * invx * invx;
2606 return 3.0f * x * x * invx;
2615 static void draw_patch(int *index, int flip, float scale)
2617 static const float uoffs[2][4] = {{0, 0, 1, 1}, {1, 1, 0, 0}};
2618 static const float voffs[4] = {0, 1, 1, 0};
2624 float du = 1.0 / PATCH_SUBDIV;
2625 float dv = 1.0 / PATCH_SUBDIV;
2627 /* collect control points */
2628 for(i=0; i<16; i++) {
2629 cp[i * 3] = teapot_verts[index[i]][0];
2630 cp[i * 3 + 1] = teapot_verts[index[i]][1];
2631 cp[i * 3 + 2] = teapot_verts[index[i]][2];
2638 for(i=0; i<PATCH_SUBDIV; i++) {
2640 for(j=0; j<PATCH_SUBDIV; j++) {
2642 for(k=0; k<4; k++) {
2643 bezier_patch(pt, cp, u + uoffs[flip][k] * du, v + voffs[k] * dv);
2645 /* top/bottom normal hack */
2649 } else if(pt[2] < 0.00001) {
2653 bezier_patch_norm(n, cp, u + uoffs[flip][k] * du, v + voffs[k] * dv);
2658 glVertex3f(pt[0] * scale, pt[1] * scale, pt[2] * scale);