SRB2/src/r_data.c

2208 lines
59 KiB
C
Raw Normal View History

2014-03-15 09:59:03 -07:00
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
2018-11-25 04:35:38 -08:00
// Copyright (C) 1999-2018 by Sonic Team Junior.
2014-03-15 09:59:03 -07:00
//
// 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 "w_wad.h"
#include "z_zone.h"
#include "p_setup.h" // levelflats
#include "v_video.h" // pLocalPalette
#include "dehacked.h"
#if defined (_WIN32) || defined (_WIN32_WCE)
#include <malloc.h> // alloca(sizeof)
#endif
#if defined(_MSC_VER)
#pragma pack(1)
#endif
// Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog
#ifdef _WIN32_WCE
#define AVOID_ERRNO
#else
#include <errno.h>
#endif
2019-05-26 19:37:23 -07:00
#ifdef HAVE_PNG
#ifndef _MSC_VER
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif
#endif
#ifndef _LFS64_LARGEFILE
#define _LFS64_LARGEFILE
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 0
#endif
#include "png.h"
#ifndef PNG_READ_SUPPORTED
#undef HAVE_PNG
#endif
#endif
2014-03-15 09:59:03 -07:00
//
// Texture definition.
// Each texture is composed of one or more patches,
// with patches being lumps stored in the WAD.
// The lumps are referenced by number, and patched
// into the rectangular texture space using origin
// and possibly other attributes.
//
typedef struct
{
INT16 originx, originy;
INT16 patch, stepdir, colormap;
} ATTRPACK mappatch_t;
//
// Texture definition.
// An SRB2 wall texture is a list of patches
// which are to be combined in a predefined order.
//
typedef struct
{
char name[8];
INT32 masked;
INT16 width;
INT16 height;
INT32 columndirectory; // FIXTHIS: OBSOLETE
INT16 patchcount;
mappatch_t patches[1];
} ATTRPACK maptexture_t;
#if defined(_MSC_VER)
#pragma pack()
#endif
// Store lists of lumps for F_START/F_END etc.
typedef struct
{
UINT16 wadfile;
UINT16 firstlump;
size_t numlumps;
} lumplist_t;
//
// 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;
// textures
INT32 numtextures = 0; // total number of textures found,
// size of following tables
texture_t **textures = NULL;
2019-05-21 11:24:26 -07:00
textureflat_t *texflats = NULL;
2014-03-15 09:59:03 -07:00
static UINT32 **texturecolumnofs; // column offset lookup table for each texture
static UINT8 **texturecache; // graphics data for each generated full-size texture
INT32 *texturewidth;
2014-03-15 09:59:03 -07:00
fixed_t *textureheight; // needed for texture pegging
INT32 *texturetranslation;
// needed for pre rendering
sprcache_t *spritecachedinfo;
lighttable_t *colormaps;
// for debugging/info purposes
static 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
// Painfully simple texture id cacheing to make maps load faster. :3
static struct {
char name[9];
INT32 id;
} *tidcache = NULL;
static INT32 tidcachelen = 0;
//
// MAPTEXTURE_T CACHING
// When a texture is first needed, it counts the number of composite columns
// required in the texture and allocates space for a column directory and
// any new columns.
// The directory will simply point inside other patches if there is only one
// patch in a given column, but any columns with multiple patches will have
// new column_ts generated.
//
//
// R_DrawColumnInCache
// Clip and draw a column from a patch into a cached post.
//
static inline void R_DrawColumnInCache(column_t *patch, UINT8 *cache, INT32 originy, INT32 cacheheight)
{
INT32 count, position;
UINT8 *source;
INT32 topdelta, prevdelta = -1;
while (patch->topdelta != 0xff)
{
topdelta = patch->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
source = (UINT8 *)patch + 3;
count = patch->length;
position = originy + topdelta;
if (position < 0)
{
count += position;
position = 0;
}
if (position + count > cacheheight)
count = cacheheight - position;
if (count > 0)
M_Memcpy(cache + position, source, count);
patch = (column_t *)((UINT8 *)patch + patch->length + 4);
}
}
//
// R_GenerateTexture
//
// Allocate space for full size texture, either single patch or 'composite'
// Build the full textures from patches.
// The texture caching system is a little more hungry of memory, but has
2019-05-26 19:37:23 -07:00
// been simplified for the sake of highcolor (lol), dynamic ligthing, & speed.
2014-03-15 09:59:03 -07:00
//
// This is not optimised, but it's supposed to be executed only once
// per level, when enough memory is available.
//
static UINT8 *R_GenerateTexture(size_t texnum)
{
UINT8 *block;
UINT8 *blocktex;
texture_t *texture;
texpatch_t *patch;
patch_t *realpatch;
int x, x1, x2, i;
size_t blocksize;
column_t *patchcol;
UINT32 *colofs;
2019-05-26 19:37:23 -07:00
UINT16 wadnum;
lumpnum_t lumpnum;
size_t lumplength;
2014-03-15 09:59:03 -07:00
I_Assert(texnum <= (size_t)numtextures);
texture = textures[texnum];
I_Assert(texture != NULL);
// allocate texture column offset lookup
// single-patch textures can have holes in them and may be used on
// 2sided lines so they need to be kept in 'packed' format
// BUT this is wrong for skies and walls with over 255 pixels,
// so check if there's holes and if not strip the posts.
if (texture->patchcount == 1)
{
boolean holey = false;
patch = texture->patches;
2019-05-26 19:37:23 -07:00
wadnum = patch->wad;
lumpnum = patch->lump;
lumplength = W_LumpLengthPwad(wadnum, lumpnum);
realpatch = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength);
2014-03-15 09:59:03 -07:00
// Check the patch for holes.
if (texture->width > SHORT(realpatch->width) || texture->height > SHORT(realpatch->height))
holey = true;
colofs = (UINT32 *)realpatch->columnofs;
for (x = 0; x < texture->width && !holey; x++)
{
column_t *col = (column_t *)((UINT8 *)realpatch + LONG(colofs[x]));
INT32 topdelta, prevdelta = -1, y = 0;
while (col->topdelta != 0xff)
{
topdelta = col->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
if (topdelta > y)
break;
y = topdelta + col->length + 1;
col = (column_t *)((UINT8 *)col + col->length + 4);
}
if (y < texture->height)
holey = true; // this texture is HOLEy! D:
}
// If the patch uses transparency, we have to save it this way.
if (holey)
{
texture->holes = true;
2019-05-26 19:37:23 -07:00
blocksize = lumplength;
2014-03-15 09:59:03 -07:00
block = Z_Calloc(blocksize, PU_STATIC, // will change tag at end of this function
&texturecache[texnum]);
M_Memcpy(block, realpatch, blocksize);
texturememory += blocksize;
// use the patch's column lookup
colofs = (UINT32 *)(void *)(block + 8);
texturecolumnofs[texnum] = colofs;
blocktex = block;
for (x = 0; x < texture->width; x++)
colofs[x] = LONG(LONG(colofs[x]) + 3);
goto done;
}
// Otherwise, do multipatch format.
}
// multi-patch textures (or 'composite')
texture->holes = false;
blocksize = (texture->width * 4) + (texture->width * texture->height);
texturememory += blocksize;
block = Z_Malloc(blocksize+1, PU_STATIC, &texturecache[texnum]);
memset(block, 0xF7, blocksize+1); // Transparency hack
// columns lookup table
colofs = (UINT32 *)(void *)block;
texturecolumnofs[texnum] = colofs;
// texture data after the lookup table
blocktex = block + (texture->width*4);
// Composite the columns together.
for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
{
2019-05-26 19:37:23 -07:00
wadnum = patch->wad;
lumpnum = patch->lump;
lumplength = W_LumpLengthPwad(wadnum, lumpnum);
realpatch = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength);
2014-03-15 09:59:03 -07:00
x1 = patch->originx;
x2 = x1 + SHORT(realpatch->width);
if (x1 < 0)
x = 0;
else
x = x1;
if (x2 > texture->width)
x2 = texture->width;
for (; x < x2; x++)
{
patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[x-x1]));
// generate column ofset lookup
colofs[x] = LONG((x * texture->height) + (texture->width*4));
R_DrawColumnInCache(patchcol, block + LONG(colofs[x]), patch->originy, texture->height);
}
}
done:
// Now that the texture has been built in column cache, it is purgable from zone memory.
Z_ChangeTag(block, PU_CACHE);
return blocktex;
}
//
// R_GetTextureNum
//
// Returns the actual texture id that we should use.
// This can either be texnum, the current frame for texnum's anim (if animated),
// or 0 if not valid.
//
INT32 R_GetTextureNum(INT32 texnum)
{
if (texnum < 0 || texnum >= numtextures)
return 0;
return texturetranslation[texnum];
}
//
// R_CheckTextureCache
//
// Use this if you need to make sure the texture is cached before R_GetColumn calls
// e.g.: midtextures and FOF walls
//
void R_CheckTextureCache(INT32 tex)
{
if (!texturecache[tex])
R_GenerateTexture(tex);
}
2014-03-15 09:59:03 -07:00
//
// R_GetColumn
//
UINT8 *R_GetColumn(fixed_t tex, INT32 col)
{
UINT8 *data;
INT32 width = texturewidth[tex];
2014-03-15 09:59:03 -07:00
if (width & (width - 1))
col = (UINT32)col % width;
else
col &= (width - 1);
2014-03-15 09:59:03 -07:00
data = texturecache[tex];
2014-03-15 09:59:03 -07:00
if (!data)
data = R_GenerateTexture(tex);
return data + LONG(texturecolumnofs[tex][col]);
}
// convert flats to hicolor as they are requested
//
UINT8 *R_GetFlat(lumpnum_t flatlumpnum)
{
return W_CacheLumpNum(flatlumpnum, PU_CACHE);
}
//
// Empty the texture cache (used for load wad at runtime)
//
void R_FlushTextureCache(void)
{
INT32 i;
if (numtextures)
for (i = 0; i < numtextures; i++)
Z_Free(texturecache[i]);
}
// Need these prototypes for later; defining them here instead of r_data.h so they're "private"
int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum);
void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *index);
2014-03-15 09:59:03 -07:00
//
// R_LoadTextures
// Initializes the texture list with the textures from the world map.
//
#define TX_START "TX_START"
#define TX_END "TX_END"
void R_LoadTextures(void)
{
INT32 i, w;
2014-03-15 09:59:03 -07:00
UINT16 j;
UINT16 texstart, texend, texturesLumpPos;
patch_t *patchlump;
texpatch_t *patch;
texture_t *texture;
// Free previous memory before numtextures change.
if (numtextures)
{
for (i = 0; i < numtextures; i++)
{
Z_Free(textures[i]);
Z_Free(texturecache[i]);
}
Z_Free(texturetranslation);
Z_Free(textures);
2019-05-21 11:24:26 -07:00
Z_Free(texflats);
2014-03-15 09:59:03 -07:00
}
// Load patches and textures.
// Get the number of textures to check.
// NOTE: Make SURE the system does not process
// the markers.
// This system will allocate memory for all duplicate/patched textures even if it never uses them,
// but the alternative is to spend a ton of time checking and re-checking all previous entries just to skip any potentially patched textures.
for (w = 0, numtextures = 0; w < numwadfiles; w++)
{
if (wadfiles[w]->type == RET_PK3)
{
texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
}
else
{
texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0) + 1;
texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
}
2014-03-15 09:59:03 -07:00
texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
while (texturesLumpPos != INT16_MAX)
2014-03-15 09:59:03 -07:00
{
numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
2014-03-15 09:59:03 -07:00
}
// Add all the textures between TX_START and TX_END
if (texstart != INT16_MAX && texend != INT16_MAX)
{
numtextures += (UINT32)(texend - texstart);
}
// If no textures found by this point, bomb out
if (!numtextures && w == (numwadfiles - 1))
{
I_Error("No textures detected in any WADs!\n");
}
}
// Allocate memory and initialize to 0 for all the textures we are initialising.
// There are actually 5 buffers allocated in one for convenience.
textures = Z_Calloc((numtextures * sizeof(void *)) * 5, PU_STATIC, NULL);
2019-05-21 11:24:26 -07:00
texflats = Z_Calloc((numtextures * sizeof(*texflats)), PU_STATIC, NULL);
2014-03-15 09:59:03 -07:00
// Allocate texture column offset table.
texturecolumnofs = (void *)((UINT8 *)textures + (numtextures * sizeof(void *)));
// Allocate texture referencing cache.
texturecache = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 2));
// Allocate texture width table.
texturewidth = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 3));
// Allocate texture height table.
2014-03-15 09:59:03 -07:00
textureheight = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 4));
// Create translation table for global animation.
texturetranslation = Z_Malloc((numtextures + 1) * sizeof(*texturetranslation), PU_STATIC, NULL);
for (i = 0; i < numtextures; i++)
texturetranslation[i] = i;
for (i = 0, w = 0; w < numwadfiles; w++)
{
// Get the lump numbers for the markers in the WAD, if they exist.
if (wadfiles[w]->type == RET_PK3)
{
texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
while (texturesLumpPos != INT16_MAX)
{
R_ParseTEXTURESLump(w, texturesLumpPos, &i);
texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
}
}
else
{
texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0) + 1;
texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
if (texturesLumpPos != INT16_MAX)
R_ParseTEXTURESLump(w, texturesLumpPos, &i);
}
2014-03-15 09:59:03 -07:00
if (texstart == INT16_MAX || texend == INT16_MAX)
continue;
// Work through each lump between the markers in the WAD.
for (j = 0; j < (texend - texstart); i++, j++)
{
2019-05-26 19:37:23 -07:00
UINT16 wadnum = (UINT16)w;
lumpnum_t lumpnum = texstart + j;
size_t lumplength = W_LumpLengthPwad(wadnum, lumpnum);
patchlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
2014-03-15 09:59:03 -07:00
// Then, check the lump directly to see if it's a texture SOC,
// and if it is, load it using dehacked instead.
if (strstr((const char *)patchlump, "TEXTURE"))
{
CONS_Alert(CONS_WARNING, "%s is a Texture SOC.\n", W_CheckNameForNumPwad((UINT16)w,texstart+j));
Z_Unlock(patchlump);
DEH_LoadDehackedLumpPwad((UINT16)w, texstart + j);
}
else
{
//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
2017-04-19 12:00:19 -07:00
texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
2014-03-15 09:59:03 -07:00
// Set texture properties.
2019-05-26 19:37:23 -07:00
M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
if (R_IsLumpPNG((UINT8 *)patchlump, lumplength))
{
INT16 width, height;
R_PNGDimensions((UINT8 *)patchlump, &width, &height, lumplength);
texture->width = width;
texture->height = height;
}
else
{
texture->width = SHORT(patchlump->width);
texture->height = SHORT(patchlump->height);
}
2017-04-19 12:00:19 -07:00
texture->patchcount = 1;
2014-03-15 09:59:03 -07:00
texture->holes = false;
// Allocate information for the texture's patches.
2017-04-19 12:00:19 -07:00
patch = &texture->patches[0];
patch->originx = patch->originy = 0;
patch->wad = (UINT16)w;
patch->lump = texstart + j;
2014-03-15 09:59:03 -07:00
Z_Unlock(patchlump);
texturewidth[i] = texture->width;
2014-03-15 09:59:03 -07:00
textureheight[i] = texture->height << FRACBITS;
}
}
}
}
static texpatch_t *R_ParsePatch(boolean actuallyLoadPatch)
{
char *texturesToken;
2016-05-21 21:44:12 -07:00
size_t texturesTokenLength;
2014-03-15 09:59:03 -07:00
char *endPos;
char *patchName = NULL;
INT16 patchXPos;
INT16 patchYPos;
texpatch_t *resultPatch = NULL;
lumpnum_t patchLumpNum;
// Patch identifier
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch name should be");
}
texturesTokenLength = strlen(texturesToken);
if (texturesTokenLength>8)
{
I_Error("Error parsing TEXTURES lump: Patch name \"%s\" exceeds 8 characters",texturesToken);
}
else
{
if (patchName != NULL)
{
Z_Free(patchName);
}
patchName = (char *)Z_Malloc((texturesTokenLength+1)*sizeof(char),PU_STATIC,NULL);
M_Memcpy(patchName,texturesToken,texturesTokenLength*sizeof(char));
patchName[texturesTokenLength] = '\0';
}
// Comma 1
Z_Free(texturesToken);
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after \"%s\"'s patch name should be",patchName);
}
if (strcmp(texturesToken,",")!=0)
{
I_Error("Error parsing TEXTURES lump: Expected \",\" after %s's patch name, got \"%s\"",patchName,texturesToken);
}
// XPos
Z_Free(texturesToken);
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s x coordinate should be",patchName);
}
endPos = NULL;
#ifndef AVOID_ERRNO
errno = 0;
#endif
patchXPos = strtol(texturesToken,&endPos,10);
(void)patchXPos; //unused for now
if (endPos == texturesToken // Empty string
|| *endPos != '\0' // Not end of string
#ifndef AVOID_ERRNO
|| errno == ERANGE // Number out-of-range
#endif
)
{
I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
}
// Comma 2
Z_Free(texturesToken);
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after patch \"%s\"'s x coordinate should be",patchName);
}
if (strcmp(texturesToken,",")!=0)
{
I_Error("Error parsing TEXTURES lump: Expected \",\" after patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
}
// YPos
Z_Free(texturesToken);
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s y coordinate should be",patchName);
}
endPos = NULL;
#ifndef AVOID_ERRNO
errno = 0;
#endif
patchYPos = strtol(texturesToken,&endPos,10);
(void)patchYPos; //unused for now
if (endPos == texturesToken // Empty string
|| *endPos != '\0' // Not end of string
#ifndef AVOID_ERRNO
|| errno == ERANGE // Number out-of-range
#endif
)
{
I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s y coordinate, got \"%s\"",patchName,texturesToken);
}
Z_Free(texturesToken);
if (actuallyLoadPatch == true)
{
// Check lump exists
patchLumpNum = W_GetNumForName(patchName);
// If so, allocate memory for texpatch_t and fill 'er up
resultPatch = (texpatch_t *)Z_Malloc(sizeof(texpatch_t),PU_STATIC,NULL);
resultPatch->originx = patchXPos;
resultPatch->originy = patchYPos;
resultPatch->lump = patchLumpNum & 65535;
resultPatch->wad = patchLumpNum>>16;
// Clean up a little after ourselves
Z_Free(patchName);
// Then return it
return resultPatch;
}
else
{
Z_Free(patchName);
return NULL;
}
}
static texture_t *R_ParseTexture(boolean actuallyLoadTexture)
{
char *texturesToken;
2016-05-21 21:44:12 -07:00
size_t texturesTokenLength;
2014-03-15 09:59:03 -07:00
char *endPos;
INT32 newTextureWidth;
INT32 newTextureHeight;
texture_t *resultTexture = NULL;
texpatch_t *newPatch;
char newTextureName[9]; // no longer dynamically allocated
2014-03-15 09:59:03 -07:00
// Texture name
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture name should be");
}
texturesTokenLength = strlen(texturesToken);
if (texturesTokenLength>8)
{
I_Error("Error parsing TEXTURES lump: Texture name \"%s\" exceeds 8 characters",texturesToken);
}
else
{
memset(&newTextureName, 0, 9);
M_Memcpy(newTextureName, texturesToken, texturesTokenLength);
// ^^ we've confirmed that the token is <= 8 characters so it will never overflow a 9 byte char buffer
strupr(newTextureName); // Just do this now so we don't have to worry about it
2014-03-15 09:59:03 -07:00
}
Z_Free(texturesToken);
// Comma 1
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s name should be",newTextureName);
}
else if (strcmp(texturesToken,",")!=0)
{
I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s name, got \"%s\"",newTextureName,texturesToken);
}
Z_Free(texturesToken);
// Width
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s width should be",newTextureName);
}
endPos = NULL;
#ifndef AVOID_ERRNO
errno = 0;
#endif
newTextureWidth = strtol(texturesToken,&endPos,10);
if (endPos == texturesToken // Empty string
|| *endPos != '\0' // Not end of string
#ifndef AVOID_ERRNO
|| errno == ERANGE // Number out-of-range
#endif
|| newTextureWidth < 0) // Number is not positive
{
I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
}
Z_Free(texturesToken);
// Comma 2
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s width should be",newTextureName);
}
if (strcmp(texturesToken,",")!=0)
{
I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
}
Z_Free(texturesToken);
// Height
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s height should be",newTextureName);
}
endPos = NULL;
#ifndef AVOID_ERRNO
errno = 0;
#endif
newTextureHeight = strtol(texturesToken,&endPos,10);
if (endPos == texturesToken // Empty string
|| *endPos != '\0' // Not end of string
#ifndef AVOID_ERRNO
|| errno == ERANGE // Number out-of-range
#endif
|| newTextureHeight < 0) // Number is not positive
{
I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s height, got \"%s\"",newTextureName,texturesToken);
}
Z_Free(texturesToken);
// Left Curly Brace
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where open curly brace for texture \"%s\" should be",newTextureName);
}
if (strcmp(texturesToken,"{")==0)
{
if (actuallyLoadTexture)
{
// Allocate memory for a zero-patch texture. Obviously, we'll be adding patches momentarily.
resultTexture = (texture_t *)Z_Calloc(sizeof(texture_t),PU_STATIC,NULL);
M_Memcpy(resultTexture->name, newTextureName, 8);
resultTexture->width = newTextureWidth;
resultTexture->height = newTextureHeight;
}
Z_Free(texturesToken);
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch definition for texture \"%s\" should be",newTextureName);
}
while (strcmp(texturesToken,"}")!=0)
{
if (stricmp(texturesToken, "PATCH")==0)
{
Z_Free(texturesToken);
if (resultTexture)
{
// Get that new patch
newPatch = R_ParsePatch(true);
// Make room for the new patch
resultTexture = Z_Realloc(resultTexture, sizeof(texture_t) + (resultTexture->patchcount+1)*sizeof(texpatch_t), PU_STATIC, NULL);
// Populate the uninitialized values in the new patch entry of our array
M_Memcpy(&resultTexture->patches[resultTexture->patchcount], newPatch, sizeof(texpatch_t));
// Account for the new number of patches in the texture
resultTexture->patchcount++;
// Then free up the memory assigned to R_ParsePatch, as it's unneeded now
Z_Free(newPatch);
}
else
{
R_ParsePatch(false);
}
}
else
{
I_Error("Error parsing TEXTURES lump: Expected \"PATCH\" in texture \"%s\", got \"%s\"",newTextureName,texturesToken);
}
texturesToken = M_GetToken(NULL);
if (texturesToken == NULL)
{
I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch declaration or right curly brace for texture \"%s\" should be",newTextureName);
}
}
if (resultTexture && resultTexture->patchcount == 0)
{
I_Error("Error parsing TEXTURES lump: Texture \"%s\" must have at least one patch",newTextureName);
}
}
else
{
I_Error("Error parsing TEXTURES lump: Expected \"{\" for texture \"%s\", got \"%s\"",newTextureName,texturesToken);
}
Z_Free(texturesToken);
if (actuallyLoadTexture) return resultTexture;
else return NULL;
}
// Parses the TEXTURES lump... but just to count the number of textures.
int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum)
2014-03-15 09:59:03 -07:00
{
char *texturesLump;
size_t texturesLumpLength;
char *texturesText;
UINT32 numTexturesInLump = 0;
char *texturesToken;
// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
// need to make a space of memory where I can ensure that it will terminate
// correctly. Start by loading the relevant data from the WAD.
texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
2014-03-15 09:59:03 -07:00
// If that didn't exist, we have nothing to do here.
if (texturesLump == NULL) return 0;
// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
2014-03-15 09:59:03 -07:00
texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
// Now move the contents of the lump into this new location.
memmove(texturesText,texturesLump,texturesLumpLength);
// Make damn well sure the last character in our new memory location is \0.
texturesText[texturesLumpLength] = '\0';
// Finally, free up the memory from the first data load, because we really
// don't need it.
Z_Free(texturesLump);
texturesToken = M_GetToken(texturesText);
while (texturesToken != NULL)
{
if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
2014-03-15 09:59:03 -07:00
{
numTexturesInLump++;
Z_Free(texturesToken);
R_ParseTexture(false);
}
else
{
I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
2014-03-15 09:59:03 -07:00
}
texturesToken = M_GetToken(NULL);
}
Z_Free(texturesToken);
Z_Free((void *)texturesText);
return numTexturesInLump;
}
// Parses the TEXTURES lump... for real, this time.
void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *texindex)
2014-03-15 09:59:03 -07:00
{
char *texturesLump;
size_t texturesLumpLength;
char *texturesText;
char *texturesToken;
texture_t *newTexture;
I_Assert(texindex != NULL);
// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
// need to make a space of memory where I can ensure that it will terminate
// correctly. Start by loading the relevant data from the WAD.
texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
2014-03-15 09:59:03 -07:00
// If that didn't exist, we have nothing to do here.
if (texturesLump == NULL) return;
// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
2014-03-15 09:59:03 -07:00
texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
// Now move the contents of the lump into this new location.
memmove(texturesText,texturesLump,texturesLumpLength);
// Make damn well sure the last character in our new memory location is \0.
texturesText[texturesLumpLength] = '\0';
// Finally, free up the memory from the first data load, because we really
// don't need it.
Z_Free(texturesLump);
texturesToken = M_GetToken(texturesText);
while (texturesToken != NULL)
{
if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
2014-03-15 09:59:03 -07:00
{
Z_Free(texturesToken);
// Get the new texture
newTexture = R_ParseTexture(true);
// Store the new texture
textures[*texindex] = newTexture;
texturewidth[*texindex] = newTexture->width;
2014-03-15 09:59:03 -07:00
textureheight[*texindex] = newTexture->height << FRACBITS;
// Increment i back in R_LoadTextures()
(*texindex)++;
}
else
{
I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
2014-03-15 09:59:03 -07:00
}
texturesToken = M_GetToken(NULL);
}
Z_Free(texturesToken);
Z_Free((void *)texturesText);
}
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 lumplist_t *colormaplumps = NULL; ///\todo free leak
static size_t numcolormaplumps = 0;
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)
2014-03-15 09:59:03 -07:00
continue;
endnum = W_CheckNumForNamePwad("C_END", cfile, clump);
if (endnum == INT16_MAX)
2014-03-15 09:59:03 -07:00
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");
2014-03-15 09:59:03 -07:00
if (numcolormaplumps >= maxcolormaplumps)
maxcolormaplumps *= 2;
colormaplumps = Z_Realloc(colormaplumps,
sizeof (*colormaplumps) * maxcolormaplumps, PU_STATIC, NULL);
colormaplumps[numcolormaplumps].wadfile = cfile;
colormaplumps[numcolormaplumps].firstlump = startnum+1;
2014-03-15 09:59:03 -07:00
colormaplumps[numcolormaplumps].numlumps = endnum - (startnum + 1);
numcolormaplumps++;
}
CONS_Printf(M_GetText("Number of Extra Colormaps: %s\n"), sizeu1(numcolormaplumps));
}
// Search for flat name.
2014-03-15 09:59:03 -07:00
lumpnum_t R_GetFlatNumForName(const char *name)
{
INT32 i;
lumpnum_t lump;
lumpnum_t start;
lumpnum_t end;
// Scan wad files backwards so patched flats take preference.
for (i = numwadfiles - 1; i >= 0; i--)
{
switch (wadfiles[i]->type)
{
case RET_WAD:
if ((start = W_CheckNumForNamePwad("F_START", (UINT16)i, 0)) == INT16_MAX)
{
if ((start = W_CheckNumForNamePwad("FF_START", (UINT16)i, 0)) == INT16_MAX)
continue;
else if ((end = W_CheckNumForNamePwad("FF_END", (UINT16)i, start)) == INT16_MAX)
continue;
}
else
if ((end = W_CheckNumForNamePwad("F_END", (UINT16)i, start)) == INT16_MAX)
continue;
break;
case RET_PK3:
if ((start = W_CheckNumForFolderStartPK3("Flats/", i, 0)) == INT16_MAX)
continue;
if ((end = W_CheckNumForFolderEndPK3("Flats/", i, start)) == INT16_MAX)
continue;
break;
default:
continue;
}
// Now find lump with specified name in that range.
lump = W_CheckNumForNamePwad(name, (UINT16)i, start);
if (lump < end)
{
lump += (i<<16); // found it, in our constraints
break;
}
lump = LUMPERROR;
}
2019-05-21 11:24:26 -07:00
// Detect textures
if (lump == LUMPERROR)
{
2019-05-21 11:24:26 -07:00
// Scan wad files backwards so patched textures take preference.
for (i = numwadfiles - 1; i >= 0; i--)
{
switch (wadfiles[i]->type)
{
case RET_WAD:
if ((start = W_CheckNumForNamePwad("TX_START", (UINT16)i, 0)) == INT16_MAX)
continue;
if ((end = W_CheckNumForNamePwad("TX_END", (UINT16)i, start)) == INT16_MAX)
continue;
break;
case RET_PK3:
if ((start = W_CheckNumForFolderStartPK3("Textures/", i, 0)) == INT16_MAX)
continue;
if ((end = W_CheckNumForFolderEndPK3("Textures/", i, start)) == INT16_MAX)
continue;
break;
default:
continue;
}
// Now find lump with specified name in that range.
lump = W_CheckNumForNamePwad(name, (UINT16)i, start);
if (lump < end)
{
lump += (i<<16); // found it, in our constraints
break;
}
lump = LUMPERROR;
}
}
2014-03-15 09:59:03 -07:00
if (lump == LUMPERROR)
{
if (strcmp(name, SKYFLATNAME))
CONS_Debug(DBG_SETUP, "R_GetFlatNumForName: Could not find flat %.8s\n", name);
lump = W_CheckNumForName("REDFLR");
}
return lump;
}
//
// 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_InitColormaps
//
static void R_InitColormaps(void)
{
lumpnum_t lump;
// Load in the light tables
lump = W_GetNumForName("COLORMAP");
colormaps = Z_MallocAlign(W_LumpLength (lump), PU_STATIC, NULL, 8);
W_ReadLump(lump, colormaps);
// Init Boom colormaps.
R_ClearColormaps();
R_InitExtraColormaps();
}
void R_ReInitColormaps(UINT16 num)
{
char colormap[9] = "COLORMAP";
lumpnum_t lump;
2014-08-03 20:49:33 -07:00
if (num > 0 && num <= 10000)
2014-03-15 09:59:03 -07:00
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 = W_GetNumForName("COLORMAP");
W_ReadLump(lump, colormaps);
// Init Boom colormaps.
R_ClearColormaps();
}
static lumpnum_t foundcolormaps[MAXCOLORMAPS];
//
// R_ClearColormaps
//
// Clears out extra colormaps between levels.
//
void R_ClearColormaps(void)
{
size_t i;
num_extra_colormaps = 0;
for (i = 0; i < MAXCOLORMAPS; i++)
foundcolormaps[i] = LUMPERROR;
memset(extra_colormaps, 0, sizeof (extra_colormaps));
}
INT32 R_ColormapNumForName(char *name)
{
lumpnum_t lump, i;
if (num_extra_colormaps == MAXCOLORMAPS)
I_Error("R_ColormapNumForName: Too many colormaps! the limit is %d\n", MAXCOLORMAPS);
lump = R_CheckNumForNameList(name, colormaplumps, numcolormaplumps);
if (lump == LUMPERROR)
I_Error("R_ColormapNumForName: Cannot find colormap lump %.8s\n", name);
for (i = 0; i < num_extra_colormaps; i++)
if (lump == foundcolormaps[i])
return i;
foundcolormaps[num_extra_colormaps] = lump;
// aligned on 8 bit for asm code
extra_colormaps[num_extra_colormaps].colormap = Z_MallocAlign(W_LumpLength(lump), PU_LEVEL, NULL, 16);
W_ReadLump(lump, extra_colormaps[num_extra_colormaps].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..
extra_colormaps[num_extra_colormaps].maskcolor = 0xffff;
extra_colormaps[num_extra_colormaps].fadecolor = 0x0;
extra_colormaps[num_extra_colormaps].maskamt = 0x0;
extra_colormaps[num_extra_colormaps].fadestart = 0;
extra_colormaps[num_extra_colormaps].fadeend = 31;
2014-03-15 09:59:03 -07:00
extra_colormaps[num_extra_colormaps].fog = 0;
num_extra_colormaps++;
return (INT32)num_extra_colormaps - 1;
}
//
// R_CreateColormap
//
// 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);
INT32 R_CreateColormap(char *p1, char *p2, char *p3)
{
double cmaskr, cmaskg, cmaskb, cdestr, cdestg, cdestb;
double maskamt = 0, othermask = 0;
2014-03-15 09:59:03 -07:00
int mask, fog = 0;
size_t mapnum = num_extra_colormaps;
size_t i;
UINT32 cr, cg, cb, maskcolor, fadecolor;
UINT32 fadestart = 0, fadeend = 31, fadedist = 31;
2014-03-15 09:59:03 -07:00
#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)
if (p1[0] == '#')
{
cr = ((HEX2INT(p1[1]) * 16) + HEX2INT(p1[2]));
cmaskr = cr;
cg = ((HEX2INT(p1[3]) * 16) + HEX2INT(p1[4]));
cmaskg = cg;
cb = ((HEX2INT(p1[5]) * 16) + HEX2INT(p1[6]));
cmaskb = cb;
// Create a rough approximation of the color (a 16 bit color)
maskcolor = ((cb) >> 3) + (((cg) >> 2) << 5) + (((cr) >> 3) << 11);
if (p1[7] >= 'a' && p1[7] <= 'z')
mask = (p1[7] - 'a');
else if (p1[7] >= 'A' && p1[7] <= 'Z')
mask = (p1[7] - 'A');
else
mask = 24;
maskamt = (double)(mask/24.0l);
othermask = 1 - maskamt;
maskamt /= 0xff;
cmaskr *= maskamt;
cmaskg *= maskamt;
cmaskb *= maskamt;
}
else
{
cmaskr = cmaskg = cmaskb = 0xff;
maskamt = 0;
maskcolor = ((0xff) >> 3) + (((0xff) >> 2) << 5) + (((0xff) >> 3) << 11);
}
#define NUMFROMCHAR(c) (c >= '0' && c <= '9' ? c - '0' : 0)
if (p2[0] == '#')
{
// Get parameters like fadestart, fadeend, and the fogflag
fadestart = NUMFROMCHAR(p2[3]) + (NUMFROMCHAR(p2[2]) * 10);
fadeend = NUMFROMCHAR(p2[5]) + (NUMFROMCHAR(p2[4]) * 10);
if (fadestart > 30)
2014-03-15 09:59:03 -07:00
fadestart = 0;
if (fadeend > 31 || fadeend < 1)
fadeend = 31;
2014-03-15 09:59:03 -07:00
fadedist = fadeend - fadestart;
fog = NUMFROMCHAR(p2[1]);
2014-03-15 09:59:03 -07:00
}
#undef NUMFROMCHAR
2014-03-15 09:59:03 -07:00
if (p3[0] == '#')
{
cdestr = cr = ((HEX2INT(p3[1]) * 16) + HEX2INT(p3[2]));
cdestg = cg = ((HEX2INT(p3[3]) * 16) + HEX2INT(p3[4]));
cdestb = cb = ((HEX2INT(p3[5]) * 16) + HEX2INT(p3[6]));
fadecolor = (((cb) >> 3) + (((cg) >> 2) << 5) + (((cr) >> 3) << 11));
}
else
cdestr = cdestg = cdestb = fadecolor = 0;
#undef HEX2INT
for (i = 0; i < num_extra_colormaps; i++)
{
if (foundcolormaps[i] != LUMPERROR)
continue;
if (maskcolor == extra_colormaps[i].maskcolor
&& fadecolor == extra_colormaps[i].fadecolor
&& fabs(maskamt - extra_colormaps[i].maskamt) < DBL_EPSILON
2014-03-15 09:59:03 -07:00
&& fadestart == extra_colormaps[i].fadestart
&& fadeend == extra_colormaps[i].fadeend
&& fog == extra_colormaps[i].fog)
{
return (INT32)i;
}
}
if (num_extra_colormaps == MAXCOLORMAPS)
I_Error("R_CreateColormap: Too many colormaps! the limit is %d\n", MAXCOLORMAPS);
num_extra_colormaps++;
foundcolormaps[mapnum] = LUMPERROR;
// aligned on 8 bit for asm code
extra_colormaps[mapnum].colormap = NULL;
extra_colormaps[mapnum].maskcolor = (UINT16)maskcolor;
extra_colormaps[mapnum].fadecolor = (UINT16)fadecolor;
extra_colormaps[mapnum].maskamt = maskamt;
extra_colormaps[mapnum].fadestart = (UINT16)fadestart;
extra_colormaps[mapnum].fadeend = (UINT16)fadeend;
extra_colormaps[mapnum].fog = fog;
// This code creates the colormap array used by software renderer
2014-03-15 09:59:03 -07:00
if (rendermode == render_soft)
{
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
2014-03-15 09:59:03 -07:00
for (i = 0; i < 256; i++)
{
r = pLocalPalette[i].s.red;
g = pLocalPalette[i].s.green;
b = pLocalPalette[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!
colormap_p = Z_MallocAlign((256 * 34) + 10, PU_LEVEL, NULL, 8);
extra_colormaps[mapnum].colormap = (UINT8 *)colormap_p;
2014-03-15 09:59:03 -07:00
// 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
}
}
}
2014-03-15 09:59:03 -07:00
return (INT32)mapnum;
}
// Thanks to quake2 source!
// utils3/qdata/images.c
2019-05-26 19:37:23 -07:00
UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b)
2014-03-15 09:59:03 -07:00
{
int dr, dg, db;
int distortion, bestdistortion = 256 * 256 * 4, bestcolor = 0, i;
for (i = 0; i < 256; i++)
{
dr = r - pLocalPalette[i].s.red;
dg = g - pLocalPalette[i].s.green;
db = b - pLocalPalette[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;
}
const char *R_ColormapNameForNum(INT32 num)
{
if (num == -1)
return "NONE";
if (num < 0 || num > MAXCOLORMAPS)
I_Error("R_ColormapNameForNum: num %d is invalid!\n", num);
if (foundcolormaps[num] == LUMPERROR)
return "INLEVEL";
return W_CheckNameForNum(foundcolormaps[num]);
}
//
// 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();
2014-03-15 09:59:03 -07:00
CONS_Printf("R_InitSprites()...\n");
R_InitSpriteLumps();
R_InitSprites();
CONS_Printf("R_InitColormaps()...\n");
R_InitColormaps();
}
void R_ClearTextureNumCache(boolean btell)
{
if (tidcache)
Z_Free(tidcache);
tidcache = NULL;
if (btell)
CONS_Debug(DBG_SETUP, "Fun Fact: There are %d textures used in this map.\n", tidcachelen);
tidcachelen = 0;
}
//
// R_CheckTextureNumForName
//
// Check whether texture is available. Filter out NoTexture indicator.
//
INT32 R_CheckTextureNumForName(const char *name)
{
INT32 i;
// "NoTexture" marker.
if (name[0] == '-')
return 0;
for (i = 0; i < tidcachelen; i++)
if (!strncasecmp(tidcache[i].name, name, 8))
return tidcache[i].id;
// Need to parse the list backwards, so textures loaded more recently are used in lieu of ones loaded earlier
//for (i = 0; i < numtextures; i++) <- old
for (i = (numtextures - 1); i >= 0; i--) // <- new
if (!strncasecmp(textures[i]->name, name, 8))
{
tidcachelen++;
Z_Realloc(tidcache, tidcachelen * sizeof(*tidcache), PU_STATIC, &tidcache);
strncpy(tidcache[tidcachelen-1].name, name, 8);
tidcache[tidcachelen-1].name[8] = '\0';
#ifndef ZDEBUG
CONS_Debug(DBG_SETUP, "texture #%s: %s\n", sizeu1(tidcachelen), tidcache[tidcachelen-1].name);
#endif
tidcache[tidcachelen-1].id = i;
return i;
}
return -1;
}
//
// R_TextureNumForName
//
// Calls R_CheckTextureNumForName, aborts with error message.
//
INT32 R_TextureNumForName(const char *name)
{
const INT32 i = R_CheckTextureNumForName(name);
if (i == -1)
{
static INT32 redwall = -2;
CONS_Debug(DBG_SETUP, "WARNING: R_TextureNumForName: %.8s not found\n", name);
if (redwall == -2)
redwall = R_CheckTextureNumForName("REDWALL");
if (redwall != -1)
return redwall;
return 1;
}
return i;
}
//
// 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));
2014-03-15 09:59:03 -07:00
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)
2014-03-15 09:59:03 -07:00
texturepresent[sides[j].toptexture] = 1;
if (sides[j].midtexture >= 0 && sides[j].midtexture < numtextures)
2014-03-15 09:59:03 -07:00
texturepresent[sides[j].midtexture] = 1;
if (sides[j].bottomtexture >= 0 && sides[j].bottomtexture < numtextures)
2014-03-15 09:59:03 -07:00
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 = thinkercap.next; th != &thinkercap; th = th->next)
if (th->function.acp1 == (actionf_p1)P_MobjThinker)
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];
for (k = 0; k < 8; k++)
{
// see R_InitSprites for more about lumppat,lumpid
lump = sf->lumppat[k];
if (devparm)
spritememory += W_LumpLength(lump);
W_CachePatchNum(lump, PU_CACHE);
}
}
}
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));
}
// https://github.com/coelckers/prboom-plus/blob/master/prboom2/src/r_patch.c#L350
boolean R_CheckIfPatch(lumpnum_t lump)
{
size_t size;
INT16 width, height;
patch_t *patch;
boolean result;
size = W_LumpLength(lump);
// minimum length of a valid Doom patch
if (size < 13)
return false;
patch = (patch_t *)W_CacheLumpNum(lump, PU_STATIC);
width = SHORT(patch->width);
height = SHORT(patch->height);
2019-05-21 07:03:53 -07:00
result = (height > 0 && height <= 16384 && width > 0 && width <= 16384 && width < (INT16)(size / 4));
if (result)
{
// The dimensions seem like they might be valid for a patch, so
// check the column directory for extra security. All columns
// must begin after the column directory, and none of them must
// point past the end of the patch.
INT16 x;
for (x = 0; x < width; x++)
{
UINT32 ofs = LONG(patch->columnofs[x]);
// Need one byte for an empty column (but there's patches that don't know that!)
if (ofs < (UINT32)width * 4 + 8 || ofs >= (UINT32)size)
{
result = false;
break;
}
}
}
return result;
}
2019-05-26 19:37:23 -07:00
void R_PatchToFlat(patch_t *patch, UINT8 *flat)
{
fixed_t col, ofs;
column_t *column;
UINT8 *desttop, *dest, *deststop;
UINT8 *source;
desttop = flat;
2019-05-26 12:22:33 -07:00
deststop = desttop + (SHORT(patch->width) * SHORT(patch->height));
for (col = 0; col < SHORT(patch->width); col++, desttop++)
{
INT32 topdelta, prevdelta = -1;
column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[col]));
while (column->topdelta != 0xff)
{
topdelta = column->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
2019-05-26 12:22:33 -07:00
dest = desttop + (topdelta * SHORT(patch->width));
source = (UINT8 *)(column) + 3;
for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
{
if (source[ofs] != TRANSPARENTPIXEL)
*dest = source[ofs];
2019-05-26 12:22:33 -07:00
dest += SHORT(patch->width);
}
column = (column_t *)((UINT8 *)column + column->length + 4);
}
}
}
2019-05-26 19:37:23 -07:00
#ifndef NO_PNG_LUMPS
boolean R_IsLumpPNG(UINT8 *d, size_t s)
{
if (s < 67) // http://garethrees.org/2007/11/14/pngcrush/
return false;
// Check for PNG file signature using memcmp
// As it may be faster on CPUs with slow unaligned memory access
// Ref: http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature
return (memcmp(&d[0], "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0);
}
#ifdef HAVE_PNG
typedef struct {
png_bytep buffer;
png_uint_32 bufsize;
png_uint_32 current_pos;
} png_ioread;
static void PNG_IOReader(png_structp png_ptr, png_bytep data, png_size_t length)
{
png_ioread *f = png_get_io_ptr(png_ptr);
if (length > (f->bufsize - f->current_pos))
png_error(png_ptr, "read error in read_data_memory (loadpng)");
memcpy(data, f->buffer + f->current_pos, length);
f->current_pos += length;
}
static void PNG_error(png_structp PNG, png_const_charp pngtext)
{
CONS_Debug(DBG_RENDER, "libpng error at %p: %s", PNG, pngtext);
//I_Error("libpng error at %p: %s", PNG, pngtext);
}
static void PNG_warn(png_structp PNG, png_const_charp pngtext)
{
CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
}
static png_bytep *PNG_Read(UINT8 *png, UINT16 *w, UINT16 *h, size_t size)
{
png_structp png_ptr;
png_infop png_info_ptr;
png_uint_32 width, height;
int bit_depth, color_type;
png_uint_32 y;
#ifdef PNG_SETJMP_SUPPORTED
#ifdef USE_FAR_KEYWORD
jmp_buf jmpbuf;
#endif
#endif
png_ioread png_io;
png_bytep *row_pointers;
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
PNG_error, PNG_warn);
if (!png_ptr)
{
CONS_Debug(DBG_RENDER, "PNG_Load: Error on initialize libpng\n");
return NULL;
}
png_info_ptr = png_create_info_struct(png_ptr);
if (!png_info_ptr)
{
CONS_Debug(DBG_RENDER, "PNG_Load: Error on allocate for libpng\n");
png_destroy_read_struct(&png_ptr, NULL, NULL);
return NULL;
}
#ifdef USE_FAR_KEYWORD
if (setjmp(jmpbuf))
#else
if (setjmp(png_jmpbuf(png_ptr)))
#endif
{
//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
return NULL;
}
#ifdef USE_FAR_KEYWORD
png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
#endif
// png_source is array which have png data
png_io.buffer = (png_bytep)png;
png_io.bufsize = size;
png_io.current_pos = 0;
// set our own read_function
png_set_read_fn(png_ptr, &png_io, PNG_IOReader);
#ifdef PNG_SET_USER_LIMITS_SUPPORTED
png_set_user_limits(png_ptr, 2048, 2048);
#endif
png_read_info(png_ptr, png_info_ptr);
png_get_IHDR(png_ptr, png_info_ptr, &width, &height, &bit_depth, &color_type,
NULL, NULL, NULL);
if (bit_depth == 16)
png_set_strip_16(png_ptr);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png_ptr);
else if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png_ptr);
if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png_ptr);
else if (color_type != PNG_COLOR_TYPE_RGB_ALPHA && color_type != PNG_COLOR_TYPE_GRAY_ALPHA)
{
#if PNG_LIBPNG_VER < 10207
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
#else
png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
#endif
}
png_read_update_info(png_ptr, png_info_ptr);
// Read the image
row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
for (y = 0; y < height; y++)
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, png_info_ptr));
png_read_image(png_ptr, row_pointers);
png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
*w = (INT32)width;
*h = (INT32)height;
return row_pointers;
}
// Convert a PNG to a raw image.
static UINT8 *PNG_RawConvert(UINT8 *png, UINT16 *w, UINT16 *h, size_t size)
{
UINT8 *flat;
png_uint_32 x, y;
png_bytep *row_pointers = PNG_Read(png, w, h, size);
png_uint_32 width = *w, height = *h;
if (!row_pointers)
return NULL;
// Convert the image to 8bpp
flat = Z_Malloc(width * height, PU_STATIC, NULL);
memset(flat, TRANSPARENTPIXEL, width * height);
for (y = 0; y < height; y++)
{
png_bytep row = row_pointers[y];
for (x = 0; x < width; x++)
{
png_bytep px = &(row[x * 4]);
flat[((y * width) + x)] = NearestColor((UINT8)px[0], (UINT8)px[1], (UINT8)px[2]);
}
}
free(row_pointers);
return flat;
}
// Get the alpha mask of the image.
static UINT8 *PNG_GetAlphaMask(UINT8 *png, size_t size)
{
UINT8 *mask;
png_uint_32 x, y;
UINT16 width, height;
png_bytep *row_pointers = PNG_Read(png, &width, &height, size);
if (!row_pointers)
return NULL;
// Convert the image to 8bpp
mask = Z_Malloc(width * height, PU_STATIC, NULL);
memset(mask, 0, width * height);
for (y = 0; y < height; y++)
{
png_bytep row = row_pointers[y];
for (x = 0; x < width; x++)
{
png_bytep px = &(row[x * 4]);
mask[((y * width) + x)] = (UINT8)px[3];
}
}
free(row_pointers);
return mask;
}
// Convert a PNG to a flat.
UINT8 *R_PNGToFlat(levelflat_t *levelflat, UINT8 *png, size_t size)
{
return PNG_RawConvert(png, &levelflat->width, &levelflat->height, size);
}
// Convert a PNG to a patch.
// This is adapted from the "kartmaker" utility
static unsigned char imgbuf[1<<26];
patch_t *R_PNGToPatch(UINT8 *png, size_t size)
{
UINT16 width, height;
UINT8 *raw = PNG_RawConvert(png, &width, &height, size);
UINT8 *alphamask = PNG_GetAlphaMask(png, size);
UINT32 x, y;
UINT8 *img;
UINT8 *imgptr = imgbuf;
UINT8 *colpointers, *startofspan;
#define WRITE8(buf, a) ({*buf = (a); buf++;})
#define WRITE16(buf, a) ({*buf = (a)&255; buf++; *buf = (a)>>8; buf++;})
#define WRITE32(buf, a) ({WRITE16(buf, (a)&65535); WRITE16(buf, (a)>>16);})
if (!raw)
return NULL;
// Write image size and offset
WRITE16(imgptr, width);
WRITE16(imgptr, height);
// no offsets
WRITE16(imgptr, 0);
WRITE16(imgptr, 0);
// Leave placeholder to column pointers
colpointers = imgptr;
imgptr += width*4;
// Write columns
for (x = 0; x < width; x++)
{
int lastStartY = 0;
int spanSize = 0;
startofspan = NULL;
//printf("%d ", x);
// Write column pointer (@TODO may be wrong)
WRITE32(colpointers, imgptr - imgbuf);
// Write pixels
for (y = 0; y < height; y++)
{
UINT8 paletteIndex = raw[((y * width) + x)];
UINT8 opaque = alphamask[((y * width) + x)]; // If 1, we have a pixel
// End span if we have a transparent pixel
if (!opaque)
{
if (startofspan)
WRITE8(imgptr, 0);
startofspan = NULL;
continue;
}
// Start new column if we need to
if (!startofspan || spanSize == 255)
{
int writeY = y;
// If we reached the span size limit, finish the previous span
if (startofspan)
WRITE8(imgptr, 0);
if (y > 254)
{
// Make sure we're aligned to 254
if (lastStartY < 254)
{
WRITE8(imgptr, 254);
WRITE8(imgptr, 0);
imgptr += 2;
lastStartY = 254;
}
// Write stopgap empty spans if needed
writeY = y - lastStartY;
while (writeY > 254)
{
WRITE8(imgptr, 254);
WRITE8(imgptr, 0);
imgptr += 2;
writeY -= 254;
}
}
startofspan = imgptr;
WRITE8(imgptr, writeY);///@TODO calculate starting y pos
imgptr += 2;
spanSize = 0;
lastStartY = y;
}
// Write the pixel
WRITE8(imgptr, paletteIndex);
spanSize++;
startofspan[1] = spanSize;
}
if (startofspan)
WRITE8(imgptr, 0);
WRITE8(imgptr, 0xFF);
}
#undef WRITE8
#undef WRITE16
#undef WRITE32
size = imgptr-imgbuf;
img = malloc(size);
memcpy(img, imgbuf, size);
return (patch_t *)img;
}
boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size)
{
png_structp png_ptr;
png_infop png_info_ptr;
png_uint_32 w, h;
int bit_depth, color_type;
#ifdef PNG_SETJMP_SUPPORTED
#ifdef USE_FAR_KEYWORD
jmp_buf jmpbuf;
#endif
#endif
png_ioread png_io;
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
PNG_error, PNG_warn);
if (!png_ptr)
{
CONS_Debug(DBG_RENDER, "PNG_Load: Error on initialize libpng\n");
return false;
}
png_info_ptr = png_create_info_struct(png_ptr);
if (!png_info_ptr)
{
CONS_Debug(DBG_RENDER, "PNG_Load: Error on allocate for libpng\n");
png_destroy_read_struct(&png_ptr, NULL, NULL);
return false;
}
#ifdef USE_FAR_KEYWORD
if (setjmp(jmpbuf))
#else
if (setjmp(png_jmpbuf(png_ptr)))
#endif
{
//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
return false;
}
#ifdef USE_FAR_KEYWORD
png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
#endif
// png_source is array which have png data
png_io.buffer = (png_bytep)png;
png_io.bufsize = size;
png_io.current_pos = 0;
// set our own read_function
png_set_read_fn(png_ptr, &png_io, PNG_IOReader);
#ifdef PNG_SET_USER_LIMITS_SUPPORTED
png_set_user_limits(png_ptr, 2048, 2048);
#endif
png_read_info(png_ptr, png_info_ptr);
png_get_IHDR(png_ptr, png_info_ptr, &w, &h, &bit_depth, &color_type,
NULL, NULL, NULL);
// okay done. stop.
png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
*width = (INT32)w;
*height = (INT32)h;
return true;
}
#endif
#endif
void R_TextureToFlat(size_t tex, UINT8 *flat)
{
texture_t *texture = textures[tex];
fixed_t col, ofs;
column_t *column;
UINT8 *desttop, *dest, *deststop;
UINT8 *source;
desttop = flat;
deststop = desttop + (texture->width * texture->height);
2019-05-26 12:22:33 -07:00
for (col = 0; col < texture->width; col++, desttop++)
{
column = (column_t *)R_GetColumn(tex, col);
if (!texture->holes)
{
dest = desttop;
source = (UINT8 *)(column);
for (ofs = 0; dest < deststop && ofs < texture->height; ofs++)
{
if (source[ofs] != TRANSPARENTPIXEL)
*dest = source[ofs];
dest += texture->width;
}
}
else
{
2019-05-26 12:22:33 -07:00
INT32 topdelta, prevdelta = -1;
while (column->topdelta != 0xff)
{
topdelta = column->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
dest = desttop + (topdelta * texture->width);
source = (UINT8 *)(column) + 3;
for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
{
if (source[ofs] != TRANSPARENTPIXEL)
*dest = source[ofs];
dest += texture->width;
}
column = (column_t *)((UINT8 *)column + column->length + 4);
}
}
}
}