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;
39 static GLXContext ctx;
40 static Atom xa_wm_proto, xa_wm_del_win;
41 static Atom xa_net_wm_state, xa_net_wm_state_fullscr;
42 static Atom xa_motif_wm_hints;
43 static Atom xa_motion_event, xa_button_press_event, xa_button_release_event, xa_command_event;
44 static unsigned int evmask;
45 static Cursor blank_cursor;
47 static int have_netwm_fullscr(void);
54 static LRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam);
56 static HINSTANCE hinst;
64 #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 warn(const char *msg);
92 static void sys_exit(int status);
93 static int sys_write(int fd, const void *buf, int count);
96 static int init_x = -1, init_y, init_width = 256, init_height = 256;
97 static unsigned int init_mode;
99 static struct ctx_info ctx_info;
100 static int cur_cursor = GLUT_CURSOR_INHERIT;
101 static int ignore_key_repeat;
103 static glut_cb cb_display;
104 static glut_cb cb_idle;
105 static glut_cb_reshape cb_reshape;
106 static glut_cb_state cb_vis, cb_entry;
107 static glut_cb_keyb cb_keydown, cb_keyup;
108 static glut_cb_special cb_skeydown, cb_skeyup;
109 static glut_cb_mouse cb_mouse;
110 static glut_cb_motion cb_motion, cb_passive;
111 static glut_cb_sbmotion cb_sball_motion, cb_sball_rotate;
112 static glut_cb_sbbutton cb_sball_button;
114 static int fullscreen;
115 static int prev_win_x, prev_win_y, prev_win_width, prev_win_height;
117 static int win_width, win_height;
120 static int upd_pending;
123 void glutInit(int *argc, char **argv)
129 if(!(dpy = XOpenDisplay(0))) {
130 panic("Failed to connect to the X server\n");
132 scr = DefaultScreen(dpy);
133 root = RootWindow(dpy, scr);
134 xa_wm_proto = XInternAtom(dpy, "WM_PROTOCOLS", False);
135 xa_wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
136 xa_motif_wm_hints = XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
137 xa_net_wm_state_fullscr = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
138 if(have_netwm_fullscr()) {
139 xa_net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
142 xa_motion_event = XInternAtom(dpy, "MotionEvent", True);
143 xa_button_press_event = XInternAtom(dpy, "ButtonPressEvent", True);
144 xa_button_release_event = XInternAtom(dpy, "ButtonReleaseEvent", True);
145 xa_command_event = XInternAtom(dpy, "CommandEvent", True);
147 evmask = ExposureMask | StructureNotifyMask;
149 if((blankpix = XCreateBitmapFromData(dpy, root, (char*)&blankpix, 1, 1))) {
150 blank_cursor = XCreatePixmapCursor(dpy, blankpix, blankpix, &xcol, &xcol, 0, 0);
151 XFreePixmap(dpy, blankpix);
158 hinst = GetModuleHandle(0);
160 wc.cbSize = sizeof wc;
161 wc.hbrBackground = GetStockObject(BLACK_BRUSH);
162 wc.hCursor = LoadCursor(0, IDC_ARROW);
163 wc.hIcon = wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
164 wc.hInstance = hinst;
165 wc.lpfnWndProc = handle_message;
166 wc.lpszClassName = "MiniGLUT";
167 wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
168 if(!RegisterClassEx(&wc)) {
169 panic("Failed to register \"MiniGLUT\" window class\n");
173 get_screen_size(&init_x, &init_y);
180 void glutInitWindowPosition(int x, int y)
186 void glutInitWindowSize(int xsz, int ysz)
192 void glutInitDisplayMode(unsigned int mode)
197 void glutCreateWindow(const char *title)
199 create_window(title);
207 void glutMainLoop(void)
214 void glutPostRedisplay(void)
219 void glutIgnoreKeyRepeat(int ignore)
221 ignore_key_repeat = ignore;
224 #define UPD_EVMASK(x) \
231 if(win) XSelectInput(dpy, win, evmask); \
235 void glutIdleFunc(glut_cb func)
240 void glutDisplayFunc(glut_cb func)
245 void glutReshapeFunc(glut_cb_reshape func)
250 void glutVisibilityFunc(glut_cb_state func)
254 UPD_EVMASK(VisibilityChangeMask);
258 void glutEntryFunc(glut_cb_state func)
262 UPD_EVMASK(EnterWindowMask | LeaveWindowMask);
266 void glutKeyboardFunc(glut_cb_keyb func)
270 UPD_EVMASK(KeyPressMask);
274 void glutKeyboardUpFunc(glut_cb_keyb func)
278 UPD_EVMASK(KeyReleaseMask);
282 void glutSpecialFunc(glut_cb_special func)
286 UPD_EVMASK(KeyPressMask);
290 void glutSpecialUpFunc(glut_cb_special func)
294 UPD_EVMASK(KeyReleaseMask);
298 void glutMouseFunc(glut_cb_mouse func)
302 UPD_EVMASK(ButtonPressMask | ButtonReleaseMask);
306 void glutMotionFunc(glut_cb_motion func)
310 UPD_EVMASK(ButtonMotionMask);
314 void glutPassiveMotionFunc(glut_cb_motion func)
318 UPD_EVMASK(PointerMotionMask);
322 void glutSpaceballMotionFunc(glut_cb_sbmotion func)
324 cb_sball_motion = func;
327 void glutSpaceballRotateFunc(glut_cb_sbmotion func)
329 cb_sball_rotate = func;
332 void glutSpaceballButtonFunc(glut_cb_sbbutton func)
334 cb_sball_button = func;
337 int glutGet(unsigned int s)
342 get_window_pos(&x, &y);
345 get_window_pos(&x, &y);
347 case GLUT_WINDOW_WIDTH:
348 get_window_size(&x, &y);
350 case GLUT_WINDOW_HEIGHT:
351 get_window_size(&x, &y);
353 case GLUT_WINDOW_BUFFER_SIZE:
354 return ctx_info.rsize + ctx_info.gsize + ctx_info.bsize + ctx_info.asize;
355 case GLUT_WINDOW_STENCIL_SIZE:
356 return ctx_info.ssize;
357 case GLUT_WINDOW_DEPTH_SIZE:
358 return ctx_info.zsize;
359 case GLUT_WINDOW_RED_SIZE:
360 return ctx_info.rsize;
361 case GLUT_WINDOW_GREEN_SIZE:
362 return ctx_info.gsize;
363 case GLUT_WINDOW_BLUE_SIZE:
364 return ctx_info.bsize;
365 case GLUT_WINDOW_ALPHA_SIZE:
366 return ctx_info.asize;
367 case GLUT_WINDOW_DOUBLEBUFFER:
368 return ctx_info.dblbuf;
369 case GLUT_WINDOW_RGBA:
371 case GLUT_WINDOW_NUM_SAMPLES:
372 return ctx_info.samples;
373 case GLUT_WINDOW_STEREO:
374 return ctx_info.stereo;
375 case GLUT_WINDOW_SRGB:
376 return ctx_info.srgb;
377 case GLUT_WINDOW_CURSOR:
379 case GLUT_WINDOW_COLORMAP_SIZE:
381 case GLUT_SCREEN_WIDTH:
382 get_screen_size(&x, &y);
384 case GLUT_SCREEN_HEIGHT:
385 get_screen_size(&x, &y);
387 case GLUT_INIT_DISPLAY_MODE:
389 case GLUT_INIT_WINDOW_X:
391 case GLUT_INIT_WINDOW_Y:
393 case GLUT_INIT_WINDOW_WIDTH:
395 case GLUT_INIT_WINDOW_HEIGHT:
397 case GLUT_ELAPSED_TIME:
405 int glutGetModifiers(void)
410 static int is_space(int c)
412 return c == ' ' || c == '\t' || c == '\v' || c == '\n' || c == '\r';
415 static const char *skip_space(const char *s)
417 while(*s && is_space(*s)) s++;
421 int glutExtensionSupported(char *ext)
423 const char *str, *eptr;
425 if(!(str = (const char*)glGetString(GL_EXTENSIONS))) {
430 str = skip_space(str);
431 eptr = skip_space(ext);
432 while(*str && !is_space(*str) && *eptr && *str == *eptr) {
436 if((!*str || is_space(*str)) && !*eptr) {
439 while(*str && !is_space(*str)) str++;
446 /* --------------- UNIX/X11 implementation ----------------- */
449 SPNAV_EVENT_ANY, /* used by spnav_remove_events() */
451 SPNAV_EVENT_BUTTON /* includes both press and release */
454 struct spnav_event_motion {
462 struct spnav_event_button {
470 struct spnav_event_motion motion;
471 struct spnav_event_button button;
475 static void handle_event(XEvent *ev);
477 static int spnav_window(Window win);
478 static int spnav_event(const XEvent *xev, union spnav_event *event);
479 static int spnav_remove_events(int type);
482 void glutMainLoopEvent(void)
487 panic("display callback not set");
490 if(!upd_pending && !cb_idle) {
491 XNextEvent(dpy, &ev);
495 while(XPending(dpy)) {
496 XNextEvent(dpy, &ev);
505 if(upd_pending && mapped) {
516 static void cleanup(void)
520 glXMakeCurrent(dpy, 0, 0);
521 XDestroyWindow(dpy, win);
525 static KeySym translate_keysym(KeySym sym)
546 static void handle_event(XEvent *ev)
549 union spnav_event sev;
558 case ConfigureNotify:
559 if(cb_reshape && (ev->xconfigure.width != win_width || ev->xconfigure.height != win_height)) {
560 win_width = ev->xconfigure.width;
561 win_height = ev->xconfigure.height;
562 cb_reshape(ev->xconfigure.width, ev->xconfigure.height);
567 if(ev->xclient.message_type == xa_wm_proto) {
568 if(ev->xclient.data.l[0] == xa_wm_del_win) {
572 if(spnav_event(ev, &sev)) {
574 case SPNAV_EVENT_MOTION:
575 if(cb_sball_motion) {
576 cb_sball_motion(sev.motion.x, sev.motion.y, sev.motion.z);
578 if(cb_sball_rotate) {
579 cb_sball_rotate(sev.motion.rx, sev.motion.ry, sev.motion.rz);
581 spnav_remove_events(SPNAV_EVENT_MOTION);
584 case SPNAV_EVENT_BUTTON:
585 if(cb_sball_button) {
586 cb_sball_button(sev.button.bnum + 1, sev.button.press ? GLUT_DOWN : GLUT_UP);
603 if(ignore_key_repeat && XEventsQueued(dpy, QueuedAfterReading)) {
605 XPeekEvent(dpy, &next);
607 if(next.type == KeyPress && next.xkey.keycode == ev->xkey.keycode &&
608 next.xkey.time == ev->xkey.time) {
609 /* this is a key-repeat event, ignore the release and consume
610 * the following press
612 XNextEvent(dpy, &next);
617 modstate = ev->xkey.state & (ShiftMask | ControlMask | Mod1Mask);
618 if(!(sym = XLookupKeysym(&ev->xkey, 0))) {
621 sym = translate_keysym(sym);
623 if(ev->type == KeyPress) {
624 if(cb_keydown) cb_keydown((unsigned char)sym, ev->xkey.x, ev->xkey.y);
626 if(cb_keyup) cb_keyup((unsigned char)sym, ev->xkey.x, ev->xkey.y);
629 if(ev->type == KeyPress) {
630 if(cb_skeydown) cb_skeydown(sym, ev->xkey.x, ev->xkey.y);
632 if(cb_skeyup) cb_skeyup(sym, ev->xkey.x, ev->xkey.y);
639 modstate = ev->xbutton.state & (ShiftMask | ControlMask | Mod1Mask);
641 int bn = ev->xbutton.button - Button1;
642 cb_mouse(bn, ev->type == ButtonPress ? GLUT_DOWN : GLUT_UP,
643 ev->xbutton.x, ev->xbutton.y);
648 if(ev->xmotion.state & (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask)) {
649 if(cb_motion) cb_motion(ev->xmotion.x, ev->xmotion.y);
651 if(cb_passive) cb_passive(ev->xmotion.x, ev->xmotion.y);
655 case VisibilityNotify:
657 cb_vis(ev->xvisibility.state == VisibilityFullyObscured ? GLUT_NOT_VISIBLE : GLUT_VISIBLE);
661 if(cb_entry) cb_entry(GLUT_ENTERED);
664 if(cb_entry) cb_entry(GLUT_LEFT);
669 void glutSwapBuffers(void)
671 glXSwapBuffers(dpy, win);
675 * set_fullscreen_mwm removes the decorations with MotifWM hints, and then it
676 * needs to resize the window to make it fullscreen. The way it does this is by
677 * querying the size of the root window (see get_screen_size), which in the
678 * case of multi-monitor setups will be the combined size of all monitors.
679 * This is problematic; the way to solve it is to use the XRandR extension, or
680 * the Xinerama extension, to figure out the dimensions of the correct video
681 * output, which would add potentially two extension support libraries to our
683 * Moreover, any X installation modern enough to support XR&R will almost
684 * certainly be running a window manager supporting the EHWM
685 * _NET_WM_STATE_FULLSCREEN method (set_fullscreen_ewmh), which does not rely
686 * on manual resizing, and is used in preference if available, making this
687 * whole endeavor pointless.
688 * So I'll just leave it with set_fullscreen_mwm covering the entire
689 * multi-monitor area for now.
694 unsigned long functions;
695 unsigned long decorations;
697 unsigned long status;
700 #define MWM_HINTS_DECORATIONS 2
701 #define MWM_DECOR_ALL 1
703 static void set_fullscreen_mwm(int fs)
705 struct mwm_hints hints;
706 int scr_width, scr_height;
709 get_window_pos(&prev_win_x, &prev_win_y);
710 get_window_size(&prev_win_width, &prev_win_height);
711 get_screen_size(&scr_width, &scr_height);
713 hints.decorations = 0;
714 hints.flags = MWM_HINTS_DECORATIONS;
715 XChangeProperty(dpy, win, xa_motif_wm_hints, xa_motif_wm_hints, 32,
716 PropModeReplace, (unsigned char*)&hints, 5);
718 XMoveResizeWindow(dpy, win, 0, 0, scr_width, scr_height);
720 XDeleteProperty(dpy, win, xa_motif_wm_hints);
721 XMoveResizeWindow(dpy, win, prev_win_x, prev_win_y, prev_win_width, prev_win_height);
725 static int have_netwm_fullscr(void)
729 unsigned long i, count, rem;
731 Atom xa_net_supported = XInternAtom(dpy, "_NET_SUPPORTED", False);
734 XGetWindowProperty(dpy, root, xa_net_supported, offs, 8, False, AnyPropertyType,
735 &type, &fmt, &count, &rem, (unsigned char**)&prop);
737 for(i=0; i<count; i++) {
738 if(prop[i] == xa_net_wm_state_fullscr) {
750 static void set_fullscreen_ewmh(int fs)
752 XClientMessageEvent msg = {0};
754 msg.type = ClientMessage;
756 msg.message_type = xa_net_wm_state; /* _NET_WM_STATE */
758 msg.data.l[0] = fs ? 1 : 0;
759 msg.data.l[1] = xa_net_wm_state_fullscr; /* _NET_WM_STATE_FULLSCREEN */
761 msg.data.l[3] = 1; /* source regular application */
762 XSendEvent(dpy, root, False, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent*)&msg);
765 static void set_fullscreen(int fs)
767 if(fullscreen == fs) return;
769 if(xa_net_wm_state && xa_net_wm_state_fullscr) {
770 set_fullscreen_ewmh(fs);
772 } else if(xa_motif_wm_hints) {
773 set_fullscreen_mwm(fs);
778 void glutPositionWindow(int x, int y)
781 XMoveWindow(dpy, win, x, y);
784 void glutReshapeWindow(int xsz, int ysz)
787 XResizeWindow(dpy, win, xsz, ysz);
790 void glutFullScreen(void)
795 void glutSetWindowTitle(const char *title)
798 if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
801 XSetWMName(dpy, win, &tprop);
805 void glutSetIconTitle(const char *title)
808 if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
811 XSetWMIconName(dpy, win, &tprop);
815 void glutSetCursor(int cidx)
820 case GLUT_CURSOR_LEFT_ARROW:
821 cur = XCreateFontCursor(dpy, XC_left_ptr);
823 case GLUT_CURSOR_INHERIT:
825 case GLUT_CURSOR_NONE:
832 XDefineCursor(dpy, win, cur);
836 void glutSetColor(int idx, float r, float g, float b)
840 if(idx >= 0 && idx < cmap_size) {
842 color.red = (unsigned short)(r * 65535.0f);
843 color.green = (unsigned short)(g * 65535.0f);
844 color.blue = (unsigned short)(b * 65535.0f);
845 color.flags = DoRed | DoGreen | DoBlue;
846 XStoreColor(dpy, cmap, &color);
850 float glutGetColor(int idx, int comp)
854 if(idx < 0 || idx >= cmap_size) {
859 XQueryColor(dpy, cmap, &color);
862 return color.red / 65535.0f;
864 return color.green / 65535.0f;
866 return color.blue / 65535.0f;
873 void glutSetKeyRepeat(int repmode)
882 static XVisualInfo *choose_visual(unsigned int mode)
889 if(mode & GLUT_DOUBLE) {
890 *aptr++ = GLX_DOUBLEBUFFER;
893 if(mode & GLUT_INDEX) {
894 *aptr++ = GLX_BUFFER_SIZE;
898 *aptr++ = GLX_RED_SIZE; *aptr++ = 1;
899 *aptr++ = GLX_GREEN_SIZE; *aptr++ = 1;
900 *aptr++ = GLX_BLUE_SIZE; *aptr++ = 1;
902 if(mode & GLUT_ALPHA) {
903 *aptr++ = GLX_ALPHA_SIZE;
906 if(mode & GLUT_DEPTH) {
907 *aptr++ = GLX_DEPTH_SIZE;
910 if(mode & GLUT_STENCIL) {
911 *aptr++ = GLX_STENCIL_SIZE;
914 if(mode & GLUT_ACCUM) {
915 *aptr++ = GLX_ACCUM_RED_SIZE; *aptr++ = 1;
916 *aptr++ = GLX_ACCUM_GREEN_SIZE; *aptr++ = 1;
917 *aptr++ = GLX_ACCUM_BLUE_SIZE; *aptr++ = 1;
919 if(mode & GLUT_STEREO) {
920 *aptr++ = GLX_STEREO;
922 if(mode & GLUT_SRGB) {
923 *aptr++ = GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB;
925 if(mode & GLUT_MULTISAMPLE) {
926 *aptr++ = GLX_SAMPLE_BUFFERS_ARB;
928 *aptr++ = GLX_SAMPLES_ARB;
935 return glXChooseVisual(dpy, scr, attr);
937 while(!(vi = glXChooseVisual(dpy, scr, attr)) && *samples) {
946 static void create_window(const char *title)
948 XSetWindowAttributes xattr = {0};
950 unsigned int xattr_mask;
951 unsigned int mode = init_mode;
953 if(!(vi = choose_visual(mode))) {
955 if(!(vi = choose_visual(mode))) {
956 panic("Failed to find compatible visual\n");
960 if(!(ctx = glXCreateContext(dpy, vi, 0, True))) {
962 panic("Failed to create OpenGL context\n");
965 glXGetConfig(dpy, vi, GLX_RED_SIZE, &ctx_info.rsize);
966 glXGetConfig(dpy, vi, GLX_GREEN_SIZE, &ctx_info.gsize);
967 glXGetConfig(dpy, vi, GLX_BLUE_SIZE, &ctx_info.bsize);
968 glXGetConfig(dpy, vi, GLX_ALPHA_SIZE, &ctx_info.asize);
969 glXGetConfig(dpy, vi, GLX_DEPTH_SIZE, &ctx_info.zsize);
970 glXGetConfig(dpy, vi, GLX_STENCIL_SIZE, &ctx_info.ssize);
971 glXGetConfig(dpy, vi, GLX_DOUBLEBUFFER, &ctx_info.dblbuf);
972 glXGetConfig(dpy, vi, GLX_STEREO, &ctx_info.stereo);
973 glXGetConfig(dpy, vi, GLX_SAMPLES_ARB, &ctx_info.samples);
974 glXGetConfig(dpy, vi, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &ctx_info.srgb);
976 if(!(cmap = XCreateColormap(dpy, root, vi->visual, mode & GLUT_INDEX ? AllocAll : AllocNone))) {
978 glXDestroyContext(dpy, ctx);
979 panic("Failed to create colormap\n");
981 cmap_size = GLUT_INDEX ? vi->colormap_size : 0;
983 xattr.background_pixel = BlackPixel(dpy, scr);
984 xattr.colormap = cmap;
985 xattr_mask = CWBackPixel | CWColormap | CWBackPixmap | CWBorderPixel;
986 if(!(win = XCreateWindow(dpy, root, init_x, init_y, init_width, init_height, 0,
987 vi->depth, InputOutput, vi->visual, xattr_mask, &xattr))) {
989 glXDestroyContext(dpy, ctx);
990 XFreeColormap(dpy, cmap);
991 panic("Failed to create window\n");
995 XSelectInput(dpy, win, evmask);
999 glutSetWindowTitle(title);
1000 glutSetIconTitle(title);
1001 XSetWMProtocols(dpy, win, &xa_wm_del_win, 1);
1002 XMapWindow(dpy, win);
1004 glXMakeCurrent(dpy, win, ctx);
1007 static void get_window_pos(int *x, int *y)
1010 XTranslateCoordinates(dpy, win, root, 0, 0, x, y, &child);
1013 static void get_window_size(int *w, int *h)
1015 XWindowAttributes wattr;
1016 XGetWindowAttributes(dpy, win, &wattr);
1021 static void get_screen_size(int *scrw, int *scrh)
1023 XWindowAttributes wattr;
1024 XGetWindowAttributes(dpy, root, &wattr);
1025 *scrw = wattr.width;
1026 *scrh = wattr.height;
1032 CMD_APP_WINDOW = 27695,
1036 static Window get_daemon_window(Display *dpy);
1037 static int catch_badwin(Display *dpy, XErrorEvent *err);
1039 #define SPNAV_INITIALIZED (xa_motion_event)
1041 static int spnav_window(Window win)
1043 int (*prev_xerr_handler)(Display*, XErrorEvent*);
1047 if(!SPNAV_INITIALIZED) {
1051 if(!(daemon_win = get_daemon_window(dpy))) {
1055 prev_xerr_handler = XSetErrorHandler(catch_badwin);
1057 xev.type = ClientMessage;
1058 xev.xclient.send_event = False;
1059 xev.xclient.display = dpy;
1060 xev.xclient.window = win;
1061 xev.xclient.message_type = xa_command_event;
1062 xev.xclient.format = 16;
1063 xev.xclient.data.s[0] = ((unsigned int)win & 0xffff0000) >> 16;
1064 xev.xclient.data.s[1] = (unsigned int)win & 0xffff;
1065 xev.xclient.data.s[2] = CMD_APP_WINDOW;
1067 XSendEvent(dpy, daemon_win, False, 0, &xev);
1070 XSetErrorHandler(prev_xerr_handler);
1074 static Bool match_events(Display *dpy, XEvent *xev, char *arg)
1076 int evtype = *(int*)arg;
1078 if(xev->type != ClientMessage) {
1082 if(xev->xclient.message_type == xa_motion_event) {
1083 return !evtype || evtype == SPNAV_EVENT_MOTION ? True : False;
1085 if(xev->xclient.message_type == xa_button_press_event ||
1086 xev->xclient.message_type == xa_button_release_event) {
1087 return !evtype || evtype == SPNAV_EVENT_BUTTON ? True : False;
1092 static int spnav_remove_events(int type)
1096 while(XCheckIfEvent(dpy, &xev, match_events, (char*)&type)) {
1102 static int spnav_event(const XEvent *xev, union spnav_event *event)
1107 xmsg_type = xev->xclient.message_type;
1109 if(xmsg_type != xa_motion_event && xmsg_type != xa_button_press_event &&
1110 xmsg_type != xa_button_release_event) {
1114 if(xmsg_type == xa_motion_event) {
1115 event->type = SPNAV_EVENT_MOTION;
1116 event->motion.data = &event->motion.x;
1118 for(i=0; i<6; i++) {
1119 event->motion.data[i] = xev->xclient.data.s[i + 2];
1121 event->motion.period = xev->xclient.data.s[8];
1123 event->type = SPNAV_EVENT_BUTTON;
1124 event->button.press = xmsg_type == xa_button_press_event ? 1 : 0;
1125 event->button.bnum = xev->xclient.data.s[2];
1130 static int mglut_strcmp(const char *s1, const char *s2)
1132 while(*s1 && *s1 == *s2) {
1139 static Window get_daemon_window(Display *dpy)
1142 XTextProperty wname;
1145 unsigned long nitems, bytes_after;
1146 unsigned char *prop;
1148 XGetWindowProperty(dpy, root, xa_command_event, 0, 1, False, AnyPropertyType,
1149 &type, &fmt, &nitems, &bytes_after, &prop);
1154 win = *(Window*)prop;
1158 if(!XGetWMName(dpy, win, &wname) || mglut_strcmp("Magellan Window", (char*)wname.value) != 0) {
1166 static int catch_badwin(Display *dpy, XErrorEvent *err)
1173 #endif /* BUILD_X11 */
1176 /* --------------- windows implementation ----------------- */
1178 static int reshape_pending;
1180 static void update_modkeys(void);
1181 static int translate_vkey(int vkey);
1182 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam);
1184 #ifdef MINIGLUT_WINMAIN
1185 int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprev, char *cmdline, int showcmd)
1188 char *argv[] = { "miniglut.exe", 0 };
1189 return main(argc, argv);
1193 void glutMainLoopEvent(void)
1198 panic("display callback not set");
1201 if(reshape_pending && cb_reshape) {
1202 reshape_pending = 0;
1203 get_window_size(&win_width, &win_height);
1204 cb_reshape(win_width, win_height);
1207 if(!upd_pending && !cb_idle) {
1208 GetMessage(&msg, 0, 0, 0);
1209 TranslateMessage(&msg);
1210 DispatchMessage(&msg);
1213 while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
1214 TranslateMessage(&msg);
1215 DispatchMessage(&msg);
1223 if(upd_pending && mapped) {
1229 static void cleanup(void)
1232 wglMakeCurrent(dc, 0);
1233 wglDeleteContext(ctx);
1234 UnregisterClass("MiniGLUT", hinst);
1238 void glutSwapBuffers(void)
1243 void glutPositionWindow(int x, int y)
1246 unsigned int flags = SWP_SHOWWINDOW;
1249 rect.left = prev_win_x;
1250 rect.top = prev_win_y;
1251 rect.right = rect.left + prev_win_width;
1252 rect.bottom = rect.top + prev_win_height;
1253 SetWindowLong(win, GWL_STYLE, WS_OVERLAPPEDWINDOW);
1255 flags |= SWP_FRAMECHANGED;
1257 GetWindowRect(win, &rect);
1259 SetWindowPos(win, HWND_NOTOPMOST, x, y, rect.right - rect.left, rect.bottom - rect.top, flags);
1262 void glutReshapeWindow(int xsz, int ysz)
1265 unsigned int flags = SWP_SHOWWINDOW;
1268 rect.left = prev_win_x;
1269 rect.top = prev_win_y;
1270 SetWindowLong(win, GWL_STYLE, WS_OVERLAPPEDWINDOW);
1272 flags |= SWP_FRAMECHANGED;
1274 GetWindowRect(win, &rect);
1276 SetWindowPos(win, HWND_NOTOPMOST, rect.left, rect.top, xsz, ysz, flags);
1279 void glutFullScreen(void)
1282 int scr_width, scr_height;
1284 if(fullscreen) return;
1286 GetWindowRect(win, &rect);
1287 prev_win_x = rect.left;
1288 prev_win_y = rect.top;
1289 prev_win_width = rect.right - rect.left;
1290 prev_win_height = rect.bottom - rect.top;
1292 get_screen_size(&scr_width, &scr_height);
1294 SetWindowLong(win, GWL_STYLE, 0);
1295 SetWindowPos(win, HWND_TOPMOST, 0, 0, scr_width, scr_height, SWP_SHOWWINDOW);
1300 void glutSetWindowTitle(const char *title)
1302 SetWindowText(win, title);
1305 void glutSetIconTitle(const char *title)
1309 void glutSetCursor(int cidx)
1312 case GLUT_CURSOR_NONE:
1315 case GLUT_CURSOR_INHERIT:
1316 case GLUT_CURSOR_LEFT_ARROW:
1318 SetCursor(LoadCursor(0, IDC_ARROW));
1323 void glutSetColor(int idx, float r, float g, float b)
1327 if(idx < 0 || idx >= 256 || !cmap) {
1331 col.peRed = (int)(r * 255.0f);
1332 col.peGreen = (int)(g * 255.0f);
1333 col.peBlue = (int)(b * 255.0f);
1334 col.peFlags = PC_NOCOLLAPSE;
1336 SetPaletteEntries(cmap, idx, 1, &col);
1339 UnrealizeObject(cmap);
1340 SelectPalette(dc, cmap, 0);
1345 float glutGetColor(int idx, int comp)
1349 if(idx < 0 || idx >= 256 || !cmap) {
1353 if(!GetPaletteEntries(cmap, idx, 1, &col)) {
1359 return col.peRed / 255.0f;
1361 return col.peGreen / 255.0f;
1363 return col.peBlue / 255.0f;
1370 void glutSetKeyRepeat(int repmode)
1374 #define WGL_DRAW_TO_WINDOW 0x2001
1375 #define WGL_ACCELERATION 0x2003
1376 #define WGL_SUPPORT_OPENGL 0x2010
1377 #define WGL_DOUBLE_BUFFER 0x2011
1378 #define WGL_STEREO 0x2012
1379 #define WGL_PIXEL_TYPE 0x2013
1380 #define WGL_COLOR_BITS 0x2014
1381 #define WGL_RED_BITS 0x2015
1382 #define WGL_GREEN_BITS 0x2017
1383 #define WGL_BLUE_BITS 0x2019
1384 #define WGL_ALPHA_BITS 0x201b
1385 #define WGL_ACCUM_BITS 0x201d
1386 #define WGL_DEPTH_BITS 0x2022
1387 #define WGL_STENCIL_BITS 0x2023
1388 #define WGL_FULL_ACCELERATION 0x2027
1390 #define WGL_TYPE_RGBA 0x202b
1391 #define WGL_TYPE_COLORINDEX 0x202c
1393 #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9
1394 #define WGL_SAMPLE_BUFFERS_ARB 0x2041
1395 #define WGL_SAMPLES_ARB 0x2042
1397 static PROC wglChoosePixelFormat;
1398 static PROC wglGetPixelFormatAttribiv;
1400 #define ATTR(a, v) \
1401 do { *aptr++ = (a); *aptr++ = (v); } while(0)
1403 static unsigned int choose_pixfmt(unsigned int mode)
1405 unsigned int num_pixfmt, pixfmt = 0;
1406 int attr[32] = { WGL_DRAW_TO_WINDOW, 1, WGL_SUPPORT_OPENGL, 1,
1407 WGL_ACCELERATION, WGL_FULL_ACCELERATION };
1408 float fattr[2] = {0, 0};
1410 int *aptr = attr + 6;
1413 if(mode & GLUT_DOUBLE) {
1414 ATTR(WGL_DOUBLE_BUFFER, 1);
1417 ATTR(WGL_PIXEL_TYPE, mode & GLUT_INDEX ? WGL_TYPE_COLORINDEX : WGL_TYPE_RGBA);
1418 ATTR(WGL_COLOR_BITS, mode & GLUT_INDEX ? 8 : 24);
1419 if(mode & GLUT_ALPHA) {
1420 ATTR(WGL_ALPHA_BITS, 4);
1422 if(mode & GLUT_DEPTH) {
1423 ATTR(WGL_DEPTH_BITS, 16);
1425 if(mode & GLUT_STENCIL) {
1426 ATTR(WGL_STENCIL_BITS, 1);
1428 if(mode & GLUT_ACCUM) {
1429 ATTR(WGL_ACCUM_BITS, 1);
1431 if(mode & GLUT_STEREO) {
1432 ATTR(WGL_STEREO, 1);
1434 if(mode & GLUT_SRGB) {
1435 ATTR(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, 1);
1437 if(mode & GLUT_MULTISAMPLE) {
1438 ATTR(WGL_SAMPLE_BUFFERS_ARB, 1);
1439 *aptr++ = WGL_SAMPLES_ARB;
1445 while((!wglChoosePixelFormat(dc, attr, fattr, 1, &pixfmt, &num_pixfmt) || !num_pixfmt) && samples && *samples) {
1454 static PIXELFORMATDESCRIPTOR pfd;
1455 static PIXELFORMATDESCRIPTOR tmppfd = {
1456 sizeof tmppfd, 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
1457 PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 8, 0,
1458 PFD_MAIN_PLANE, 0, 0, 0, 0
1460 #define TMPCLASS "TempMiniGLUT"
1462 #define GETATTR(attr, vptr) \
1465 wglGetPixelFormatAttribiv(dc, pixfmt, 0, 1, &gattr, vptr); \
1468 static int create_window_wglext(const char *title, int width, int height)
1470 WNDCLASSEX wc = {0};
1476 /* create a temporary window and GL context, just to query and retrieve
1477 * the wglChoosePixelFormatEXT function
1479 wc.cbSize = sizeof wc;
1480 wc.hbrBackground = GetStockObject(BLACK_BRUSH);
1481 wc.hCursor = LoadCursor(0, IDC_ARROW);
1482 wc.hIcon = wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
1483 wc.hInstance = hinst;
1484 wc.lpfnWndProc = DefWindowProc;
1485 wc.lpszClassName = TMPCLASS;
1486 wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
1487 if(!RegisterClassEx(&wc)) {
1490 if(!(tmpwin = CreateWindow(TMPCLASS, "temp", WS_OVERLAPPEDWINDOW, 0, 0,
1491 width, height, 0, 0, hinst, 0))) {
1494 tmpdc = GetDC(tmpwin);
1496 if(!(pixfmt = ChoosePixelFormat(tmpdc, &tmppfd)) ||
1497 !SetPixelFormat(tmpdc, pixfmt, &tmppfd) ||
1498 !(tmpctx = wglCreateContext(tmpdc))) {
1501 wglMakeCurrent(tmpdc, tmpctx);
1503 if(!(wglChoosePixelFormat = wglGetProcAddress("wglChoosePixelFormatARB"))) {
1504 if(!(wglChoosePixelFormat = wglGetProcAddress("wglChoosePixelFormatEXT"))) {
1507 if(!(wglGetPixelFormatAttribiv = wglGetProcAddress("wglGetPixelFormatAttribivEXT"))) {
1511 if(!(wglGetPixelFormatAttribiv = wglGetProcAddress("wglGetPixelFormatAttribivARB"))) {
1515 wglMakeCurrent(0, 0);
1516 wglDeleteContext(tmpctx);
1517 DestroyWindow(tmpwin);
1518 UnregisterClass(TMPCLASS, hinst);
1520 /* create the real window and context */
1521 if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW, init_x,
1522 init_y, width, height, 0, 0, hinst, 0))) {
1523 panic("Failed to create window\n");
1527 if(!(pixfmt = choose_pixfmt(init_mode))) {
1528 panic("Failed to find suitable pixel format\n");
1530 if(!SetPixelFormat(dc, pixfmt, &pfd)) {
1531 panic("Failed to set the selected pixel format\n");
1533 if(!(ctx = wglCreateContext(dc))) {
1534 panic("Failed to create the OpenGL context\n");
1536 wglMakeCurrent(dc, ctx);
1538 GETATTR(WGL_RED_BITS, &ctx_info.rsize);
1539 GETATTR(WGL_GREEN_BITS, &ctx_info.gsize);
1540 GETATTR(WGL_BLUE_BITS, &ctx_info.bsize);
1541 GETATTR(WGL_ALPHA_BITS, &ctx_info.asize);
1542 GETATTR(WGL_DEPTH_BITS, &ctx_info.zsize);
1543 GETATTR(WGL_STENCIL_BITS, &ctx_info.ssize);
1544 GETATTR(WGL_DOUBLE_BUFFER, &ctx_info.dblbuf);
1545 GETATTR(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, &ctx_info.srgb);
1546 GETATTR(WGL_SAMPLES_ARB, &ctx_info.samples);
1552 wglMakeCurrent(0, 0);
1553 wglDeleteContext(tmpctx);
1556 DestroyWindow(tmpwin);
1558 UnregisterClass(TMPCLASS, hinst);
1563 static void create_window(const char *title)
1566 int i, pixfmt, width, height;
1567 char palbuf[sizeof(LOGPALETTE) + 255 * sizeof(PALETTEENTRY)];
1573 rect.right = init_x + init_width;
1574 rect.bottom = init_y + init_height;
1575 AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0);
1576 width = rect.right - rect.left;
1577 height = rect.bottom - rect.top;
1579 memset(&pfd, 0, sizeof pfd);
1580 pfd.nSize = sizeof pfd;
1582 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
1583 if(init_mode & GLUT_STEREO) {
1584 pfd.dwFlags |= PFD_STEREO;
1586 if(init_mode & GLUT_INDEX) {
1587 pfd.iPixelType = PFD_TYPE_COLORINDEX;
1590 pfd.iPixelType = PFD_TYPE_RGBA;
1591 pfd.cColorBits = 24;
1593 if(init_mode & GLUT_ALPHA) {
1596 if(init_mode & GLUT_ACCUM) {
1597 pfd.cAccumBits = 24;
1599 if(init_mode & GLUT_DEPTH) {
1600 pfd.cDepthBits = 24;
1602 if(init_mode & GLUT_STENCIL) {
1603 pfd.cStencilBits = 8;
1605 pfd.iLayerType = PFD_MAIN_PLANE;
1607 if(init_mode & (GLUT_SRGB | GLUT_MULTISAMPLE)) {
1608 if(create_window_wglext(title, width, height) != -1) {
1613 /* if we don't need sRGB or multisample, or if the wglChoosePixelFormat method
1614 * failed, just use the old-style ChoosePixelFormat method instead
1616 if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW,
1617 rect.left, rect.top, width, height, 0, 0, hinst, 0))) {
1618 panic("Failed to create window\n");
1622 if(!(pixfmt = ChoosePixelFormat(dc, &pfd))) {
1623 panic("Failed to find suitable pixel format\n");
1625 if(!SetPixelFormat(dc, pixfmt, &pfd)) {
1626 panic("Failed to set the selected pixel format\n");
1628 if(!(ctx = wglCreateContext(dc))) {
1629 panic("Failed to create the OpenGL context\n");
1631 wglMakeCurrent(dc, ctx);
1633 DescribePixelFormat(dc, pixfmt, sizeof pfd, &pfd);
1634 ctx_info.rsize = pfd.cRedBits;
1635 ctx_info.gsize = pfd.cGreenBits;
1636 ctx_info.bsize = pfd.cBlueBits;
1637 ctx_info.asize = pfd.cAlphaBits;
1638 ctx_info.zsize = pfd.cDepthBits;
1639 ctx_info.ssize = pfd.cStencilBits;
1640 ctx_info.dblbuf = pfd.dwFlags & PFD_DOUBLEBUFFER ? 1 : 0;
1641 ctx_info.samples = 0;
1646 SetForegroundWindow(win);
1649 if(init_mode & GLUT_INDEX) {
1650 logpal = (LOGPALETTE*)palbuf;
1652 GetSystemPaletteEntries(dc, 0, 256, logpal->palPalEntry);
1654 logpal->palVersion = 0x300;
1655 logpal->palNumEntries = 256;
1657 if(!(cmap = CreatePalette(logpal))) {
1658 panic("Failed to create palette in indexed mode");
1660 SelectPalette(dc, cmap, 0);
1665 if(GetDeviceCaps(dc, BITSPIXEL) * GetDeviceCaps(dc, PLANES) <= 8) {
1666 /* for RGB mode in 8bpp displays we also need to set up a palette
1667 * with RGB 332 colors
1669 logpal = (LOGPALETTE*)palbuf;
1671 logpal->palVersion = 0x300;
1672 logpal->palNumEntries = 256;
1674 for(i=0; i<256; i++) {
1676 int g = (i >> 3) & 7;
1677 int b = (i >> 5) & 3;
1679 logpal->palPalEntry[i].peRed = (r << 5) | (r << 2) | (r >> 1);
1680 logpal->palPalEntry[i].peGreen = (g << 5) | (g << 2) | (g >> 1);
1681 logpal->palPalEntry[i].peBlue = (b << 6) | (b << 4) | (b << 2) | b;
1682 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
1685 if(!(cmap = CreatePalette(logpal))) {
1686 warn("Failed to create RGB 332 palette on palettized mode. Colors will be wrong\n");
1688 SelectPalette(dc, cmap, 0);
1696 reshape_pending = 1;
1699 static LRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
1701 static int mouse_x, mouse_y;
1706 if(win) DestroyWindow(win);
1717 ValidateRect(win, 0);
1721 x = lparam & 0xffff;
1723 if(x != win_width && y != win_height) {
1727 reshape_pending = 0;
1728 cb_reshape(win_width, win_height);
1735 if(cb_vis) cb_vis(mapped ? GLUT_VISIBLE : GLUT_NOT_VISIBLE);
1741 key = translate_vkey(wparam);
1744 cb_keydown((unsigned char)key, mouse_x, mouse_y);
1748 cb_skeydown(key, mouse_x, mouse_y);
1756 key = translate_vkey(wparam);
1759 cb_keyup((unsigned char)key, mouse_x, mouse_y);
1763 cb_skeyup(key, mouse_x, mouse_y);
1768 case WM_LBUTTONDOWN:
1769 handle_mbutton(0, 1, wparam, lparam);
1771 case WM_MBUTTONDOWN:
1772 handle_mbutton(1, 1, wparam, lparam);
1774 case WM_RBUTTONDOWN:
1775 handle_mbutton(2, 1, wparam, lparam);
1778 handle_mbutton(0, 0, wparam, lparam);
1781 handle_mbutton(1, 0, wparam, lparam);
1784 handle_mbutton(2, 0, wparam, lparam);
1788 if(wparam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1789 if(cb_motion) cb_motion(lparam & 0xffff, lparam >> 16);
1791 if(cb_passive) cb_passive(lparam & 0xffff, lparam >> 16);
1797 if(wparam == SC_KEYMENU || wparam == SC_SCREENSAVE || wparam == SC_MONITORPOWER) {
1801 return DefWindowProc(win, msg, wparam, lparam);
1807 static void update_modkeys(void)
1809 if(GetKeyState(VK_SHIFT) & 0x8000) {
1810 modstate |= GLUT_ACTIVE_SHIFT;
1812 modstate &= ~GLUT_ACTIVE_SHIFT;
1814 if(GetKeyState(VK_CONTROL) & 0x8000) {
1815 modstate |= GLUT_ACTIVE_CTRL;
1817 modstate &= ~GLUT_ACTIVE_CTRL;
1819 if(GetKeyState(VK_MENU) & 0x8000) {
1820 modstate |= GLUT_ACTIVE_ALT;
1822 modstate &= ~GLUT_ACTIVE_ALT;
1827 #define VK_OEM_1 0xba
1828 #define VK_OEM_2 0xbf
1829 #define VK_OEM_3 0xc0
1830 #define VK_OEM_4 0xdb
1831 #define VK_OEM_5 0xdc
1832 #define VK_OEM_6 0xdd
1833 #define VK_OEM_7 0xde
1836 static int translate_vkey(int vkey)
1839 case VK_PRIOR: return GLUT_KEY_PAGE_UP;
1840 case VK_NEXT: return GLUT_KEY_PAGE_DOWN;
1841 case VK_END: return GLUT_KEY_END;
1842 case VK_HOME: return GLUT_KEY_HOME;
1843 case VK_LEFT: return GLUT_KEY_LEFT;
1844 case VK_UP: return GLUT_KEY_UP;
1845 case VK_RIGHT: return GLUT_KEY_RIGHT;
1846 case VK_DOWN: return GLUT_KEY_DOWN;
1847 case VK_OEM_1: return ';';
1848 case VK_OEM_2: return '/';
1849 case VK_OEM_3: return '`';
1850 case VK_OEM_4: return '[';
1851 case VK_OEM_5: return '\\';
1852 case VK_OEM_6: return ']';
1853 case VK_OEM_7: return '\'';
1858 if(vkey >= 'A' && vkey <= 'Z') {
1860 } else if(vkey >= VK_F1 && vkey <= VK_F12) {
1861 vkey -= VK_F1 + GLUT_KEY_F1;
1867 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam)
1874 x = lparam & 0xffff;
1876 cb_mouse(bn, st ? GLUT_DOWN : GLUT_UP, x, y);
1880 static void get_window_pos(int *x, int *y)
1883 GetWindowRect(win, &rect);
1888 static void get_window_size(int *w, int *h)
1891 GetClientRect(win, &rect);
1892 *w = rect.right - rect.left;
1893 *h = rect.bottom - rect.top;
1896 static void get_screen_size(int *scrw, int *scrh)
1898 *scrw = GetSystemMetrics(SM_CXSCREEN);
1899 *scrh = GetSystemMetrics(SM_CYSCREEN);
1901 #endif /* BUILD_WIN32 */
1903 #if defined(unix) || defined(__unix__) || defined(__APPLE__)
1904 #include <sys/time.h>
1906 #ifdef MINIGLUT_USE_LIBC
1907 #define sys_gettimeofday(tv, tz) gettimeofday(tv, tz)
1909 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz);
1912 static long get_msec(void)
1914 static struct timeval tv0;
1917 sys_gettimeofday(&tv, 0);
1918 if(tv0.tv_sec == 0 && tv0.tv_usec == 0) {
1922 return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
1926 static long get_msec(void)
1931 #ifdef MINIGLUT_NO_WINMM
1932 tm = GetTickCount();
1944 static void panic(const char *msg)
1946 const char *end = msg;
1948 sys_write(2, msg, end - msg);
1952 static void warn(const char *msg)
1954 const char *end = msg;
1956 sys_write(2, msg, end - msg);
1960 #ifdef MINIGLUT_USE_LIBC
1968 static void sys_exit(int status)
1973 static int sys_write(int fd, const void *buf, int count)
1975 return write(fd, buf, count);
1978 #else /* !MINIGLUT_USE_LIBC */
1982 static void sys_exit(int status)
1986 :: "a"(60), "D"(status));
1988 static int sys_write(int fd, const void *buf, int count)
1994 : "a"(1), "D"(fd), "S"(buf), "d"(count));
1997 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
2003 : "a"(96), "D"(tv), "S"(tz));
2006 #endif /* __x86_64__ */
2008 static void sys_exit(int status)
2012 :: "a"(1), "b"(status));
2014 static int sys_write(int fd, const void *buf, int count)
2020 : "a"(4), "b"(fd), "c"(buf), "d"(count));
2023 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
2029 : "a"(78), "b"(tv), "c"(tz));
2032 #endif /* __i386__ */
2033 #endif /* __linux__ */
2036 static void sys_exit(int status)
2038 ExitProcess(status);
2040 static int sys_write(int fd, const void *buf, int count)
2042 unsigned long wrsz = 0;
2044 HANDLE out = GetStdHandle(fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
2045 if(!WriteFile(out, buf, count, &wrsz, 0)) {
2051 #endif /* !MINIGLUT_USE_LIBC */
2054 /* ----------------- primitives ------------------ */
2055 #ifdef MINIGLUT_USE_LIBC
2059 void mglut_sincos(float angle, float *sptr, float *cptr)
2065 float mglut_atan(float x)
2070 #else /* !MINIGLUT_USE_LIBC */
2073 void mglut_sincos(float angle, float *sptr, float *cptr)
2080 : "=m"(*sptr), "=m"(*cptr)
2085 float mglut_atan(float x)
2101 void mglut_sincos(float angle, float *sptr, float *cptr)
2114 float mglut_atan(float x)
2128 #pragma aux mglut_sincos = \
2130 "fstp dword ptr [edx]" \
2131 "fstp dword ptr [eax]" \
2132 parm[8087][eax][edx] \
2135 #pragma aux mglut_atan = \
2141 #endif /* __WATCOMC__ */
2143 #endif /* !MINIGLUT_USE_LIBC */
2145 #define PI 3.1415926536f
2147 void glutSolidSphere(float rad, int slices, int stacks)
2150 float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
2151 float du = 1.0f / (float)slices;
2152 float dv = 1.0f / (float)stacks;
2155 for(i=0; i<stacks; i++) {
2157 for(j=0; j<slices; j++) {
2159 for(k=0; k<4; k++) {
2160 gray = k ^ (k >> 1);
2161 s = gray & 1 ? u + du : u;
2162 t = gray & 2 ? v + dv : v;
2163 theta = s * PI * 2.0f;
2165 mglut_sincos(theta, &sintheta, &costheta);
2166 mglut_sincos(phi, &sinphi, &cosphi);
2167 x = sintheta * sinphi;
2168 y = costheta * sinphi;
2173 glNormal3f(x, y, z);
2174 glVertex3f(x * rad, y * rad, z * rad);
2181 void glutWireSphere(float rad, int slices, int stacks)
2183 glPushAttrib(GL_POLYGON_BIT);
2184 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2185 glutSolidSphere(rad, slices, stacks);
2189 void glutSolidCube(float sz)
2191 int i, j, idx, gray, flip, rotx;
2192 float vpos[3], norm[3];
2193 float rad = sz * 0.5f;
2196 for(i=0; i<6; i++) {
2199 idx = (~i & 2) - rotx;
2200 norm[0] = norm[1] = norm[2] = 0.0f;
2201 norm[idx] = flip ^ ((i >> 1) & 1) ? -1 : 1;
2203 vpos[idx] = norm[idx] * rad;
2204 for(j=0; j<4; j++) {
2205 gray = j ^ (j >> 1);
2206 vpos[i & 2] = (gray ^ flip) & 1 ? rad : -rad;
2207 vpos[rotx + 1] = (gray ^ (rotx << 1)) & 2 ? rad : -rad;
2208 glTexCoord2f(gray & 1, gray >> 1);
2215 void glutWireCube(float sz)
2217 glPushAttrib(GL_POLYGON_BIT);
2218 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2223 static void draw_cylinder(float rbot, float rtop, float height, int slices, int stacks)
2226 float x, y, z, s, t, u, v, theta, phi, sintheta, costheta, sinphi, cosphi, rad;
2227 float du = 1.0f / (float)slices;
2228 float dv = 1.0f / (float)stacks;
2231 phi = mglut_atan((rad < 0 ? -rad : rad) / height);
2232 mglut_sincos(phi, &sinphi, &cosphi);
2235 for(i=0; i<stacks; i++) {
2237 for(j=0; j<slices; j++) {
2239 for(k=0; k<4; k++) {
2240 gray = k ^ (k >> 1);
2241 s = gray & 2 ? u + du : u;
2242 t = gray & 1 ? v + dv : v;
2243 rad = rbot + (rtop - rbot) * t;
2244 theta = s * PI * 2.0f;
2245 mglut_sincos(theta, &sintheta, &costheta);
2247 x = sintheta * cosphi;
2248 y = costheta * cosphi;
2253 glNormal3f(x, y, z);
2254 glVertex3f(sintheta * rad, costheta * rad, t * height);
2261 void glutSolidCone(float base, float height, int slices, int stacks)
2263 draw_cylinder(base, 0, height, slices, stacks);
2266 void glutWireCone(float base, float height, int slices, int stacks)
2268 glPushAttrib(GL_POLYGON_BIT);
2269 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2270 glutSolidCone(base, height, slices, stacks);
2274 void glutSolidCylinder(float rad, float height, int slices, int stacks)
2276 draw_cylinder(rad, rad, height, slices, stacks);
2279 void glutWireCylinder(float rad, float height, int slices, int stacks)
2281 glPushAttrib(GL_POLYGON_BIT);
2282 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2283 glutSolidCylinder(rad, height, slices, stacks);
2287 void glutSolidTorus(float inner_rad, float outer_rad, int sides, int rings)
2290 float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
2291 float du = 1.0f / (float)rings;
2292 float dv = 1.0f / (float)sides;
2295 for(i=0; i<rings; i++) {
2297 for(j=0; j<sides; j++) {
2299 for(k=0; k<4; k++) {
2300 gray = k ^ (k >> 1);
2301 s = gray & 1 ? u + du : u;
2302 t = gray & 2 ? v + dv : v;
2303 theta = s * PI * 2.0f;
2304 phi = t * PI * 2.0f;
2305 mglut_sincos(theta, &sintheta, &costheta);
2306 mglut_sincos(phi, &sinphi, &cosphi);
2307 x = sintheta * sinphi;
2308 y = costheta * sinphi;
2313 glNormal3f(x, y, z);
2315 x = x * inner_rad + sintheta * outer_rad;
2316 y = y * inner_rad + costheta * outer_rad;
2318 glVertex3f(x, y, z);
2325 void glutWireTorus(float inner_rad, float outer_rad, int sides, int rings)
2327 glPushAttrib(GL_POLYGON_BIT);
2328 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2329 glutSolidTorus(inner_rad, outer_rad, sides, rings);
2333 #define NUM_TEAPOT_INDICES (sizeof teapot_index / sizeof *teapot_index)
2334 #define NUM_TEAPOT_VERTS (sizeof teapot_verts / sizeof *teapot_verts)
2336 #define NUM_TEAPOT_PATCHES (NUM_TEAPOT_INDICES / 16)
2338 #define PATCH_SUBDIV 7
2340 static float teapot_part_flip[] = {
2341 1, 1, 1, 1, /* rim flip */
2342 1, 1, 1, 1, /* body1 flip */
2343 1, 1, 1, 1, /* body2 flip */
2344 1, 1, 1, 1, /* lid patch 1 flip */
2345 1, 1, 1, 1, /* lid patch 2 flip */
2346 1, -1, /* handle 1 flip */
2347 1, -1, /* handle 2 flip */
2348 1, -1, /* spout 1 flip */
2349 1, -1, /* spout 2 flip */
2350 1, 1, 1, 1 /* bottom flip */
2353 static float teapot_part_rot[] = {
2354 0, 90, 180, 270, /* rim rotations */
2355 0, 90, 180, 270, /* body patch 1 rotations */
2356 0, 90, 180, 270, /* body patch 2 rotations */
2357 0, 90, 180, 270, /* lid patch 1 rotations */
2358 0, 90, 180, 270, /* lid patch 2 rotations */
2359 0, 0, /* handle 1 rotations */
2360 0, 0, /* handle 2 rotations */
2361 0, 0, /* spout 1 rotations */
2362 0, 0, /* spout 2 rotations */
2363 0, 90, 180, 270 /* bottom rotations */
2367 static int teapot_index[] = {
2369 102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
2370 102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
2371 102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
2372 102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
2374 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
2375 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
2376 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
2377 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
2379 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
2380 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
2381 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
2382 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
2384 96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,
2385 96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,
2386 96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,
2387 96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,
2389 0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
2390 0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
2391 0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
2392 0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
2394 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
2395 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
2397 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 28, 65, 66, 67,
2398 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 28, 65, 66, 67,
2400 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
2401 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
2403 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
2404 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
2406 118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37,
2407 118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37,
2408 118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37,
2409 118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37
2413 static float teapot_verts[][3] = {
2414 { 0.2000, 0.0000, 2.70000 }, { 0.2000, -0.1120, 2.70000 },
2415 { 0.1120, -0.2000, 2.70000 }, { 0.0000, -0.2000, 2.70000 },
2416 { 1.3375, 0.0000, 2.53125 }, { 1.3375, -0.7490, 2.53125 },
2417 { 0.7490, -1.3375, 2.53125 }, { 0.0000, -1.3375, 2.53125 },
2418 { 1.4375, 0.0000, 2.53125 }, { 1.4375, -0.8050, 2.53125 },
2419 { 0.8050, -1.4375, 2.53125 }, { 0.0000, -1.4375, 2.53125 },
2420 { 1.5000, 0.0000, 2.40000 }, { 1.5000, -0.8400, 2.40000 },
2421 { 0.8400, -1.5000, 2.40000 }, { 0.0000, -1.5000, 2.40000 },
2422 { 1.7500, 0.0000, 1.87500 }, { 1.7500, -0.9800, 1.87500 },
2423 { 0.9800, -1.7500, 1.87500 }, { 0.0000, -1.7500, 1.87500 },
2424 { 2.0000, 0.0000, 1.35000 }, { 2.0000, -1.1200, 1.35000 },
2425 { 1.1200, -2.0000, 1.35000 }, { 0.0000, -2.0000, 1.35000 },
2426 { 2.0000, 0.0000, 0.90000 }, { 2.0000, -1.1200, 0.90000 },
2427 { 1.1200, -2.0000, 0.90000 }, { 0.0000, -2.0000, 0.90000 },
2428 { -2.0000, 0.0000, 0.90000 }, { 2.0000, 0.0000, 0.45000 },
2429 { 2.0000, -1.1200, 0.45000 }, { 1.1200, -2.0000, 0.45000 },
2430 { 0.0000, -2.0000, 0.45000 }, { 1.5000, 0.0000, 0.22500 },
2431 { 1.5000, -0.8400, 0.22500 }, { 0.8400, -1.5000, 0.22500 },
2432 { 0.0000, -1.5000, 0.22500 }, { 1.5000, 0.0000, 0.15000 },
2433 { 1.5000, -0.8400, 0.15000 }, { 0.8400, -1.5000, 0.15000 },
2434 { 0.0000, -1.5000, 0.15000 }, { -1.6000, 0.0000, 2.02500 },
2435 { -1.6000, -0.3000, 2.02500 }, { -1.5000, -0.3000, 2.25000 },
2436 { -1.5000, 0.0000, 2.25000 }, { -2.3000, 0.0000, 2.02500 },
2437 { -2.3000, -0.3000, 2.02500 }, { -2.5000, -0.3000, 2.25000 },
2438 { -2.5000, 0.0000, 2.25000 }, { -2.7000, 0.0000, 2.02500 },
2439 { -2.7000, -0.3000, 2.02500 }, { -3.0000, -0.3000, 2.25000 },
2440 { -3.0000, 0.0000, 2.25000 }, { -2.7000, 0.0000, 1.80000 },
2441 { -2.7000, -0.3000, 1.80000 }, { -3.0000, -0.3000, 1.80000 },
2442 { -3.0000, 0.0000, 1.80000 }, { -2.7000, 0.0000, 1.57500 },
2443 { -2.7000, -0.3000, 1.57500 }, { -3.0000, -0.3000, 1.35000 },
2444 { -3.0000, 0.0000, 1.35000 }, { -2.5000, 0.0000, 1.12500 },
2445 { -2.5000, -0.3000, 1.12500 }, { -2.6500, -0.3000, 0.93750 },
2446 { -2.6500, 0.0000, 0.93750 }, { -2.0000, -0.3000, 0.90000 },
2447 { -1.9000, -0.3000, 0.60000 }, { -1.9000, 0.0000, 0.60000 },
2448 { 1.7000, 0.0000, 1.42500 }, { 1.7000, -0.6600, 1.42500 },
2449 { 1.7000, -0.6600, 0.60000 }, { 1.7000, 0.0000, 0.60000 },
2450 { 2.6000, 0.0000, 1.42500 }, { 2.6000, -0.6600, 1.42500 },
2451 { 3.1000, -0.6600, 0.82500 }, { 3.1000, 0.0000, 0.82500 },
2452 { 2.3000, 0.0000, 2.10000 }, { 2.3000, -0.2500, 2.10000 },
2453 { 2.4000, -0.2500, 2.02500 }, { 2.4000, 0.0000, 2.02500 },
2454 { 2.7000, 0.0000, 2.40000 }, { 2.7000, -0.2500, 2.40000 },
2455 { 3.3000, -0.2500, 2.40000 }, { 3.3000, 0.0000, 2.40000 },
2456 { 2.8000, 0.0000, 2.47500 }, { 2.8000, -0.2500, 2.47500 },
2457 { 3.5250, -0.2500, 2.49375 }, { 3.5250, 0.0000, 2.49375 },
2458 { 2.9000, 0.0000, 2.47500 }, { 2.9000, -0.1500, 2.47500 },
2459 { 3.4500, -0.1500, 2.51250 }, { 3.4500, 0.0000, 2.51250 },
2460 { 2.8000, 0.0000, 2.40000 }, { 2.8000, -0.1500, 2.40000 },
2461 { 3.2000, -0.1500, 2.40000 }, { 3.2000, 0.0000, 2.40000 },
2462 { 0.0000, 0.0000, 3.15000 }, { 0.8000, 0.0000, 3.15000 },
2463 { 0.8000, -0.4500, 3.15000 }, { 0.4500, -0.8000, 3.15000 },
2464 { 0.0000, -0.8000, 3.15000 }, { 0.0000, 0.0000, 2.85000 },
2465 { 1.4000, 0.0000, 2.40000 }, { 1.4000, -0.7840, 2.40000 },
2466 { 0.7840, -1.4000, 2.40000 }, { 0.0000, -1.4000, 2.40000 },
2467 { 0.4000, 0.0000, 2.55000 }, { 0.4000, -0.2240, 2.55000 },
2468 { 0.2240, -0.4000, 2.55000 }, { 0.0000, -0.4000, 2.55000 },
2469 { 1.3000, 0.0000, 2.55000 }, { 1.3000, -0.7280, 2.55000 },
2470 { 0.7280, -1.3000, 2.55000 }, { 0.0000, -1.3000, 2.55000 },
2471 { 1.3000, 0.0000, 2.40000 }, { 1.3000, -0.7280, 2.40000 },
2472 { 0.7280, -1.3000, 2.40000 }, { 0.0000, -1.3000, 2.40000 },
2473 { 0.0000, 0.0000, 0.00000 }, { 1.4250, -0.7980, 0.00000 },
2474 { 1.5000, 0.0000, 0.07500 }, { 1.4250, 0.0000, 0.00000 },
2475 { 0.7980, -1.4250, 0.00000 }, { 0.0000, -1.5000, 0.07500 },
2476 { 0.0000, -1.4250, 0.00000 }, { 1.5000, -0.8400, 0.07500 },
2477 { 0.8400, -1.5000, 0.07500 }
2480 static void draw_patch(int *index, int flip, float scale);
2481 static float bernstein(int i, float x);
2483 void glutSolidTeapot(float size)
2489 for(i=0; i<NUM_TEAPOT_PATCHES; i++) {
2490 float flip = teapot_part_flip[i];
2491 float rot = teapot_part_rot[i];
2493 glMatrixMode(GL_MODELVIEW);
2495 glTranslatef(0, -3.15 * size * 0.5, 0);
2496 glRotatef(rot, 0, 1, 0);
2497 glScalef(1, 1, flip);
2498 glRotatef(-90, 1, 0, 0);
2500 draw_patch(teapot_index + i * 16, flip < 0.0 ? 1 : 0, size);
2506 void glutWireTeapot(float size)
2508 glPushAttrib(GL_POLYGON_BIT);
2509 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2510 glutSolidTeapot(size);
2515 static void bezier_patch(float *res, float *cp, float u, float v)
2519 res[0] = res[1] = res[2] = 0.0f;
2521 for(j=0; j<4; j++) {
2522 for(i=0; i<4; i++) {
2523 float bu = bernstein(i, u);
2524 float bv = bernstein(j, v);
2526 res[0] += cp[0] * bu * bv;
2527 res[1] += cp[1] * bu * bv;
2528 res[2] += cp[2] * bu * bv;
2535 static float rsqrt(float x)
2537 float xhalf = x * 0.5f;
2539 i = 0x5f3759df - (i >> 1);
2541 x = x * (1.5f - xhalf * x * x);
2546 #define CROSS(res, a, b) \
2548 (res)[0] = (a)[1] * (b)[2] - (a)[2] * (b)[1]; \
2549 (res)[1] = (a)[2] * (b)[0] - (a)[0] * (b)[2]; \
2550 (res)[2] = (a)[0] * (b)[1] - (a)[1] * (b)[0]; \
2553 #define NORMALIZE(v) \
2555 float s = rsqrt((v)[0] * (v)[0] + (v)[1] * (v)[1] + (v)[2] * (v)[2]); \
2563 static void bezier_patch_norm(float *res, float *cp, float u, float v)
2565 float tang[3], bitan[3], tmp[3];
2567 bezier_patch(tang, cp, u + DT, v);
2568 bezier_patch(tmp, cp, u - DT, v);
2573 bezier_patch(bitan, cp, u, v + DT);
2574 bezier_patch(tmp, cp, u, v - DT);
2579 CROSS(res, tang, bitan);
2585 static float bernstein(int i, float x)
2587 float invx = 1.0f - x;
2591 return invx * invx * invx;
2593 return 3.0f * x * invx * invx;
2595 return 3.0f * x * x * invx;
2604 static void draw_patch(int *index, int flip, float scale)
2606 static const float uoffs[2][4] = {{0, 0, 1, 1}, {1, 1, 0, 0}};
2607 static const float voffs[4] = {0, 1, 1, 0};
2613 float du = 1.0 / PATCH_SUBDIV;
2614 float dv = 1.0 / PATCH_SUBDIV;
2616 /* collect control points */
2617 for(i=0; i<16; i++) {
2618 cp[i * 3] = teapot_verts[index[i]][0];
2619 cp[i * 3 + 1] = teapot_verts[index[i]][1];
2620 cp[i * 3 + 2] = teapot_verts[index[i]][2];
2627 for(i=0; i<PATCH_SUBDIV; i++) {
2629 for(j=0; j<PATCH_SUBDIV; j++) {
2631 for(k=0; k<4; k++) {
2632 bezier_patch(pt, cp, u + uoffs[flip][k] * du, v + voffs[k] * dv);
2634 /* top/bottom normal hack */
2638 } else if(pt[2] < 0.00001) {
2642 bezier_patch_norm(n, cp, u + uoffs[flip][k] * du, v + voffs[k] * dv);
2647 glVertex3f(pt[0] * scale, pt[1] * scale, pt[2] * scale);