// rendergl.cpp: core opengl rendering stuff #define gamma math_gamma #include "cube.h" #import "Command.h" #import "Monster.h" #import "OFColor+Cube.h" #import "OFString+Cube.h" #import "Player.h" #import "Variable.h" #ifdef DARWIN # define GL_COMBINE_EXT GL_COMBINE_ARB # define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB # define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB # define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB # define GL_RGB_SCALE_EXT GL_RGB_SCALE_ARB #endif extern int curvert; bool hasoverbright = false; void purgetextures(); GLUquadricObj *qsphere = NULL; int glmaxtexsize = 256; void gl_init(int w, int h) { // #define fogvalues 0.5f, 0.6f, 0.7f, 1.0f glViewport(0, 0, w, h); glClearDepth(1.0); glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); glShadeModel(GL_SMOOTH); glEnable(GL_FOG); glFogi(GL_FOG_MODE, GL_LINEAR); glFogf(GL_FOG_DENSITY, 0.25); glHint(GL_FOG_HINT, GL_NICEST); glEnable(GL_LINE_SMOOTH); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_POLYGON_OFFSET_LINE); glPolygonOffset(-3.0, -3.0); glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); char *exts = (char *)glGetString(GL_EXTENSIONS); if (strstr(exts, "GL_EXT_texture_env_combine") || strstr(exts, "GL_ARB_texture_env_combine")) hasoverbright = true; else conoutf(@"WARNING: cannot use overbright lighting, using old " @"lighting model!"); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glmaxtexsize); purgetextures(); if (!(qsphere = gluNewQuadric())) fatal(@"glu sphere"); gluQuadricDrawStyle(qsphere, GLU_FILL); gluQuadricOrientation(qsphere, GLU_INSIDE); gluQuadricTexture(qsphere, GL_TRUE); glNewList(1, GL_COMPILE); gluSphere(qsphere, 1, 12, 6); glEndList(); } void cleangl() { if (qsphere) gluDeleteQuadric(qsphere); } bool installtex(int tnum, OFIRI *IRI, int *xs, int *ys, bool clamp) { SDL_Surface *s = IMG_Load(IRI.fileSystemRepresentation.UTF8String); if (s == NULL) { conoutf(@"couldn't load texture %@", IRI.string); return false; } if (s->format->BitsPerPixel != 24) { SDL_PixelFormat *format = SDL_AllocFormat(SDL_PIXELFORMAT_RGB24); if (format == NULL) { conoutf(@"texture cannot be converted to 24bpp: %@", IRI.string); return false; } @try { SDL_Surface *converted = SDL_ConvertSurface(s, format, 0); if (converted == NULL) { conoutf(@"texture cannot be converted " @"to 24bpp: %@", IRI.string); return false; } SDL_FreeSurface(s); s = converted; } @finally { SDL_FreeFormat(format); } } #if 0 for (int i = 0; i < s->w * s->h * 3; i++) { unsigned char *p = (unsigned char *)s->pixels + i; *p = 255 - *p; } #endif glBindTexture(GL_TEXTURE_2D, tnum); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // NEAREST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); *xs = s->w; *ys = s->h; while (*xs > glmaxtexsize || *ys > glmaxtexsize) { *xs /= 2; *ys /= 2; } void *scaledimg = s->pixels; if (*xs != s->w) { conoutf(@"warning: quality loss: scaling %@", IRI.string); // for voodoo cards under linux scaledimg = OFAllocMemory(1, *xs * *ys * 3); gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels, *xs, *ys, GL_UNSIGNED_BYTE, scaledimg); } if (gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, *xs, *ys, GL_RGB, GL_UNSIGNED_BYTE, scaledimg)) fatal(@"could not build mipmaps"); if (*xs != s->w) free(scaledimg); SDL_FreeSurface(s); return true; } // management of texture slots // each texture slot can have multople texture frames, of which currently only // the first is used additional frames can be used for various shaders #define MAXTEX 1000 static int texx[MAXTEX]; // ( loaded texture ) -> ( name, size ) static int texy[MAXTEX]; static OFString *texname[MAXTEX]; static int curtex = 0; static const int FIRSTTEX = 1000; // opengl id = loaded id + FIRSTTEX // std 1+, sky 14+, mdls 20+ // increase to allow more complex shader defs #define MAXFRAMES 2 // ( cube texture, frame ) -> ( opengl id, name ) static int mapping[256][MAXFRAMES]; static OFString *mapname[256][MAXFRAMES]; void purgetextures() { for (int i = 0; i < 256; i++) for (int j = 0; j < MAXFRAMES; j++) mapping[i][j] = 0; } int curtexnum = 0; COMMAND(texturereset, ARG_NONE, ^ { curtexnum = 0; }) COMMAND(texture, ARG_2STR, (^ (OFString *aframe, OFString *name) { int num = curtexnum++, frame = aframe.cube_intValue; if (num < 0 || num >= 256 || frame < 0 || frame >= MAXFRAMES) return; mapping[num][frame] = 1; mapname[num][frame] = [name stringByReplacingOccurrencesOfString: @"\\" withString: @"/"]; })) int lookuptexture(int tex, int *xs, int *ys) { int frame = 0; // other frames? int tid = mapping[tex][frame]; if (tid >= FIRSTTEX) { *xs = texx[tid - FIRSTTEX]; *ys = texy[tid - FIRSTTEX]; return tid; } *xs = *ys = 16; if (tid == 0) return 1; // crosshair :) // lazily happens once per "texture" command, basically for (int i = 0; i < curtex; i++) { if ([mapname[tex][frame] isEqual: texname[i]]) { mapping[tex][frame] = tid = i + FIRSTTEX; *xs = texx[i]; *ys = texy[i]; return tid; } } if (curtex == MAXTEX) fatal(@"loaded too many textures"); int tnum = curtex + FIRSTTEX; texname[curtex] = mapname[tex][frame]; OFString *path = [OFString stringWithFormat: @"packages/%@", texname[curtex]]; if (installtex(tnum, [Cube.sharedInstance.gameDataIRI IRIByAppendingPathComponent: path], xs, ys, false)) { mapping[tex][frame] = tnum; texx[curtex] = *xs; texy[curtex] = *ys; curtex++; return tnum; } else { return mapping[tex][frame] = FIRSTTEX; // temp fix } } static void gl_setupworld() { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); setarraypointers(); if (hasoverbright) { glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); } } int skyoglid; struct strip { int tex, start, num; }; static OFMutableData *strips; void renderstripssky() { glBindTexture(GL_TEXTURE_2D, skyoglid); const struct strip *items = strips.items; size_t count = strips.count; for (size_t i = 0; i < count; i++) if (items[i].tex == skyoglid) glDrawArrays( GL_TRIANGLE_STRIP, items[i].start, items[i].num); } void renderstrips() { int lasttex = -1; const struct strip *items = strips.items; size_t count = strips.count; for (size_t i = 0; i < count; i++) { if (items[i].tex == skyoglid) continue; if (items[i].tex != lasttex) { glBindTexture(GL_TEXTURE_2D, items[i].tex); lasttex = items[i].tex; } glDrawArrays(GL_TRIANGLE_STRIP, items[i].start, items[i].num); } } void overbright(float amount) { if (hasoverbright) glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, amount); } void addstrip(int tex, int start, int n) { if (strips == nil) strips = [[OFMutableData alloc] initWithItemSize: sizeof(struct strip)]; struct strip s = { .tex = tex, .start = start, .num = n }; [strips addItem: &s]; } #undef gamma static int gamma = 100; VARBP(gamma, 30, 300, ^ { return gamma; }, ^ (int value) { float f = value / 100.0f; Uint16 ramp[256]; SDL_CalculateGammaRamp(f, ramp); if (SDL_SetWindowGammaRamp(Cube.sharedInstance.window, ramp, ramp, ramp) != -1) gamma = value; else { conoutf( @"Could not set gamma (card/driver doesn't support it?)"); conoutf(@"sdl: %s", SDL_GetError()); } }) void transplayer() { Player *player1 = Player.player1; glLoadIdentity(); glRotated(player1.roll, 0.0, 0.0, 1.0); glRotated(player1.pitch, -1.0, 0.0, 0.0); glRotated(player1.yaw, 0.0, 1.0, 0.0); glTranslated(-player1.origin.x, (player1.state == CS_DEAD ? player1.eyeHeight - 0.2f : 0) - player1.origin.z, -player1.origin.y); } VARP(fov, 10, 105, 120); int xtraverts; VAR(fog, 64, 180, 1024); static OFColor *fogColor; VARB(fogcolour, 0, 0xFFFFFF, (^ { float red, green, blue; if (fogColor == nil) return 0x8099B3; [fogColor getRed: &red green: &green blue: &blue alpha: NULL]; return ((unsigned char)(red * 255.0f) << 16) | ((unsigned char)(green * 255.0f) << 8) | (unsigned char)(blue * 255.0f); }), ^ (int value) { unsigned char red = (value >> 16) & 0xFF; unsigned char green = (value >> 8) & 0xFF; unsigned char blue = value & 0xFF; fogColor = [OFColor colorWithRed: red / 255.0f green: green / 255.0f blue: blue / 255.0f alpha: 1.f]; }) VARP(hudgun, 0, 1, 1); OFString *hudgunnames[] = { @"hudguns/fist", @"hudguns/shotg", @"hudguns/chaing", @"hudguns/rocket", @"hudguns/rifle" }; void drawhudmodel(int start, int end, float speed, int base) { Player *player1 = Player.player1; rendermodel(hudgunnames[player1.gunSelect], start, end, 0, 1.0f, OFMakeVector3D(player1.origin.x, player1.origin.z, player1.origin.y), player1.yaw + 90, player1.pitch, false, 1.0f, speed, 0, base); } void drawhudgun(float fovy, float aspect, int farplane) { Player *player1 = Player.player1; if (!hudgun /*|| !player1.gunSelect*/) return; glEnable(GL_CULL_FACE); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(fovy, aspect, 0.3f, farplane); glMatrixMode(GL_MODELVIEW); // glClear(GL_DEPTH_BUFFER_BIT); int rtime = reloadtime(player1.gunSelect); if (player1.lastAction && player1.lastAttackGun == player1.gunSelect && lastmillis - player1.lastAction < rtime) { drawhudmodel(7, 18, rtime / 18.0f, player1.lastAction); } else drawhudmodel(6, 1, 100, 0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(fovy, aspect, 0.15f, farplane); glMatrixMode(GL_MODELVIEW); glDisable(GL_CULL_FACE); } void gl_drawframe(int w, int h, float curfps) { Player *player1 = Player.player1; float hf = hdr.waterlevel - 0.3f; float fovy = (float)fov * h / w; float aspect = w / (float)h; bool underwater = (player1.origin.z < hf); glFogi(GL_FOG_START, (fog + 64) / 8); glFogi(GL_FOG_END, fog); [fogColor cube_setAsGLFogColor]; [fogColor cube_setAsGLClearColor]; if (underwater) { fovy += (float)sin(lastmillis / 1000.0) * 2.0f; aspect += (float)sin(lastmillis / 1000.0 + PI) * 0.1f; glFogi(GL_FOG_START, 0); glFogi(GL_FOG_END, (fog + 96) / 8); } glClear((player1.outsideMap ? GL_COLOR_BUFFER_BIT : 0) | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); int farplane = fog * 5 / 2; gluPerspective(fovy, aspect, 0.15f, farplane); glMatrixMode(GL_MODELVIEW); transplayer(); glEnable(GL_TEXTURE_2D); int xs, ys; skyoglid = lookuptexture(DEFAULT_SKY, &xs, &ys); resetcubes(); curvert = 0; [strips removeAllItems]; render_world(player1.origin.x, player1.origin.y, player1.origin.z, (int)player1.yaw, (int)player1.pitch, (float)fov, w, h); finishstrips(); gl_setupworld(); renderstripssky(); glLoadIdentity(); glRotated(player1.pitch, -1.0, 0.0, 0.0); glRotated(player1.yaw, 0.0, 1.0, 0.0); glRotated(90.0, 1.0, 0.0, 0.0); [OFColor.white cube_setAsGLColor]; glDisable(GL_FOG); glDepthFunc(GL_GREATER); draw_envbox(14, fog * 4 / 3); glDepthFunc(GL_LESS); glEnable(GL_FOG); transplayer(); overbright(2); renderstrips(); xtraverts = 0; renderclients(); [Monster renderAll]; renderentities(); renderspheres(curtime); renderents(); glDisable(GL_CULL_FACE); drawhudgun(fovy, aspect, farplane); overbright(1); int nquads = renderwater(hf); overbright(2); render_particles(curtime); overbright(1); glDisable(GL_FOG); glDisable(GL_TEXTURE_2D); gl_drawhud(w, h, (int)curfps, nquads, curvert, underwater); glEnable(GL_CULL_FACE); glEnable(GL_FOG); }