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 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 void glutReshapeWindow(int xsz, int ysz)
1269 unsigned int flags = SWP_SHOWWINDOW;
1272 rect.left = prev_win_x;
1273 rect.top = prev_win_y;
1274 SetWindowLong(win, GWL_STYLE, WS_OVERLAPPEDWINDOW);
1276 flags |= SWP_FRAMECHANGED;
1278 GetWindowRect(win, &rect);
1280 SetWindowPos(win, HWND_NOTOPMOST, rect.left, rect.top, xsz, ysz, flags);
1283 void glutFullScreen(void)
1286 int scr_width, scr_height;
1288 if(fullscreen) return;
1290 GetWindowRect(win, &rect);
1291 prev_win_x = rect.left;
1292 prev_win_y = rect.top;
1293 prev_win_width = rect.right - rect.left;
1294 prev_win_height = rect.bottom - rect.top;
1296 get_screen_size(&scr_width, &scr_height);
1298 SetWindowLong(win, GWL_STYLE, 0);
1299 SetWindowPos(win, HWND_TOPMOST, 0, 0, scr_width, scr_height, SWP_SHOWWINDOW);
1304 void glutSetWindowTitle(const char *title)
1306 SetWindowText(win, title);
1309 void glutSetIconTitle(const char *title)
1313 void glutSetCursor(int cidx)
1316 case GLUT_CURSOR_NONE:
1319 case GLUT_CURSOR_INHERIT:
1320 case GLUT_CURSOR_LEFT_ARROW:
1322 SetCursor(LoadCursor(0, IDC_ARROW));
1327 void glutWarpPointer(int x, int y)
1332 void glutSetColor(int idx, float r, float g, float b)
1336 if(idx < 0 || idx >= 256 || !cmap) {
1340 col.peRed = (int)(r * 255.0f);
1341 col.peGreen = (int)(g * 255.0f);
1342 col.peBlue = (int)(b * 255.0f);
1343 col.peFlags = PC_NOCOLLAPSE;
1345 SetPaletteEntries(cmap, idx, 1, &col);
1348 UnrealizeObject(cmap);
1349 SelectPalette(dc, cmap, 0);
1354 float glutGetColor(int idx, int comp)
1358 if(idx < 0 || idx >= 256 || !cmap) {
1362 if(!GetPaletteEntries(cmap, idx, 1, &col)) {
1368 return col.peRed / 255.0f;
1370 return col.peGreen / 255.0f;
1372 return col.peBlue / 255.0f;
1379 void glutSetKeyRepeat(int repmode)
1383 #define WGL_DRAW_TO_WINDOW 0x2001
1384 #define WGL_ACCELERATION 0x2003
1385 #define WGL_SUPPORT_OPENGL 0x2010
1386 #define WGL_DOUBLE_BUFFER 0x2011
1387 #define WGL_STEREO 0x2012
1388 #define WGL_PIXEL_TYPE 0x2013
1389 #define WGL_COLOR_BITS 0x2014
1390 #define WGL_RED_BITS 0x2015
1391 #define WGL_GREEN_BITS 0x2017
1392 #define WGL_BLUE_BITS 0x2019
1393 #define WGL_ALPHA_BITS 0x201b
1394 #define WGL_ACCUM_BITS 0x201d
1395 #define WGL_DEPTH_BITS 0x2022
1396 #define WGL_STENCIL_BITS 0x2023
1397 #define WGL_FULL_ACCELERATION 0x2027
1399 #define WGL_TYPE_RGBA 0x202b
1400 #define WGL_TYPE_COLORINDEX 0x202c
1402 #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9
1403 #define WGL_SAMPLE_BUFFERS_ARB 0x2041
1404 #define WGL_SAMPLES_ARB 0x2042
1406 static PROC wglChoosePixelFormat;
1407 static PROC wglGetPixelFormatAttribiv;
1409 #define ATTR(a, v) \
1410 do { *aptr++ = (a); *aptr++ = (v); } while(0)
1412 static unsigned int choose_pixfmt(unsigned int mode)
1414 unsigned int num_pixfmt, pixfmt = 0;
1415 int attr[32] = { WGL_DRAW_TO_WINDOW, 1, WGL_SUPPORT_OPENGL, 1,
1416 WGL_ACCELERATION, WGL_FULL_ACCELERATION };
1417 float fattr[2] = {0, 0};
1419 int *aptr = attr + 6;
1422 if(mode & GLUT_DOUBLE) {
1423 ATTR(WGL_DOUBLE_BUFFER, 1);
1426 ATTR(WGL_PIXEL_TYPE, mode & GLUT_INDEX ? WGL_TYPE_COLORINDEX : WGL_TYPE_RGBA);
1427 ATTR(WGL_COLOR_BITS, mode & GLUT_INDEX ? 8 : 24);
1428 if(mode & GLUT_ALPHA) {
1429 ATTR(WGL_ALPHA_BITS, 4);
1431 if(mode & GLUT_DEPTH) {
1432 ATTR(WGL_DEPTH_BITS, 16);
1434 if(mode & GLUT_STENCIL) {
1435 ATTR(WGL_STENCIL_BITS, 1);
1437 if(mode & GLUT_ACCUM) {
1438 ATTR(WGL_ACCUM_BITS, 1);
1440 if(mode & GLUT_STEREO) {
1441 ATTR(WGL_STEREO, 1);
1443 if(mode & GLUT_SRGB) {
1444 ATTR(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, 1);
1446 if(mode & GLUT_MULTISAMPLE) {
1447 ATTR(WGL_SAMPLE_BUFFERS_ARB, 1);
1448 *aptr++ = WGL_SAMPLES_ARB;
1454 while((!wglChoosePixelFormat(dc, attr, fattr, 1, &pixfmt, &num_pixfmt) || !num_pixfmt) && samples && *samples) {
1463 static PIXELFORMATDESCRIPTOR pfd;
1464 static PIXELFORMATDESCRIPTOR tmppfd = {
1465 sizeof tmppfd, 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
1466 PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 8, 0,
1467 PFD_MAIN_PLANE, 0, 0, 0, 0
1469 #define TMPCLASS "TempMiniGLUT"
1471 #define GETATTR(attr, vptr) \
1474 wglGetPixelFormatAttribiv(dc, pixfmt, 0, 1, &gattr, vptr); \
1477 static int create_window_wglext(const char *title, int width, int height)
1479 WNDCLASSEX wc = {0};
1485 /* create a temporary window and GL context, just to query and retrieve
1486 * the wglChoosePixelFormatEXT function
1488 wc.cbSize = sizeof wc;
1489 wc.hbrBackground = GetStockObject(BLACK_BRUSH);
1490 wc.hCursor = LoadCursor(0, IDC_ARROW);
1491 wc.hIcon = wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
1492 wc.hInstance = hinst;
1493 wc.lpfnWndProc = DefWindowProc;
1494 wc.lpszClassName = TMPCLASS;
1495 wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
1496 if(!RegisterClassEx(&wc)) {
1499 if(!(tmpwin = CreateWindow(TMPCLASS, "temp", WS_OVERLAPPEDWINDOW, 0, 0,
1500 width, height, 0, 0, hinst, 0))) {
1503 tmpdc = GetDC(tmpwin);
1505 if(!(pixfmt = ChoosePixelFormat(tmpdc, &tmppfd)) ||
1506 !SetPixelFormat(tmpdc, pixfmt, &tmppfd) ||
1507 !(tmpctx = wglCreateContext(tmpdc))) {
1510 wglMakeCurrent(tmpdc, tmpctx);
1512 if(!(wglChoosePixelFormat = wglGetProcAddress("wglChoosePixelFormatARB"))) {
1513 if(!(wglChoosePixelFormat = wglGetProcAddress("wglChoosePixelFormatEXT"))) {
1516 if(!(wglGetPixelFormatAttribiv = wglGetProcAddress("wglGetPixelFormatAttribivEXT"))) {
1520 if(!(wglGetPixelFormatAttribiv = wglGetProcAddress("wglGetPixelFormatAttribivARB"))) {
1524 wglMakeCurrent(0, 0);
1525 wglDeleteContext(tmpctx);
1526 DestroyWindow(tmpwin);
1527 UnregisterClass(TMPCLASS, hinst);
1529 /* create the real window and context */
1530 if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW, init_x,
1531 init_y, width, height, 0, 0, hinst, 0))) {
1532 panic("Failed to create window\n");
1536 if(!(pixfmt = choose_pixfmt(init_mode))) {
1537 panic("Failed to find suitable pixel format\n");
1539 if(!SetPixelFormat(dc, pixfmt, &pfd)) {
1540 panic("Failed to set the selected pixel format\n");
1542 if(!(ctx = wglCreateContext(dc))) {
1543 panic("Failed to create the OpenGL context\n");
1545 wglMakeCurrent(dc, ctx);
1547 GETATTR(WGL_RED_BITS, &ctx_info.rsize);
1548 GETATTR(WGL_GREEN_BITS, &ctx_info.gsize);
1549 GETATTR(WGL_BLUE_BITS, &ctx_info.bsize);
1550 GETATTR(WGL_ALPHA_BITS, &ctx_info.asize);
1551 GETATTR(WGL_DEPTH_BITS, &ctx_info.zsize);
1552 GETATTR(WGL_STENCIL_BITS, &ctx_info.ssize);
1553 GETATTR(WGL_DOUBLE_BUFFER, &ctx_info.dblbuf);
1554 GETATTR(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, &ctx_info.srgb);
1555 GETATTR(WGL_SAMPLES_ARB, &ctx_info.samples);
1561 wglMakeCurrent(0, 0);
1562 wglDeleteContext(tmpctx);
1565 DestroyWindow(tmpwin);
1567 UnregisterClass(TMPCLASS, hinst);
1572 static void create_window(const char *title)
1575 int i, pixfmt, width, height;
1576 char palbuf[sizeof(LOGPALETTE) + 255 * sizeof(PALETTEENTRY)];
1582 rect.right = init_x + init_width;
1583 rect.bottom = init_y + init_height;
1584 AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0);
1585 width = rect.right - rect.left;
1586 height = rect.bottom - rect.top;
1588 memset(&pfd, 0, sizeof pfd);
1589 pfd.nSize = sizeof pfd;
1591 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
1592 if(init_mode & GLUT_STEREO) {
1593 pfd.dwFlags |= PFD_STEREO;
1595 if(init_mode & GLUT_INDEX) {
1596 pfd.iPixelType = PFD_TYPE_COLORINDEX;
1599 pfd.iPixelType = PFD_TYPE_RGBA;
1600 pfd.cColorBits = 24;
1602 if(init_mode & GLUT_ALPHA) {
1605 if(init_mode & GLUT_ACCUM) {
1606 pfd.cAccumBits = 24;
1608 if(init_mode & GLUT_DEPTH) {
1609 pfd.cDepthBits = 24;
1611 if(init_mode & GLUT_STENCIL) {
1612 pfd.cStencilBits = 8;
1614 pfd.iLayerType = PFD_MAIN_PLANE;
1616 if(init_mode & (GLUT_SRGB | GLUT_MULTISAMPLE)) {
1617 if(create_window_wglext(title, width, height) != -1) {
1622 /* if we don't need sRGB or multisample, or if the wglChoosePixelFormat method
1623 * failed, just use the old-style ChoosePixelFormat method instead
1625 if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW,
1626 rect.left, rect.top, width, height, 0, 0, hinst, 0))) {
1627 panic("Failed to create window\n");
1631 if(!(pixfmt = ChoosePixelFormat(dc, &pfd))) {
1632 panic("Failed to find suitable pixel format\n");
1634 if(!SetPixelFormat(dc, pixfmt, &pfd)) {
1635 panic("Failed to set the selected pixel format\n");
1637 if(!(ctx = wglCreateContext(dc))) {
1638 panic("Failed to create the OpenGL context\n");
1640 wglMakeCurrent(dc, ctx);
1642 DescribePixelFormat(dc, pixfmt, sizeof pfd, &pfd);
1643 ctx_info.rsize = pfd.cRedBits;
1644 ctx_info.gsize = pfd.cGreenBits;
1645 ctx_info.bsize = pfd.cBlueBits;
1646 ctx_info.asize = pfd.cAlphaBits;
1647 ctx_info.zsize = pfd.cDepthBits;
1648 ctx_info.ssize = pfd.cStencilBits;
1649 ctx_info.dblbuf = pfd.dwFlags & PFD_DOUBLEBUFFER ? 1 : 0;
1650 ctx_info.samples = 0;
1655 SetForegroundWindow(win);
1658 if(init_mode & GLUT_INDEX) {
1659 logpal = (LOGPALETTE*)palbuf;
1661 GetSystemPaletteEntries(dc, 0, 256, logpal->palPalEntry);
1663 logpal->palVersion = 0x300;
1664 logpal->palNumEntries = 256;
1666 if(!(cmap = CreatePalette(logpal))) {
1667 panic("Failed to create palette in indexed mode");
1669 SelectPalette(dc, cmap, 0);
1674 if(GetDeviceCaps(dc, BITSPIXEL) * GetDeviceCaps(dc, PLANES) <= 8) {
1675 /* for RGB mode in 8bpp displays we also need to set up a palette
1676 * with RGB 332 colors
1678 logpal = (LOGPALETTE*)palbuf;
1680 logpal->palVersion = 0x300;
1681 logpal->palNumEntries = 256;
1683 for(i=0; i<256; i++) {
1685 int g = (i >> 3) & 7;
1686 int b = (i >> 5) & 3;
1688 logpal->palPalEntry[i].peRed = (r << 5) | (r << 2) | (r >> 1);
1689 logpal->palPalEntry[i].peGreen = (g << 5) | (g << 2) | (g >> 1);
1690 logpal->palPalEntry[i].peBlue = (b << 6) | (b << 4) | (b << 2) | b;
1691 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
1694 if((cmap = CreatePalette(logpal))) {
1695 SelectPalette(dc, cmap, 0);
1703 reshape_pending = 1;
1706 static LRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
1708 static int mouse_x, mouse_y;
1713 if(win) DestroyWindow(win);
1724 ValidateRect(win, 0);
1728 x = lparam & 0xffff;
1730 if(x != win_width && y != win_height) {
1734 reshape_pending = 0;
1735 cb_reshape(win_width, win_height);
1742 if(cb_vis) cb_vis(mapped ? GLUT_VISIBLE : GLUT_NOT_VISIBLE);
1748 key = translate_vkey(wparam);
1751 cb_keydown((unsigned char)key, mouse_x, mouse_y);
1755 cb_skeydown(key, mouse_x, mouse_y);
1763 key = translate_vkey(wparam);
1766 cb_keyup((unsigned char)key, mouse_x, mouse_y);
1770 cb_skeyup(key, mouse_x, mouse_y);
1775 case WM_LBUTTONDOWN:
1776 handle_mbutton(0, 1, wparam, lparam);
1778 case WM_MBUTTONDOWN:
1779 handle_mbutton(1, 1, wparam, lparam);
1781 case WM_RBUTTONDOWN:
1782 handle_mbutton(2, 1, wparam, lparam);
1785 handle_mbutton(0, 0, wparam, lparam);
1788 handle_mbutton(1, 0, wparam, lparam);
1791 handle_mbutton(2, 0, wparam, lparam);
1795 if(wparam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1796 if(cb_motion) cb_motion(lparam & 0xffff, lparam >> 16);
1798 if(cb_passive) cb_passive(lparam & 0xffff, lparam >> 16);
1804 if(wparam == SC_KEYMENU || wparam == SC_SCREENSAVE || wparam == SC_MONITORPOWER) {
1808 return DefWindowProc(win, msg, wparam, lparam);
1814 static void update_modkeys(void)
1816 if(GetKeyState(VK_SHIFT) & 0x8000) {
1817 modstate |= GLUT_ACTIVE_SHIFT;
1819 modstate &= ~GLUT_ACTIVE_SHIFT;
1821 if(GetKeyState(VK_CONTROL) & 0x8000) {
1822 modstate |= GLUT_ACTIVE_CTRL;
1824 modstate &= ~GLUT_ACTIVE_CTRL;
1826 if(GetKeyState(VK_MENU) & 0x8000) {
1827 modstate |= GLUT_ACTIVE_ALT;
1829 modstate &= ~GLUT_ACTIVE_ALT;
1834 #define VK_OEM_1 0xba
1835 #define VK_OEM_2 0xbf
1836 #define VK_OEM_3 0xc0
1837 #define VK_OEM_4 0xdb
1838 #define VK_OEM_5 0xdc
1839 #define VK_OEM_6 0xdd
1840 #define VK_OEM_7 0xde
1843 static int translate_vkey(int vkey)
1846 case VK_PRIOR: return GLUT_KEY_PAGE_UP;
1847 case VK_NEXT: return GLUT_KEY_PAGE_DOWN;
1848 case VK_END: return GLUT_KEY_END;
1849 case VK_HOME: return GLUT_KEY_HOME;
1850 case VK_LEFT: return GLUT_KEY_LEFT;
1851 case VK_UP: return GLUT_KEY_UP;
1852 case VK_RIGHT: return GLUT_KEY_RIGHT;
1853 case VK_DOWN: return GLUT_KEY_DOWN;
1854 case VK_OEM_1: return ';';
1855 case VK_OEM_2: return '/';
1856 case VK_OEM_3: return '`';
1857 case VK_OEM_4: return '[';
1858 case VK_OEM_5: return '\\';
1859 case VK_OEM_6: return ']';
1860 case VK_OEM_7: return '\'';
1865 if(vkey >= 'A' && vkey <= 'Z') {
1867 } else if(vkey >= VK_F1 && vkey <= VK_F12) {
1868 vkey -= VK_F1 + GLUT_KEY_F1;
1874 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam)
1881 x = lparam & 0xffff;
1883 cb_mouse(bn, st ? GLUT_DOWN : GLUT_UP, x, y);
1887 static void get_window_pos(int *x, int *y)
1890 GetWindowRect(win, &rect);
1895 static void get_window_size(int *w, int *h)
1898 GetClientRect(win, &rect);
1899 *w = rect.right - rect.left;
1900 *h = rect.bottom - rect.top;
1903 static void get_screen_size(int *scrw, int *scrh)
1905 *scrw = GetSystemMetrics(SM_CXSCREEN);
1906 *scrh = GetSystemMetrics(SM_CYSCREEN);
1908 #endif /* BUILD_WIN32 */
1910 #if defined(unix) || defined(__unix__) || defined(__APPLE__)
1911 #include <sys/time.h>
1913 #ifdef MINIGLUT_USE_LIBC
1914 #define sys_gettimeofday(tv, tz) gettimeofday(tv, tz)
1916 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz);
1919 static long get_msec(void)
1921 static struct timeval tv0;
1924 sys_gettimeofday(&tv, 0);
1925 if(tv0.tv_sec == 0 && tv0.tv_usec == 0) {
1929 return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
1933 static long get_msec(void)
1938 #ifdef MINIGLUT_NO_WINMM
1939 tm = GetTickCount();
1951 static void panic(const char *msg)
1953 const char *end = msg;
1955 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);