diff --git a/src/4dt_gtxt.c b/src/4dt_gtxt.c index 55ded56..c290b54 100644 --- a/src/4dt_gtxt.c +++ b/src/4dt_gtxt.c @@ -10,9 +10,13 @@ #include #include #include +#ifndef __EMSCRIPTEN__ #include +#endif #include +#ifndef __EMSCRIPTEN__ #include +#endif #include "4dt_main.h" @@ -39,6 +43,7 @@ int nextpoweroftwo(int x) } +#ifndef __EMSCRIPTEN__ void SDL_GL_RenderText(char *text, TTF_Font *font, SDL_Color color, @@ -105,13 +110,150 @@ void SDL_GL_RenderText(char *text, SDL_FreeSurface(intermediary); glDeleteTextures(1, &texture); } +#endif +#ifdef __EMSCRIPTEN__ +/* The browser build has no fontconfig or SDL_ttf: the text is drawn with FreeType from the Liberation + * Sans Bold Italic (SIL Open Font License) that comes with the game. */ +#include +#include FT_FREETYPE_H + +static FT_Library ftLibrary; +static FT_Face ftFace; +static int ftSize = 0; + +static int ftLoad(int px) +{ + if (!ftLibrary && FT_Init_FreeType(&ftLibrary)) return 0; + if (!ftFace && FT_New_Face(ftLibrary, "/fonts/LiberationSans-BoldItalic.ttf", 0, &ftFace)) return 0; + if (ftSize != px) { FT_Set_Pixel_Sizes(ftFace, 0, px); ftSize = px; } + return 1; +} + +/* Next code point of a UTF-8 string */ +static unsigned ftNext(const unsigned char **p) +{ + unsigned c = *(*p)++; + int extra = 0; + if (c >= 0xF0) { c &= 0x07; extra = 3; } + else if (c >= 0xE0) { c &= 0x0F; extra = 2; } + else if (c >= 0xC0) { c &= 0x1F; extra = 1; } + while (extra-- > 0 && (**p & 0xC0) == 0x80) c = (c << 6) | (*(*p)++ & 0x3F); + return c; +} + +/** Draw text (the lower left corner at the given position, in pixels) with the given color and alpha. */ +static void ftRenderText(const char *text, SDL_Color color, int *x, int *y, float alpha, int px) +{ + const unsigned char *p; + int ascent, descent, width = 0, height, w, h, pen = 0, i; + unsigned char *pixels; + GLuint texture; + + if (!ftLoad(px)) return; + ascent = (int) ((ftFace->size->metrics.ascender + 63) >> 6); + descent = (int) ((-ftFace->size->metrics.descender + 63) >> 6); + height = ascent + descent; + for (p = (const unsigned char *) text; *p;) + { + if (FT_Load_Char(ftFace, ftNext(&p), FT_LOAD_DEFAULT)) continue; + width += (int) (ftFace->glyph->advance.x >> 6); + } + if (width < 1) width = 1; + w = nextpoweroftwo(width); + h = nextpoweroftwo(height); + pixels = (unsigned char *) calloc((size_t) w * h, 4); + for (p = (const unsigned char *) text; *p;) + { + FT_Bitmap *bm; + int gx, gy, row, col; + if (FT_Load_Char(ftFace, ftNext(&p), FT_LOAD_RENDER)) continue; + bm = &ftFace->glyph->bitmap; + gx = pen + ftFace->glyph->bitmap_left; + gy = ascent - ftFace->glyph->bitmap_top; + for (row = 0; row < (int) bm->rows; row++) + for (col = 0; col < (int) bm->width; col++) + { + int dx = gx + col, dy = gy + row; + unsigned char cov = bm->buffer[row * bm->pitch + col], *d; + if (dx < 0 || dx >= w || dy < 0 || dy >= h) continue; + d = pixels + ((size_t) dy * w + dx) * 4; + if (cov > d[3]) { d[0] = color.r; d[1] = color.g; d[2] = color.b; d[3] = cov; } + } + pen += (int) (ftFace->glyph->advance.x >> 6); + } + + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glEnable(GL_TEXTURE_2D); + glColor4f(alpha, alpha, alpha, alpha); + glBegin(GL_QUADS); + glTexCoord2f(0.0f, 1.0f); glVertex2f(*x, *y); + glTexCoord2f(1.0f, 1.0f); glVertex2f(*x + w, *y); + glTexCoord2f(1.0f, 0.0f); glVertex2f(*x + w, *y + h); + glTexCoord2f(0.0f, 0.0f); glVertex2f(*x, *y + h); + glEnd(); + glFinish(); + glDeleteTextures(1, &texture); + /* a deleted texture left bound and enabled would make WebGL draw everything after it black */ + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + free(pixels); + (void) i; +} +#endif + /** Draw bitmap string to the specified coordinates and font. */ void g3dRenderString(double x, double y, float color[4], char *string) { +#ifdef __EMSCRIPTEN__ + SDL_Color colorf; + SDL_Rect position; + int vPort[4]; + + glDisable(GL_LIGHTING); + glDisable(GL_TEXTURE_2D); + glGetIntegerv(GL_VIEWPORT, vPort); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, vPort[2], 0, vPort[3], -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glDisable(GL_DEPTH_TEST); + glBlendFunc(GL_ONE, GL_ONE); + glEnable(GL_BLEND); + + colorf.r = 255 * color[0]; + colorf.g = 255 * color[1]; + colorf.b = 255 * color[2]; + position.x = ((SDL_Surface *)getSDLScreen())->w * x; + position.y = ((SDL_Surface *)getSDLScreen())->h * y; + { + int px = ((SDL_Surface *)getSDLScreen())->h / 24, ix = position.x, iy = position.y; + ftRenderText(gettext(string), colorf, &ix, &iy, color[3], px); + } + + glEnable(GL_DEPTH_TEST); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glDisable(GL_BLEND); + glEnable(GL_LIGHTING); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +#else + const char *font_full_name = "Liberation Sans Bold Italic"; SDL_Color colorf; SDL_Rect position; @@ -183,6 +325,7 @@ void g3dRenderString(double x, double y, glDisable(GL_BLEND); glEnable(GL_LIGHTING); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +#endif } diff --git a/src/4dt_main.c b/src/4dt_main.c index be5db4e..0297b47 100644 --- a/src/4dt_main.c +++ b/src/4dt_main.c @@ -47,7 +47,11 @@ #include #include +#ifndef __EMSCRIPTEN__ #include +#else +#include +#endif #include "4dt_m3d.h" #include "4dt_m4d.h" @@ -94,17 +98,103 @@ void *getSDLScreen(void) { return((void *)screen); } */ /** Sets a timer to call back the function passed after given time (msec) */ +#ifdef __EMSCRIPTEN__ +/* The SDL_AddTimer of the SDL 1.2 of Emscripten runs the callback once and ignores the interval it returns, + * but this game's clock (the falling solid, the drop, the animations, the auto player) reschedules itself + * that way. These timers are run by webFrame, once per frame, with the semantics of SDL 1.2: the return + * value is the next interval, and 0 ends the timer. */ +#define WEB_MAX_TIMERS 64 +static struct +{ + unsigned id; + Uint32 next; + Uint32 interval; + int (*callback)(int interval, void *param); + void *param; +} webTimers[WEB_MAX_TIMERS]; +static unsigned webLastTimerId = 0; + +static int *webAddTimer(int time, int (*callback)(int interval, void *param), void *param) +{ + int i; + + for (i = 0; i < WEB_MAX_TIMERS; i++) + { + if (webTimers[i].id == 0) + { + webTimers[i].id = ++webLastTimerId; + webTimers[i].interval = time > 0 ? time : 1; + webTimers[i].next = SDL_GetTicks() + webTimers[i].interval; + webTimers[i].callback = callback; + webTimers[i].param = param; + return((int *)(size_t)webTimers[i].id); + } + } + return(NULL); +} + +static void webRemoveTimer(int *id) +{ + int i; + + for (i = 0; i < WEB_MAX_TIMERS; i++) + { + if (id != NULL && webTimers[i].id == (unsigned)(size_t)id) + { + webTimers[i].id = 0; + } + } +} + +static void webRunTimers(void) +{ + Uint32 now = SDL_GetTicks(); + int i; + + for (i = 0; i < WEB_MAX_TIMERS; i++) + { + if (webTimers[i].id != 0 && (Sint32)(now - webTimers[i].next) >= 0) + { + unsigned id = webTimers[i].id; + int again = webTimers[i].callback((int)webTimers[i].interval, webTimers[i].param); + + /* the callback may have removed this timer */ + if (webTimers[i].id == id) + { + if (again <= 0) + { + webTimers[i].id = 0; + } + else + { + webTimers[i].interval = again; + webTimers[i].next = now + again; + } + } + } + } +} +#endif + int *setTimerCallback(int time, int (*callback)(int interval, void *param), void *param) { +#ifdef __EMSCRIPTEN__ + return(webAddTimer(time, callback, param)); +#else return(SDL_AddTimer(time, callback, param)); +#endif } /** Remove the previously set timer */ void clearTimerCallback(int *id) { +#ifdef __EMSCRIPTEN__ + webRemoveTimer(id); +#else SDL_RemoveTimer(id); +#endif return; } @@ -130,6 +220,71 @@ static void processARGV(int argc, char *argv[]) /*------------------------------------------------------------------------------ M A I N */ + +#ifdef __EMSCRIPTEN__ +/* The game state of the main loop, which the browser runs one frame at a time. */ +static int webDone = 0; +static tEngGame *webEngGame, webEngGameDraw; +static tScnSet *webScnSet, webScnSetDraw; + +static void webFrame(void) +{ + Uint8 *keys; + int uiKey; + SDL_Event event; + + while ( SDL_PollEvent(&event) ) + { + switch(event.type) + { + case SDL_QUIT: + { + webDone = 1; + break; + } + case SDL_KEYDOWN: + { + keys = SDL_GetKeyState(NULL); + uiKey = 0; + if(keys[SDLK_UP]) { uiKey = UI_KEY_UP; } + if(keys[SDLK_LEFT]) { uiKey = UI_KEY_LEFT; } + if(keys[SDLK_DOWN]) { uiKey = UI_KEY_DOWN; } + if(keys[SDLK_RIGHT]) { uiKey = UI_KEY_RIGHT; } + if(keys[SDLK_PAGEUP]) { uiKey = UI_KEY_PGUP; } + if(keys[SDLK_PAGEDOWN]) { uiKey = UI_KEY_PGDOWN; } + if(keys[SDLK_HOME]) { uiKey = UI_KEY_HOME; } + if(keys[SDLK_END]) { uiKey = UI_KEY_END; } + if(keys[SDLK_F1]) { uiKey = UI_KEY_F1; } + if(keys[SDLK_F2]) { uiKey = UI_KEY_F2; } + if(keys[SDLK_ESCAPE]) { uiKey = UI_KEY_ESC; } + if(keys[SDLK_BACKSPACE]) { uiKey = UI_KEY_BACKSPACE;} + if(keys[SDLK_TAB]) { uiKey = UI_KEY_TAB; } + if(keys[SDLK_RETURN]) { uiKey = UI_KEY_ENTER; } + if(keys[SDLK_SPACE]) { uiKey = ' '; } + if(keys[SDLK_a]) { uiKey = 'a'; } + if(keys[SDLK_z]) { uiKey = 'z'; } + if(keys[SDLK_x]) { uiKey = 'x'; } + if(keys[SDLK_c]) { uiKey = 'c'; } + if(keys[SDLK_v]) { uiKey = 'v'; } + if(keys[SDLK_1]) { uiKey = '1'; } + if(keys[SDLK_2]) { uiKey = '2'; } + if(keys[SDLK_3]) { uiKey = '3'; } + if(keys[SDLK_KP_PLUS]) { uiKey = '+'; } + if(keys[SDLK_KP_MINUS]) { uiKey = '-'; } + if(uiKey != 0) { uiKeyPress(uiKey, webEngGame, webScnSet); } + } + } + } + + webRunTimers(); + + webEngGameDraw = *webEngGame; + webScnSetDraw = *webScnSet; + scnDisplay(&webEngGameDraw, &webScnSetDraw); + + if (webDone) emscripten_cancel_main_loop(); +} +#endif /** Main function of the software */ int main(int argc, char *argv[]) @@ -164,7 +319,9 @@ int main(int argc, char *argv[]) exit(2); } SDL_WM_SetCaption("4DTris", "4dtris"); +#ifndef __EMSCRIPTEN__ TTF_Init(); +#endif /* initialise 3D drawing modul */ g3dInit(); @@ -182,6 +339,13 @@ int main(int argc, char *argv[]) aiSetActive(1, &engGame); g3dResize(screen->w, screen->h); +#ifdef __EMSCRIPTEN__ + /* The engine's timers keep pointers to these variables; the loop never returns, so they stay valid */ + webEngGame = &engGame; + webScnSet = &scnSet; + emscripten_set_main_loop(webFrame, 0, 1); + return EXIT_SUCCESS; +#endif done = 0; while ( ! done ) @@ -261,7 +425,9 @@ int main(int argc, char *argv[]) } SDL_Delay(delay); } +#ifndef __EMSCRIPTEN__ TTF_Quit(); +#endif SDL_Quit(); /* Return with successed exit constant. */ diff --git a/src/4dt_menu.c b/src/4dt_menu.c index 7c76b17..b7b2f5f 100644 --- a/src/4dt_menu.c +++ b/src/4dt_menu.c @@ -83,7 +83,12 @@ static tMenuItem menuItems[eMenuItemNum] = /* en, caption, activate, deact., parent, submenus */ {0, "", NULL, NULL, eMenuNull, {eMenuNull}}, {0, "", NULL, NULL, eMenuOFF, {eMenuRoot, eMenuNull}}, +#ifdef __EMSCRIPTEN__ + /* no Quit in a browser tab */ + {1, "Main menu", NULL, NULL, eMenuOFF, {eMenuNewGame, eMenuOptions, eMenuHelp, eMenuBackToGame, eMenuNull}}, +#else {1, "Main menu", NULL, NULL, eMenuOFF, {eMenuNewGame, eMenuOptions, eMenuHelp, eMenuBackToGame, eMenuQuit, eMenuNull}}, +#endif {1, "New Game", menuNew, NULL, eMenuRoot, {eMenuNull} }, {1, "Options", NULL, NULL, eMenuRoot, {eMenuVideoOptions, eMenuAudioOptions, eMenuGameOptions, eMenuNull} }, {1, "Video Options", NULL, NULL, eMenuOptions, {eMenuAnimation, eMenuDrawmode, eMenuGrid, eMenuStereo, eMenuNull} }, diff --git a/src/4dt_web.h b/src/4dt_web.h new file mode 100644 index 0000000..c4cca5c --- /dev/null +++ b/src/4dt_web.h @@ -0,0 +1,185 @@ +/* + * 4dt_web.h: what the WebAssembly build of 4DTris (fredygames.com) needs on top of the OpenGL emulation of + * Emscripten (-sLEGACY_GL_EMULATION), which maps fixed-function OpenGL 1.x onto WebGL. Included in every + * file of the game with -include, only when building with Emscripten. + * + * - glBegin/glEnd: the emulation stores the attributes of every vertex in the order the calls come and + * expects all vertices to carry the same ones, while desktop OpenGL keeps a current color, texture + * coordinate and normal. The vertices are collected here with the current attributes and handed over + * complete at glEnd. A color set inside glBegin/glEnd stays current afterwards, as in OpenGL. + * - The double precision forms of the vertex, normal and color calls, which the emulation lacks. + * - gluNewQuadric, gluSphere and gluCylinder (the game's balls and bars), which Emscripten's GLU lacks, as + * triangles with smooth normals. + * - glTexImage2D: WebGL wants the internal format equal to the format (the game passes the number 4). + * + * Copyright (C) 2026 FredyGames. Part of the 4DTris build for fredygames.com; distributed with 4DTris under + * the GNU General Public License, version 3. + */ +#ifndef FDT_WEB_H +#define FDT_WEB_H + +#include +#include +#include +#include +#include + +struct fdt_vertex { GLfloat color[4], tc[2], normal[3], pos[3]; }; + +static GLfloat fdt_color[4] = { 1, 1, 1, 1 }; +static GLfloat fdt_tc[2] = { 0, 0 }; +static GLfloat fdt_normal[3] = { 0, 0, 1 }; +static GLenum fdt_mode; +static int fdt_in_begin = 0, fdt_used_color = 0, fdt_used_tc = 0, fdt_used_normal = 0; +static struct fdt_vertex *fdt_vertices = NULL; +static int fdt_count = 0, fdt_capacity = 0; + +static void fdt_glBegin(GLenum mode) +{ + fdt_mode = mode; + fdt_in_begin = 1; + fdt_used_color = fdt_used_tc = fdt_used_normal = 0; + fdt_count = 0; +} + +static void fdt_glColor4f(GLfloat r, GLfloat g, GLfloat b, GLfloat a) +{ + fdt_color[0] = r; fdt_color[1] = g; fdt_color[2] = b; fdt_color[3] = a; + if (fdt_in_begin) fdt_used_color = 1; else glColor4f(r, g, b, a); +} + +static void fdt_glTexCoord2f(GLfloat s, GLfloat t) +{ + fdt_tc[0] = s; fdt_tc[1] = t; + if (fdt_in_begin) fdt_used_tc = 1; +} + +static void fdt_glNormal3f(GLfloat x, GLfloat y, GLfloat z) +{ + fdt_normal[0] = x; fdt_normal[1] = y; fdt_normal[2] = z; + if (fdt_in_begin) fdt_used_normal = 1; else glNormal3f(x, y, z); +} + +static void fdt_glVertex3f(GLfloat x, GLfloat y, GLfloat z) +{ + struct fdt_vertex *v; + if (!fdt_in_begin) return; + if (fdt_count == fdt_capacity) + { + fdt_capacity = fdt_capacity ? fdt_capacity * 2 : 256; + fdt_vertices = (struct fdt_vertex *) realloc(fdt_vertices, fdt_capacity * sizeof(struct fdt_vertex)); + } + v = &fdt_vertices[fdt_count++]; + memcpy(v->color, fdt_color, sizeof(fdt_color)); + memcpy(v->tc, fdt_tc, sizeof(fdt_tc)); + memcpy(v->normal, fdt_normal, sizeof(fdt_normal)); + v->pos[0] = x; v->pos[1] = y; v->pos[2] = z; +} + +static void fdt_glVertex2f(GLfloat x, GLfloat y) { fdt_glVertex3f(x, y, 0.0f); } + +static void fdt_glEnd(void) +{ + int i; + if (!fdt_in_begin) return; + fdt_in_begin = 0; + if (fdt_count == 0) return; + /* the game calls glBegin(GL_LINE), which is not a primitive (an OpenGL error and nothing drawn) */ + if (fdt_mode > GL_POLYGON) return; + glBegin(fdt_mode); + for (i = 0; i < fdt_count; ++i) + { + const struct fdt_vertex *v = &fdt_vertices[i]; + if (fdt_used_color) glColor4f(v->color[0], v->color[1], v->color[2], v->color[3]); + if (fdt_used_tc) glTexCoord2f(v->tc[0], v->tc[1]); + if (fdt_used_normal) glNormal3f(v->normal[0], v->normal[1], v->normal[2]); + glVertex3f(v->pos[0], v->pos[1], v->pos[2]); + } + glEnd(); + if (fdt_used_color) glColor4f(fdt_color[0], fdt_color[1], fdt_color[2], fdt_color[3]); +} + +static void fdt_glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, const void *pixels) +{ + glTexImage2D(target, level, (GLint) format, width, height, border, format, type, pixels); +} + +/* Quadrics: only what the game uses (smooth normals, outside orientation, filled). */ +struct fdt_quadric { int dummy; }; +static GLUquadric *fdt_gluNewQuadric(void) { return (GLUquadric *) calloc(1, sizeof(struct fdt_quadric)); } +static void fdt_gluDeleteQuadric(GLUquadric *q) { free(q); } +static void fdt_gluQuadricNormals(GLUquadric *q, GLenum n) { (void) q; (void) n; } +static void fdt_gluQuadricOrientation(GLUquadric *q, GLenum o) { (void) q; (void) o; } + +static void fdt_gluSphere(GLUquadric *q, GLdouble radius, GLint slices, GLint stacks) +{ + int i, j; + (void) q; + for (i = 0; i < stacks; i++) + { + double p0 = M_PI * i / stacks - M_PI / 2, p1 = M_PI * (i + 1) / stacks - M_PI / 2; + fdt_glBegin(GL_TRIANGLES); + for (j = 0; j < slices; j++) + { + double t0 = 2 * M_PI * j / slices, t1 = 2 * M_PI * (j + 1) / slices; + double n[4][3] = { + { cos(p0) * cos(t0), cos(p0) * sin(t0), sin(p0) }, { cos(p0) * cos(t1), cos(p0) * sin(t1), sin(p0) }, + { cos(p1) * cos(t1), cos(p1) * sin(t1), sin(p1) }, { cos(p1) * cos(t0), cos(p1) * sin(t0), sin(p1) } }; + static const int tri[6] = { 0, 1, 2, 0, 2, 3 }; + int k; + for (k = 0; k < 6; k++) + { + const double *v = n[tri[k]]; + fdt_glNormal3f(v[0], v[1], v[2]); + fdt_glVertex3f(radius * v[0], radius * v[1], radius * v[2]); + } + } + fdt_glEnd(); + } +} + +static void fdt_gluCylinder(GLUquadric *q, GLdouble base, GLdouble top, GLdouble height, GLint slices, + GLint stacks) +{ + int i, j; + (void) q; + for (i = 0; i < stacks; i++) + { + double z0 = height * i / stacks, z1 = height * (i + 1) / stacks; + double r0 = base + (top - base) * i / stacks, r1 = base + (top - base) * (i + 1) / stacks; + fdt_glBegin(GL_TRIANGLES); + for (j = 0; j < slices; j++) + { + double t0 = 2 * M_PI * j / slices, t1 = 2 * M_PI * (j + 1) / slices; + double c0 = cos(t0), s0 = sin(t0), c1 = cos(t1), s1 = sin(t1); + fdt_glNormal3f(c0, s0, 0); fdt_glVertex3f(r0 * c0, r0 * s0, z0); + fdt_glNormal3f(c1, s1, 0); fdt_glVertex3f(r0 * c1, r0 * s1, z0); + fdt_glNormal3f(c1, s1, 0); fdt_glVertex3f(r1 * c1, r1 * s1, z1); + fdt_glNormal3f(c0, s0, 0); fdt_glVertex3f(r0 * c0, r0 * s0, z0); + fdt_glNormal3f(c1, s1, 0); fdt_glVertex3f(r1 * c1, r1 * s1, z1); + fdt_glNormal3f(c0, s0, 0); fdt_glVertex3f(r1 * c0, r1 * s0, z1); + } + fdt_glEnd(); + } +} + +#define glBegin fdt_glBegin +#define glEnd fdt_glEnd +#define glColor4f fdt_glColor4f +#define glColor4d(r, g, b, a) fdt_glColor4f((GLfloat) (r), (GLfloat) (g), (GLfloat) (b), (GLfloat) (a)) +#define glTexCoord2f fdt_glTexCoord2f +#define glNormal3f fdt_glNormal3f +#define glNormal3d(x, y, z) fdt_glNormal3f((GLfloat) (x), (GLfloat) (y), (GLfloat) (z)) +#define glVertex3f fdt_glVertex3f +#define glVertex3d(x, y, z) fdt_glVertex3f((GLfloat) (x), (GLfloat) (y), (GLfloat) (z)) +#define glVertex2f fdt_glVertex2f +#define glTexImage2D fdt_glTexImage2D +#define gluNewQuadric fdt_gluNewQuadric +#define gluDeleteQuadric fdt_gluDeleteQuadric +#define gluQuadricNormals fdt_gluQuadricNormals +#define gluQuadricOrientation fdt_gluQuadricOrientation +#define gluSphere fdt_gluSphere +#define gluCylinder fdt_gluCylinder + +#endif