// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2020 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file r_data.c /// \brief Preparation of data for rendering, generation of lookups, caching, retrieval by name #include "doomdef.h" #include "g_game.h" #include "i_video.h" #include "r_local.h" #include "r_sky.h" #include "p_local.h" #include "m_misc.h" #include "r_data.h" #include "r_textures.h" #include "r_patch.h" #include "r_picformats.h" #include "w_wad.h" #include "z_zone.h" #include "p_setup.h" // levelflats #include "v_video.h" // pMasterPalette #include "f_finale.h" // wipes #include "byteptr.h" #include "dehacked.h" #ifdef _WIN32 #include // alloca(sizeof) #endif // // Graphics. // SRB2 graphics for walls and sprites // is stored in vertical runs of opaque pixels (posts). // A column is composed of zero or more posts, // a patch or sprite is composed of zero or more columns. // size_t numspritelumps, max_spritelumps; // needed for pre rendering sprcache_t *spritecachedinfo; lighttable_t *colormaps; lighttable_t *fadecolormap; // for debugging/info purposes size_t flatmemory, spritememory, texturememory; // highcolor stuff INT16 color8to16[256]; // remap color index to highcolor rgb value INT16 *hicolormaps; // test a 32k colormap remaps high -> high // Blends two pixels together, using the equation // that matches the specified alpha style. UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha) { RGBA_t output; INT16 fullalpha = (alpha - (0xFF - foreground.s.alpha)); if (style == AST_TRANSLUCENT) { if (fullalpha <= 0) output.rgba = background.rgba; else { // don't go too high if (fullalpha >= 0xFF) fullalpha = 0xFF; alpha = (UINT8)fullalpha; // if the background pixel is empty, // match software and don't blend anything if (!background.s.alpha) { // ...unless the foreground pixel ISN'T actually translucent. if (alpha == 0xFF) output.rgba = foreground.rgba; else output.rgba = 0; } else { UINT8 beta = (0xFF - alpha); output.s.red = ((background.s.red * beta) + (foreground.s.red * alpha)) / 0xFF; output.s.green = ((background.s.green * beta) + (foreground.s.green * alpha)) / 0xFF; output.s.blue = ((background.s.blue * beta) + (foreground.s.blue * alpha)) / 0xFF; output.s.alpha = 0xFF; } } return output.rgba; } #define clamp(c) max(min(c, 0xFF), 0x00); else { float falpha = ((float)alpha / 256.0f); float fr = ((float)foreground.s.red * falpha); float fg = ((float)foreground.s.green * falpha); float fb = ((float)foreground.s.blue * falpha); if (style == AST_ADD) { output.s.red = clamp((int)(background.s.red + fr)); output.s.green = clamp((int)(background.s.green + fg)); output.s.blue = clamp((int)(background.s.blue + fb)); } else if (style == AST_SUBTRACT) { output.s.red = clamp((int)(background.s.red - fr)); output.s.green = clamp((int)(background.s.green - fg)); output.s.blue = clamp((int)(background.s.blue - fb)); } else if (style == AST_REVERSESUBTRACT) { output.s.red = clamp((int)((-background.s.red) + fr)); output.s.green = clamp((int)((-background.s.green) + fg)); output.s.blue = clamp((int)((-background.s.blue) + fb)); } else if (style == AST_MODULATE) { fr = ((float)foreground.s.red / 256.0f); fg = ((float)foreground.s.green / 256.0f); fb = ((float)foreground.s.blue / 256.0f); output.s.red = clamp((int)(background.s.red * fr)); output.s.green = clamp((int)(background.s.green * fg)); output.s.blue = clamp((int)(background.s.blue * fb)); } // just copy the pixel else if (style == AST_COPY) output.rgba = foreground.rgba; output.s.alpha = 0xFF; return output.rgba; } #undef clamp return 0; } INT32 ASTTextureBlendingThreshold[2] = {255/11, (10*255/11)}; // Blends a pixel for a texture patch. UINT32 ASTBlendTexturePixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha) { // Alpha style set to translucent? if (style == AST_TRANSLUCENT) { // Is the alpha small enough for translucency? if (alpha <= ASTTextureBlendingThreshold[1]) { // Is the patch way too translucent? Don't blend then. if (alpha < ASTTextureBlendingThreshold[0]) return background.rgba; return ASTBlendPixel(background, foreground, style, alpha); } else // just copy the pixel return foreground.rgba; } else return ASTBlendPixel(background, foreground, style, alpha); } // Blends two palette indexes for a texture patch, then // finds the nearest palette index from the blended output. UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT8 alpha) { // Alpha style set to translucent? if (style == AST_TRANSLUCENT) { // Is the alpha small enough for translucency? if (alpha <= ASTTextureBlendingThreshold[1]) { UINT8 *mytransmap; // Is the patch way too translucent? Don't blend then. if (alpha < ASTTextureBlendingThreshold[0]) return background; // The equation's not exact but it works as intended. I'll call it a day for now. mytransmap = transtables + ((8*(alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT); if (background != 0xFF) return *(mytransmap + (background<<8) + foreground); } else // just copy the pixel return foreground; } // just copy the pixel else if (style == AST_COPY) return foreground; // use ASTBlendPixel for all other blend modes // and find the nearest colour in the palette else if (style != AST_TRANSLUCENT) { RGBA_t texel; RGBA_t bg = V_GetMasterColor(background); RGBA_t fg = V_GetMasterColor(foreground); texel.rgba = ASTBlendPixel(bg, fg, style, alpha); return NearestColor(texel.s.red, texel.s.green, texel.s.blue); } // fallback if all above fails, somehow // return the background pixel return background; } #ifdef EXTRACOLORMAPLUMPS static lumplist_t *colormaplumps = NULL; ///\todo free leak static size_t numcolormaplumps = 0; static inline lumpnum_t R_CheckNumForNameList(const char *name, lumplist_t *list, size_t listsize) { size_t i; UINT16 lump; for (i = listsize - 1; i < INT16_MAX; i--) { lump = W_CheckNumForNamePwad(name, list[i].wadfile, list[i].firstlump); if (lump == INT16_MAX || lump > (list[i].firstlump + list[i].numlumps)) continue; else return (list[i].wadfile<<16)+lump; } return LUMPERROR; } static void R_InitExtraColormaps(void) { lumpnum_t startnum, endnum; UINT16 cfile, clump; static size_t maxcolormaplumps = 16; for (cfile = clump = 0; cfile < numwadfiles; cfile++, clump = 0) { startnum = W_CheckNumForNamePwad("C_START", cfile, clump); if (startnum == INT16_MAX) continue; endnum = W_CheckNumForNamePwad("C_END", cfile, clump); if (endnum == INT16_MAX) I_Error("R_InitExtraColormaps: C_START without C_END\n"); // This shouldn't be possible when you use the Pwad function, silly //if (WADFILENUM(startnum) != WADFILENUM(endnum)) //I_Error("R_InitExtraColormaps: C_START and C_END in different wad files!\n"); if (numcolormaplumps >= maxcolormaplumps) maxcolormaplumps *= 2; colormaplumps = Z_Realloc(colormaplumps, sizeof (*colormaplumps) * maxcolormaplumps, PU_STATIC, NULL); colormaplumps[numcolormaplumps].wadfile = cfile; colormaplumps[numcolormaplumps].firstlump = startnum+1; colormaplumps[numcolormaplumps].numlumps = endnum - (startnum + 1); numcolormaplumps++; } CONS_Printf(M_GetText("Number of Extra Colormaps: %s\n"), sizeu1(numcolormaplumps)); } #endif // // R_InitSpriteLumps // Finds the width and hoffset of all sprites in the wad, so the sprite does not need to be // cached completely, just for having the header info ready during rendering. // // // allocate sprite lookup tables // static void R_InitSpriteLumps(void) { numspritelumps = 0; max_spritelumps = 8192; Z_Malloc(max_spritelumps*sizeof(*spritecachedinfo), PU_STATIC, &spritecachedinfo); } // // R_CreateFadeColormaps // static void R_CreateFadeColormaps(void) { UINT8 px, fade; RGBA_t rgba; INT32 r, g, b; size_t len, i; len = (256 * FADECOLORMAPROWS); fadecolormap = Z_MallocAlign(len*2, PU_STATIC, NULL, 8); for (i = 0; i < len*2; i++) fadecolormap[i] = (i%256); // Load in the light tables, now 64k aligned for smokie... { lumpnum_t lump = W_CheckNumForName("FADECMAP"); lumpnum_t wlump = W_CheckNumForName("FADEWMAP"); // to black if (lump != LUMPERROR) W_ReadLumpHeader(lump, fadecolormap, len, 0U); // to white if (wlump != LUMPERROR) W_ReadLumpHeader(wlump, fadecolormap+len, len, 0U); // missing "to white" colormap lump if (lump != LUMPERROR && wlump == LUMPERROR) goto makewhite; // missing "to black" colormap lump else if (lump == LUMPERROR && wlump != LUMPERROR) goto makeblack; // both lumps found else if (lump != LUMPERROR && wlump != LUMPERROR) return; } #define GETCOLOR \ px = colormaps[i%256]; \ fade = (i/256) * (256 / FADECOLORMAPROWS); \ rgba = V_GetMasterColor(px); // to black makeblack: for (i = 0; i < len; i++) { // find pixel and fade amount GETCOLOR; // subtractive color blending r = rgba.s.red - FADEREDFACTOR*fade/10; g = rgba.s.green - FADEGREENFACTOR*fade/10; b = rgba.s.blue - FADEBLUEFACTOR*fade/10; // clamp values if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; // find nearest color in palette fadecolormap[i] = NearestColor(r,g,b); } // to white makewhite: for (i = len; i < len*2; i++) { // find pixel and fade amount GETCOLOR; // additive color blending r = rgba.s.red + FADEREDFACTOR*fade/10; g = rgba.s.green + FADEGREENFACTOR*fade/10; b = rgba.s.blue + FADEBLUEFACTOR*fade/10; // clamp values if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; // find nearest color in palette fadecolormap[i] = NearestColor(r,g,b); } #undef GETCOLOR } // // R_InitColormaps // static void R_InitColormaps(void) { size_t len; lumpnum_t lump; // Load in the light tables lump = W_GetNumForName("COLORMAP"); len = W_LumpLength(lump); colormaps = Z_MallocAlign(len, PU_STATIC, NULL, 8); W_ReadLump(lump, colormaps); // Make colormap for fades R_CreateFadeColormaps(); // Init Boom colormaps. R_ClearColormaps(); #ifdef EXTRACOLORMAPLUMPS R_InitExtraColormaps(); #endif } void R_ReInitColormaps(UINT16 num) { char colormap[9] = "COLORMAP"; lumpnum_t lump; const lumpnum_t basecolormaplump = W_GetNumForName(colormap); if (num > 0 && num <= 10000) snprintf(colormap, 8, "CLM%04u", num-1); // Load in the light tables, now 64k aligned for smokie... lump = W_GetNumForName(colormap); if (lump == LUMPERROR) lump = basecolormaplump; else { if (W_LumpLength(lump) != W_LumpLength(basecolormaplump)) { CONS_Alert(CONS_WARNING, "%s lump size does not match COLORMAP, results may be unexpected.\n", colormap); } } W_ReadLumpHeader(lump, colormaps, W_LumpLength(basecolormaplump), 0U); if (fadecolormap) Z_Free(fadecolormap); R_CreateFadeColormaps(); // Init Boom colormaps. R_ClearColormaps(); } // // R_ClearColormaps // // Clears out extra colormaps between levels. // void R_ClearColormaps(void) { // Purged by PU_LEVEL, just overwrite the pointer extra_colormaps = R_CreateDefaultColormap(true); } // // R_CreateDefaultColormap() // NOTE: The result colormap is not added to the extra_colormaps chain. You must do that yourself! // extracolormap_t *R_CreateDefaultColormap(boolean lighttable) { extracolormap_t *exc = Z_Calloc(sizeof (*exc), PU_LEVEL, NULL); exc->fadestart = 0; exc->fadeend = 31; exc->flags = 0; exc->rgba = 0; exc->fadergba = 0x19000000; exc->colormap = lighttable ? R_CreateLightTable(exc) : NULL; #ifdef EXTRACOLORMAPLUMPS exc->lump = LUMPERROR; exc->lumpname[0] = 0; #endif exc->next = exc->prev = NULL; return exc; } // // R_GetDefaultColormap() // extracolormap_t *R_GetDefaultColormap(void) { #ifdef COLORMAPREVERSELIST extracolormap_t *exc; #endif if (!extra_colormaps) return (extra_colormaps = R_CreateDefaultColormap(true)); #ifdef COLORMAPREVERSELIST for (exc = extra_colormaps; exc->next; exc = exc->next); return exc; #else return extra_colormaps; #endif } // // R_CopyColormap() // NOTE: The result colormap is not added to the extra_colormaps chain. You must do that yourself! // extracolormap_t *R_CopyColormap(extracolormap_t *extra_colormap, boolean lighttable) { extracolormap_t *exc = Z_Calloc(sizeof (*exc), PU_LEVEL, NULL); if (!extra_colormap) extra_colormap = R_GetDefaultColormap(); *exc = *extra_colormap; exc->next = exc->prev = NULL; #ifdef EXTRACOLORMAPLUMPS strncpy(exc->lumpname, extra_colormap->lumpname, 9); if (exc->lump != LUMPERROR && lighttable) { // aligned on 8 bit for asm code exc->colormap = Z_MallocAlign(W_LumpLength(lump), PU_LEVEL, NULL, 16); W_ReadLump(lump, exc->colormap); } else #endif if (lighttable) exc->colormap = R_CreateLightTable(exc); else exc->colormap = NULL; return exc; } // // R_AddColormapToList // // Sets prev/next chain for extra_colormaps var // Copypasta from P_AddFFloorToList // void R_AddColormapToList(extracolormap_t *extra_colormap) { #ifndef COLORMAPREVERSELIST extracolormap_t *exc; #endif if (!extra_colormaps) { extra_colormaps = extra_colormap; extra_colormap->next = 0; extra_colormap->prev = 0; return; } #ifdef COLORMAPREVERSELIST extra_colormaps->prev = extra_colormap; extra_colormap->next = extra_colormaps; extra_colormaps = extra_colormap; extra_colormap->prev = 0; #else for (exc = extra_colormaps; exc->next; exc = exc->next); exc->next = extra_colormap; extra_colormap->prev = exc; extra_colormap->next = 0; #endif } // // R_CheckDefaultColormapByValues() // #ifdef EXTRACOLORMAPLUMPS boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams, INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags, lumpnum_t lump) #else boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams, INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags) #endif { return ( (!checkparams ? true : (fadestart == 0 && fadeend == 31 && !flags) ) && (!checkrgba ? true : rgba == 0) && (!checkfadergba ? true : fadergba == 0x19000000) #ifdef EXTRACOLORMAPLUMPS && lump == LUMPERROR && extra_colormap->lumpname[0] == 0 #endif ); } boolean R_CheckDefaultColormap(extracolormap_t *extra_colormap, boolean checkrgba, boolean checkfadergba, boolean checkparams) { if (!extra_colormap) return true; #ifdef EXTRACOLORMAPLUMPS return R_CheckDefaultColormapByValues(checkrgba, checkfadergba, checkparams, extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->flags, extra_colormap->lump); #else return R_CheckDefaultColormapByValues(checkrgba, checkfadergba, checkparams, extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->flags); #endif } boolean R_CheckEqualColormaps(extracolormap_t *exc_a, extracolormap_t *exc_b, boolean checkrgba, boolean checkfadergba, boolean checkparams) { // Treat NULL as default colormap // We need this because what if one exc is a default colormap, and the other is NULL? They're really both equal. if (!exc_a) exc_a = R_GetDefaultColormap(); if (!exc_b) exc_b = R_GetDefaultColormap(); if (exc_a == exc_b) return true; return ( (!checkparams ? true : (exc_a->fadestart == exc_b->fadestart && exc_a->fadeend == exc_b->fadeend && exc_a->flags == exc_b->flags) ) && (!checkrgba ? true : exc_a->rgba == exc_b->rgba) && (!checkfadergba ? true : exc_a->fadergba == exc_b->fadergba) #ifdef EXTRACOLORMAPLUMPS && exc_a->lump == exc_b->lump && !strncmp(exc_a->lumpname, exc_b->lumpname, 9) #endif ); } // // R_GetColormapFromListByValues() // NOTE: Returns NULL if no match is found // #ifdef EXTRACOLORMAPLUMPS extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags, lumpnum_t lump) #else extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags) #endif { extracolormap_t *exc; UINT32 dbg_i = 0; for (exc = extra_colormaps; exc; exc = exc->next) { if (rgba == exc->rgba && fadergba == exc->fadergba && fadestart == exc->fadestart && fadeend == exc->fadeend && flags == exc->flags #ifdef EXTRACOLORMAPLUMPS && (lump != LUMPERROR && lump == exc->lump) #endif ) { CONS_Debug(DBG_RENDER, "Found Colormap %d: rgba(%d,%d,%d,%d) fadergba(%d,%d,%d,%d)\n", dbg_i, R_GetRgbaR(rgba), R_GetRgbaG(rgba), R_GetRgbaB(rgba), R_GetRgbaA(rgba), R_GetRgbaR(fadergba), R_GetRgbaG(fadergba), R_GetRgbaB(fadergba), R_GetRgbaA(fadergba)); return exc; } dbg_i++; } return NULL; } extracolormap_t *R_GetColormapFromList(extracolormap_t *extra_colormap) { #ifdef EXTRACOLORMAPLUMPS return R_GetColormapFromListByValues(extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->flags, extra_colormap->lump); #else return R_GetColormapFromListByValues(extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->flags); #endif } #ifdef EXTRACOLORMAPLUMPS extracolormap_t *R_ColormapForName(char *name) { lumpnum_t lump; extracolormap_t *exc; lump = R_CheckNumForNameList(name, colormaplumps, numcolormaplumps); if (lump == LUMPERROR) I_Error("R_ColormapForName: Cannot find colormap lump %.8s\n", name); exc = R_GetColormapFromListByValues(0, 0x19000000, 0, 31, 0, lump); if (exc) return exc; exc = Z_Calloc(sizeof (*exc), PU_LEVEL, NULL); exc->lump = lump; strncpy(exc->lumpname, name, 9); exc->lumpname[8] = 0; // aligned on 8 bit for asm code exc->colormap = Z_MallocAlign(W_LumpLength(lump), PU_LEVEL, NULL, 16); W_ReadLump(lump, exc->colormap); // We set all params of the colormap to normal because there // is no real way to tell how GL should handle a colormap lump anyway.. exc->fadestart = 0; exc->fadeend = 31; exc->flags = 0; exc->rgba = 0; exc->fadergba = 0x19000000; R_AddColormapToList(exc); return exc; } #endif // // R_CreateColormapFromLinedef // // This is a more GL friendly way of doing colormaps: Specify colormap // data in a special linedef's texture areas and use that to generate // custom colormaps at runtime. NOTE: For GL mode, we only need to color // data and not the colormap data. // static double deltas[256][3], map[256][3]; static int RoundUp(double number); lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) { double cmaskr, cmaskg, cmaskb, cdestr, cdestg, cdestb; double maskamt = 0, othermask = 0; UINT8 cr = R_GetRgbaR(extra_colormap->rgba), cg = R_GetRgbaG(extra_colormap->rgba), cb = R_GetRgbaB(extra_colormap->rgba), ca = R_GetRgbaA(extra_colormap->rgba), cfr = R_GetRgbaR(extra_colormap->fadergba), cfg = R_GetRgbaG(extra_colormap->fadergba), cfb = R_GetRgbaB(extra_colormap->fadergba); // cfa = R_GetRgbaA(extra_colormap->fadergba); // unused in software UINT8 fadestart = extra_colormap->fadestart, fadedist = extra_colormap->fadeend - extra_colormap->fadestart; lighttable_t *lighttable = NULL; size_t i; ///////////////////// // Calc the RGBA mask ///////////////////// cmaskr = cr; cmaskg = cg; cmaskb = cb; maskamt = (double)(ca/24.0l); othermask = 1 - maskamt; maskamt /= 0xff; cmaskr *= maskamt; cmaskg *= maskamt; cmaskb *= maskamt; ///////////////////// // Calc the RGBA fade mask ///////////////////// cdestr = cfr; cdestg = cfg; cdestb = cfb; // fade alpha unused in software // maskamt = (double)(cfa/24.0l); // othermask = 1 - maskamt; // maskamt /= 0xff; // cdestr *= maskamt; // cdestg *= maskamt; // cdestb *= maskamt; ///////////////////// // This code creates the colormap array used by software renderer ///////////////////// { double r, g, b, cbrightness; int p; char *colormap_p; // Initialise the map and delta arrays // map[i] stores an RGB color (as double) for index i, // which is then converted to SRB2's palette later // deltas[i] stores a corresponding fade delta between the RGB color and the final fade color; // map[i]'s values are decremented by after each use for (i = 0; i < 256; i++) { r = pMasterPalette[i].s.red; g = pMasterPalette[i].s.green; b = pMasterPalette[i].s.blue; cbrightness = sqrt((r*r) + (g*g) + (b*b)); map[i][0] = (cbrightness * cmaskr) + (r * othermask); if (map[i][0] > 255.0l) map[i][0] = 255.0l; deltas[i][0] = (map[i][0] - cdestr) / (double)fadedist; map[i][1] = (cbrightness * cmaskg) + (g * othermask); if (map[i][1] > 255.0l) map[i][1] = 255.0l; deltas[i][1] = (map[i][1] - cdestg) / (double)fadedist; map[i][2] = (cbrightness * cmaskb) + (b * othermask); if (map[i][2] > 255.0l) map[i][2] = 255.0l; deltas[i][2] = (map[i][2] - cdestb) / (double)fadedist; } // Now allocate memory for the actual colormap array itself! // aligned on 8 bit for asm code colormap_p = Z_MallocAlign((256 * 34) + 10, PU_LEVEL, NULL, 8); lighttable = (UINT8 *)colormap_p; // Calculate the palette index for each palette index, for each light level // (as well as the two unused colormap lines we inherited from Doom) for (p = 0; p < 34; p++) { for (i = 0; i < 256; i++) { *colormap_p = NearestColor((UINT8)RoundUp(map[i][0]), (UINT8)RoundUp(map[i][1]), (UINT8)RoundUp(map[i][2])); colormap_p++; if ((UINT32)p < fadestart) continue; #define ABS2(x) ((x) < 0 ? -(x) : (x)) if (ABS2(map[i][0] - cdestr) > ABS2(deltas[i][0])) map[i][0] -= deltas[i][0]; else map[i][0] = cdestr; if (ABS2(map[i][1] - cdestg) > ABS2(deltas[i][1])) map[i][1] -= deltas[i][1]; else map[i][1] = cdestg; if (ABS2(map[i][2] - cdestb) > ABS2(deltas[i][1])) map[i][2] -= deltas[i][2]; else map[i][2] = cdestb; #undef ABS2 } } } return lighttable; } extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) { // default values UINT8 cr = 0, cg = 0, cb = 0, ca = 0, cfr = 0, cfg = 0, cfb = 0, cfa = 25; UINT32 fadestart = 0, fadeend = 31; UINT8 flags = 0; INT32 rgba = 0, fadergba = 0x19000000; #define HEX2INT(x) (UINT32)(x >= '0' && x <= '9' ? x - '0' : x >= 'a' && x <= 'f' ? x - 'a' + 10 : x >= 'A' && x <= 'F' ? x - 'A' + 10 : 0) #define ALPHA2INT(x) (x >= 'a' && x <= 'z' ? x - 'a' : x >= 'A' && x <= 'Z' ? x - 'A' : x >= '0' && x <= '9' ? 25 : 0) // Get base colormap value // First alpha-only, then full value if (p1[0] >= 'a' && p1[0] <= 'z' && !p1[1]) ca = (p1[0] - 'a'); else if (p1[0] == '#' && p1[1] >= 'a' && p1[1] <= 'z' && !p1[2]) ca = (p1[1] - 'a'); else if (p1[0] >= 'A' && p1[0] <= 'Z' && !p1[1]) ca = (p1[0] - 'A'); else if (p1[0] == '#' && p1[1] >= 'A' && p1[1] <= 'Z' && !p1[2]) ca = (p1[1] - 'A'); else if (p1[0] == '#') { // For each subsequent value, the value before it must exist // If we don't get every value, then set alpha to max if (p1[1] && p1[2]) { cr = ((HEX2INT(p1[1]) * 16) + HEX2INT(p1[2])); if (p1[3] && p1[4]) { cg = ((HEX2INT(p1[3]) * 16) + HEX2INT(p1[4])); if (p1[5] && p1[6]) { cb = ((HEX2INT(p1[5]) * 16) + HEX2INT(p1[6])); if (p1[7] >= 'a' && p1[7] <= 'z') ca = (p1[7] - 'a'); else if (p1[7] >= 'A' && p1[7] <= 'Z') ca = (p1[7] - 'A'); else ca = 25; } else ca = 25; } else ca = 25; } else ca = 25; } #define NUMFROMCHAR(c) (c >= '0' && c <= '9' ? c - '0' : 0) // Get parameters like fadestart, fadeend, and flags if (p2[0] == '#') { if (p2[1]) { flags = NUMFROMCHAR(p2[1]); if (p2[2] && p2[3]) { fadestart = NUMFROMCHAR(p2[3]) + (NUMFROMCHAR(p2[2]) * 10); if (p2[4] && p2[5]) fadeend = NUMFROMCHAR(p2[5]) + (NUMFROMCHAR(p2[4]) * 10); } } if (fadestart > 30) fadestart = 0; if (fadeend > 31 || fadeend < 1) fadeend = 31; } #undef NUMFROMCHAR // Get fade (dark) colormap value // First alpha-only, then full value if (p3[0] >= 'a' && p3[0] <= 'z' && !p3[1]) cfa = (p3[0] - 'a'); else if (p3[0] == '#' && p3[1] >= 'a' && p3[1] <= 'z' && !p3[2]) cfa = (p3[1] - 'a'); else if (p3[0] >= 'A' && p3[0] <= 'Z' && !p3[1]) cfa = (p3[0] - 'A'); else if (p3[0] == '#' && p3[1] >= 'A' && p3[1] <= 'Z' && !p3[2]) cfa = (p3[1] - 'A'); else if (p3[0] == '#') { // For each subsequent value, the value before it must exist // If we don't get every value, then set alpha to max if (p3[1] && p3[2]) { cfr = ((HEX2INT(p3[1]) * 16) + HEX2INT(p3[2])); if (p3[3] && p3[4]) { cfg = ((HEX2INT(p3[3]) * 16) + HEX2INT(p3[4])); if (p3[5] && p3[6]) { cfb = ((HEX2INT(p3[5]) * 16) + HEX2INT(p3[6])); if (p3[7] >= 'a' && p3[7] <= 'z') cfa = (p3[7] - 'a'); else if (p3[7] >= 'A' && p3[7] <= 'Z') cfa = (p3[7] - 'A'); else cfa = 25; } else cfa = 25; } else cfa = 25; } else cfa = 25; } #undef ALPHA2INT #undef HEX2INT // Pack rgba values into combined var // OpenGL also uses this instead of lighttables for rendering rgba = R_PutRgbaRGBA(cr, cg, cb, ca); fadergba = R_PutRgbaRGBA(cfr, cfg, cfb, cfa); return R_CreateColormap(rgba, fadergba, fadestart, fadeend, flags); } extracolormap_t *R_CreateColormap(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags) { extracolormap_t *extra_colormap; // Did we just make a default colormap? #ifdef EXTRACOLORMAPLUMPS if (R_CheckDefaultColormapByValues(true, true, true, rgba, fadergba, fadestart, fadeend, flags, LUMPERROR)) return NULL; #else if (R_CheckDefaultColormapByValues(true, true, true, rgba, fadergba, fadestart, fadeend, flags)) return NULL; #endif // Look for existing colormaps #ifdef EXTRACOLORMAPLUMPS extra_colormap = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags, LUMPERROR); #else extra_colormap = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags); #endif if (extra_colormap) return extra_colormap; CONS_Debug(DBG_RENDER, "Creating Colormap: rgba(%x) fadergba(%x)\n", rgba, fadergba); extra_colormap = Z_Calloc(sizeof(*extra_colormap), PU_LEVEL, NULL); extra_colormap->fadestart = (UINT16)fadestart; extra_colormap->fadeend = (UINT16)fadeend; extra_colormap->flags = flags; extra_colormap->rgba = rgba; extra_colormap->fadergba = fadergba; #ifdef EXTRACOLORMAPLUMPS extra_colormap->lump = LUMPERROR; extra_colormap->lumpname[0] = 0; #endif // Having lighttables for alpha-only entries is kind of pointless, // but if there happens to be a matching rgba entry that is NOT alpha-only (but has same rgb values), // then it needs this lighttable because we share matching entries. extra_colormap->colormap = R_CreateLightTable(extra_colormap); R_AddColormapToList(extra_colormap); return extra_colormap; } // // R_AddColormaps() // NOTE: The result colormap is not added to the extra_colormaps chain. You must do that yourself! // extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *exc_addend, boolean subR, boolean subG, boolean subB, boolean subA, boolean subFadeR, boolean subFadeG, boolean subFadeB, boolean subFadeA, boolean subFadeStart, boolean subFadeEnd, boolean ignoreFlags, boolean lighttable) { INT16 red, green, blue, alpha; // exc_augend is added (or subtracted) onto by exc_addend // In Rennaisance times, the first number was considered the augend, the second number the addend // But since the commutative property was discovered, today they're both called addends! // So let's be Olde English for a hot second. exc_augend = R_CopyColormap(exc_augend, false); if(!exc_addend) exc_addend = R_GetDefaultColormap(); /////////////////// // base rgba /////////////////// red = max(min( R_GetRgbaR(exc_augend->rgba) + (subR ? -1 : 1) // subtract R * R_GetRgbaR(exc_addend->rgba) , 255), 0); green = max(min( R_GetRgbaG(exc_augend->rgba) + (subG ? -1 : 1) // subtract G * R_GetRgbaG(exc_addend->rgba) , 255), 0); blue = max(min( R_GetRgbaB(exc_augend->rgba) + (subB ? -1 : 1) // subtract B * R_GetRgbaB(exc_addend->rgba) , 255), 0); alpha = R_GetRgbaA(exc_addend->rgba); alpha = max(min(R_GetRgbaA(exc_augend->rgba) + (subA ? -1 : 1) * alpha, 25), 0); exc_augend->rgba = R_PutRgbaRGBA(red, green, blue, alpha); /////////////////// // fade/dark rgba /////////////////// red = max(min( R_GetRgbaR(exc_augend->fadergba) + (subFadeR ? -1 : 1) // subtract R * R_GetRgbaR(exc_addend->fadergba) , 255), 0); green = max(min( R_GetRgbaG(exc_augend->fadergba) + (subFadeG ? -1 : 1) // subtract G * R_GetRgbaG(exc_addend->fadergba) , 255), 0); blue = max(min( R_GetRgbaB(exc_augend->fadergba) + (subFadeB ? -1 : 1) // subtract B * R_GetRgbaB(exc_addend->fadergba) , 255), 0); alpha = R_GetRgbaA(exc_addend->fadergba); if (alpha == 25 && !R_GetRgbaRGB(exc_addend->fadergba)) alpha = 0; // HACK: fadergba A defaults at 25, so don't add anything in this case alpha = max(min(R_GetRgbaA(exc_augend->fadergba) + (subFadeA ? -1 : 1) * alpha, 25), 0); exc_augend->fadergba = R_PutRgbaRGBA(red, green, blue, alpha); /////////////////// // parameters /////////////////// exc_augend->fadestart = max(min( exc_augend->fadestart + (subFadeStart ? -1 : 1) // subtract fadestart * exc_addend->fadestart , 31), 0); exc_augend->fadeend = max(min( exc_augend->fadeend + (subFadeEnd ? -1 : 1) // subtract fadeend * (exc_addend->fadeend == 31 && !exc_addend->fadestart ? 0 : exc_addend->fadeend) // HACK: fadeend defaults to 31, so don't add anything in this case , 31), 0); if (!ignoreFlags) // overwrite flags with new value exc_augend->flags = exc_addend->flags; /////////////////// // put it together /////////////////// exc_augend->colormap = lighttable ? R_CreateLightTable(exc_augend) : NULL; exc_augend->next = exc_augend->prev = NULL; return exc_augend; } // Thanks to quake2 source! // utils3/qdata/images.c UINT8 NearestPaletteColor(UINT8 r, UINT8 g, UINT8 b, RGBA_t *palette) { int dr, dg, db; int distortion, bestdistortion = 256 * 256 * 4, bestcolor = 0, i; // Use master palette if none specified if (palette == NULL) palette = pMasterPalette; for (i = 0; i < 256; i++) { dr = r - palette[i].s.red; dg = g - palette[i].s.green; db = b - palette[i].s.blue; distortion = dr*dr + dg*dg + db*db; if (distortion < bestdistortion) { if (!distortion) return (UINT8)i; bestdistortion = distortion; bestcolor = i; } } return (UINT8)bestcolor; } // Rounds off floating numbers and checks for 0 - 255 bounds static int RoundUp(double number) { if (number > 255.0l) return 255; if (number < 0.0l) return 0; if ((int)number <= (int)(number - 0.5f)) return (int)number + 1; return (int)number; } #ifdef EXTRACOLORMAPLUMPS const char *R_NameForColormap(extracolormap_t *extra_colormap) { if (!extra_colormap) return "NONE"; if (extra_colormap->lump == LUMPERROR) return "INLEVEL"; return extra_colormap->lumpname; } #endif // // build a table for quick conversion from 8bpp to 15bpp // // // added "static inline" keywords, linking with the debug version // of allegro, it have a makecol15 function of it's own, now // with "static inline" keywords,it sloves this problem ;) // FUNCMATH static inline int makecol15(int r, int g, int b) { return (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3))); } static void R_Init8to16(void) { UINT8 *palette; int i; palette = W_CacheLumpName("PLAYPAL",PU_CACHE); for (i = 0; i < 256; i++) { // PLAYPAL uses 8 bit values color8to16[i] = (INT16)makecol15(palette[0], palette[1], palette[2]); palette += 3; } // test a big colormap hicolormaps = Z_Malloc(16384*sizeof(*hicolormaps), PU_STATIC, NULL); for (i = 0; i < 16384; i++) hicolormaps[i] = (INT16)(i<<1); } // // R_InitData // // Locates all the lumps that will be used by all views // Must be called after W_Init. // void R_InitData(void) { if (highcolor) { CONS_Printf("InitHighColor...\n"); R_Init8to16(); } CONS_Printf("R_LoadTextures()...\n"); R_LoadTextures(); CONS_Printf("P_InitPicAnims()...\n"); P_InitPicAnims(); CONS_Printf("R_InitSprites()...\n"); R_InitSpriteLumps(); R_InitSprites(); CONS_Printf("R_InitColormaps()...\n"); R_InitColormaps(); } // // R_PrecacheLevel // // Preloads all relevant graphics for the level. // void R_PrecacheLevel(void) { char *texturepresent, *spritepresent; size_t i, j, k; lumpnum_t lump; thinker_t *th; spriteframe_t *sf; if (demoplayback) return; // do not flush the memory, Z_Malloc twice with same user will cause error in Z_CheckHeap() if (rendermode != render_soft) return; // Precache flats. flatmemory = P_PrecacheLevelFlats(); // // Precache textures. // // no need to precache all software textures in 3D mode // (note they are still used with the reference software view) texturepresent = calloc(numtextures, sizeof (*texturepresent)); if (texturepresent == NULL) I_Error("%s: Out of memory looking up textures", "R_PrecacheLevel"); for (j = 0; j < numsides; j++) { // huh, a potential bug here???? if (sides[j].toptexture >= 0 && sides[j].toptexture < numtextures) texturepresent[sides[j].toptexture] = 1; if (sides[j].midtexture >= 0 && sides[j].midtexture < numtextures) texturepresent[sides[j].midtexture] = 1; if (sides[j].bottomtexture >= 0 && sides[j].bottomtexture < numtextures) texturepresent[sides[j].bottomtexture] = 1; } // Sky texture is always present. // Note that F_SKY1 is the name used to indicate a sky floor/ceiling as a flat, // while the sky texture is stored like a wall texture, with a skynum dependent name. texturepresent[skytexture] = 1; texturememory = 0; for (j = 0; j < (unsigned)numtextures; j++) { if (!texturepresent[j]) continue; if (!texturecache[j]) R_GenerateTexture(j); // pre-caching individual patches that compose textures became obsolete, // since we cache entire composite textures } free(texturepresent); // // Precache sprites. // spritepresent = calloc(numsprites, sizeof (*spritepresent)); if (spritepresent == NULL) I_Error("%s: Out of memory looking up sprites", "R_PrecacheLevel"); for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed) spritepresent[((mobj_t *)th)->sprite] = 1; spritememory = 0; for (i = 0; i < numsprites; i++) { if (!spritepresent[i]) continue; for (j = 0; j < sprites[i].numframes; j++) { sf = &sprites[i].spriteframes[j]; #define cacheang(a) {\ lump = sf->lumppat[a];\ if (devparm)\ spritememory += W_LumpLength(lump);\ W_CachePatchNum(lump, PU_SPRITE);\ } // see R_InitSprites for more about lumppat,lumpid switch (sf->rotate) { case SRF_SINGLE: cacheang(0); break; case SRF_2D: cacheang(2); cacheang(6); break; default: k = (sf->rotate & SRF_3DGE ? 16 : 8); while (k--) cacheang(k); break; } #undef cacheang } } free(spritepresent); // FIXME: this is no longer correct with OpenGL render mode CONS_Debug(DBG_SETUP, "Precache level done:\n" "flatmemory: %s k\n" "texturememory: %s k\n" "spritememory: %s k\n", sizeu1(flatmemory>>10), sizeu2(texturememory>>10), sizeu3(spritememory>>10)); }