Files
SRB2Kart-Saturn/src/r_patch.cpp
T
alufolie91 e94fecf56d cleanup even more style issues
also should fix R_InitSprites printing the wrong spritename in some cases
2026-06-16 19:08:04 +02:00

811 lines
20 KiB
C++

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2005-2009 by Andrey "entryway" Budko.
// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
// Copyright (C) 2019-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_patch.c
/// \brief Patch generation.
#include "byteptr.h"
#include "doomdef.h"
#include "r_patch.h"
#include "r_things.h"
#include "r_skins.h"
#include "z_zone.h"
#include "w_wad.h"
#include <vector>
#ifdef HWRENDER
#include "hardware/hw_glob.h"
#endif
//
// R_ParseSpriteInfoFrame
//
// Parse a SPRTINFO frame.
//
static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
{
char *sprinfoToken;
size_t sprinfoTokenLength;
char *frameChar = NULL;
UINT8 frameFrame = 0xFF;
INT16 frameXPivot = 0;
INT16 frameYPivot = 0;
rotaxis_t frameRotAxis = ROTAXIS_X;
// Sprite identifier
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be");
}
sprinfoTokenLength = strlen(sprinfoToken);
if (sprinfoTokenLength != 1)
{
I_Error("Error parsing SPRTINFO lump: Invalid frame \"%s\"",sprinfoToken);
}
frameChar = sprinfoToken;
frameFrame = R_Char2Frame(frameChar[0]);
Z_Free(sprinfoToken);
// Left Curly Brace
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
I_Error("Error parsing SPRTINFO lump: Missing sprite info");
if (fastcmp(sprinfoToken, "{"))
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be");
}
while (!fastcmp(sprinfoToken, "}"))
{
if (fasticmp(sprinfoToken, "XPIVOT"))
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
frameXPivot = atoi(sprinfoToken);
}
else if (fasticmp(sprinfoToken, "YPIVOT"))
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
frameYPivot = atoi(sprinfoToken);
}
else if (fasticmp(sprinfoToken, "ROTAXIS"))
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
if (fasticmp(sprinfoToken, "X") || fasticmp(sprinfoToken, "XAXIS") || fasticmp(sprinfoToken, "ROLL"))
frameRotAxis = ROTAXIS_X;
else if (fasticmp(sprinfoToken, "Y") || fasticmp(sprinfoToken, "YAXIS") || fasticmp(sprinfoToken, "PITCH"))
frameRotAxis = ROTAXIS_Y;
else if (fasticmp(sprinfoToken, "Z") || fasticmp(sprinfoToken, "ZAXIS") || fasticmp(sprinfoToken, "YAW"))
frameRotAxis = ROTAXIS_Z;
}
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be");
}
}
}
Z_Free(sprinfoToken);
// set fields
info->pivot[frameFrame].x = frameXPivot;
info->pivot[frameFrame].y = frameYPivot;
info->pivot[frameFrame].rotaxis = frameRotAxis;
}
//
// R_ParseSpriteInfo
//
// Parse a SPRTINFO lump.
//
static void R_ParseSpriteInfo(boolean spr2)
{
spriteinfo_t *info;
char *sprinfoToken;
size_t sprinfoTokenLength;
char newSpriteName[5]; // no longer dynamically allocated
spritenum_t sprnum = NUMSPRITES;
//playersprite_t spr2num = NUMPLAYERSPRITES;
INT32 i;
INT32 skinnumbers[MAXSKINS]; // >variable 'skinnumbers' set but not used >compile error when commented out
INT32 foundskins = 0;
// Sprite name
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be");
}
sprinfoTokenLength = strlen(sprinfoToken);
if (sprinfoTokenLength != 4)
{
I_Error("Error parsing SPRTINFO lump: Sprite name \"%s\" isn't 4 characters long",sprinfoToken);
}
else
{
memset(&newSpriteName, 0, 5);
memcpy(newSpriteName, sprinfoToken, sprinfoTokenLength);
// ^^ we've confirmed that the token is == 4 characters so it will never overflow a 5 byte char buffer
strupr(newSpriteName); // Just do this now so we don't have to worry about it
}
Z_Free(sprinfoToken);
if (!spr2)
{
for (i = 0; i <= NUMSPRITES; i++)
{
if (i == NUMSPRITES)
I_Error("Error parsing SPRTINFO lump: Unknown sprite name \"%s\"", newSpriteName);
if (!memcmp(newSpriteName, sprnames[i], 4))
{
sprnum = static_cast<spritenum_t>(i);
break;
}
}
}
// allocate a spriteinfo
info = static_cast<spriteinfo_t*>(Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL));
info->available = true;
// Left Curly Brace
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be",newSpriteName);
}
if (fastcmp(sprinfoToken, "{"))
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be",newSpriteName);
}
while (!fastcmp(sprinfoToken, "}"))
{
if (fasticmp(sprinfoToken, "SKIN"))
{
INT32 skinnum;
char *skinName = NULL;
if (!spr2)
I_Error("Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition");
Z_Free(sprinfoToken);
// Skin name
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where skin frame should be");
}
// copy skin name yada yada
sprinfoTokenLength = strlen(sprinfoToken);
skinName = (char *)Z_Malloc((sprinfoTokenLength+1)*sizeof(char),PU_STATIC,NULL);
memcpy(skinName,sprinfoToken,sprinfoTokenLength*sizeof(char));
skinName[sprinfoTokenLength] = '\0';
strlwr(skinName);
Z_Free(sprinfoToken);
skinnum = R_AnySkinAvailable(skinName);
if (skinnum == -1)
I_Error("Error parsing SPRTINFO lump: Unknown skin \"%s\"", skinName);
skinnumbers[foundskins] = skinnum;
foundskins++;
}
else if (fasticmp(sprinfoToken, "FRAME"))
{
R_ParseSpriteInfoFrame(info);
Z_Free(sprinfoToken);
if (spr2)
{
if (!foundskins)
I_Error("Error parsing SPRTINFO lump: No skins specified in this sprite2 definition");
for (i = 0; i < foundskins; i++)
{
size_t skinnum = skinnumbers[i];
skin_t *skin;
if (allskins[skinnum].localskin)
skin = &localskins[allskins[skinnum].localnum];
else
skin = &skins[allskins[skinnum].localnum];
memcpy(&skin->sprinfo, info, sizeof(spriteinfo_t));
}
}
else
memcpy(&spriteinfo[sprnum], info, sizeof(spriteinfo_t));
}
else
{
I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s",sprinfoToken,newSpriteName);
}
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be",newSpriteName);
}
}
}
else
{
I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken);
}
Z_Free(sprinfoToken);
Z_Free(info);
}
//
// R_ParseSPRTINFOLump
//
// Read a SPRTINFO lump.
//
void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum)
{
char *sprinfoLump;
size_t sprinfoLumpLength;
char *sprinfoText;
char *sprinfoToken;
// 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.
sprinfoLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
// If that didn't exist, we have nothing to do here.
if (sprinfoLump == NULL) return;
// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
sprinfoLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
sprinfoText = (char *)Z_Malloc((sprinfoLumpLength+1)*sizeof(char),PU_STATIC,NULL);
// Now move the contents of the lump into this new location.
memmove(sprinfoText, sprinfoLump, sprinfoLumpLength);
// Make damn well sure the last character in our new memory location is \0.
sprinfoText[sprinfoLumpLength] = '\0';
// Finally, free up the memory from the first data load, because we really
// don't need it.
Z_Free(sprinfoLump);
sprinfoToken = M_GetToken(sprinfoText);
while (sprinfoToken != NULL)
{
CONS_Printf("Attempting to parse le SPRTINFO lump for ya..\n");
if (fasticmp(sprinfoToken, "SPRITE"))
R_ParseSpriteInfo(false);
else if (fasticmp(sprinfoToken, "SPRITE2"))
R_ParseSpriteInfo(true);
else
I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\"", sprinfoToken);
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
}
Z_Free((void *)sprinfoText);
}
//
// R_LoadSpriteInfoLumps
//
// Load and read every SPRTINFO lump from the specified file.
//
void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps)
{
lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo;
UINT16 i;
char *name;
for (i = 0; i < numlumps; i++, lumpinfo++)
{
name = lumpinfo->name;
// Load SPRTINFO and SPR_ lumps as SpriteInfo
if (!memcmp(name, "SPRTINFO", 8) || !memcmp(name, "SPR_", 4))
R_ParseSPRTINFOLump(wadnum, i);
}
}
//
// Creates a patch.
// Assumes a PU_PATCH zone memory tag and no user, but can always be set later
//
patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest)
{
patch_t *patch = static_cast<patch_t*>((dest == NULL) ? Z_Calloc(sizeof(patch_t), PU_PATCH, NULL) : dest);
if (source)
{
INT32 col, colsize;
size_t size = sizeof(INT32) * SHORT(source->width);
size_t offs = (sizeof(INT16) * 4) + size;
patch->width = SHORT(source->width);
patch->height = SHORT(source->height);
patch->leftoffset = SHORT(source->leftoffset);
patch->topoffset = SHORT(source->topoffset);
patch->columnofs = static_cast<INT32*>(Z_Calloc(size, PU_PATCH_DATA, NULL));
for (col = 0; col < source->width; col++)
{
// This makes the column offsets relative to the column data itself,
// instead of the entire patch data
patch->columnofs[col] = LONG(source->columnofs[col]) - offs;
}
if (!srcsize)
I_Error("Patch_Create: no source size!");
colsize = (INT32)(srcsize) - (INT32)offs;
if (colsize <= 0)
I_Error("Patch_Create: no column data!");
patch->columns = static_cast<UINT8*>(Z_Calloc(colsize, PU_PATCH_DATA, NULL));
memcpy(patch->columns, ((UINT8 *)source + LONG(source->columnofs[0])), colsize);
}
return patch;
}
//
// Frees a patch from memory.
//
static void Patch_FreeData(patch_t *patch)
{
#ifdef HWRENDER
if (patch->hardware)
HWR_FreeTexture(patch);
#endif
#ifdef ROTSPRITE
/*if (patch->rotated)
{
rotsprite_t *rotsprite = patch->rotated;
INT32 i = 0;
for (; i < rotsprite->angles; i++)
{
if (rotsprite->patches[i])
Patch_Free(rotsprite->patches[i]);
}
Z_Free(rotsprite->patches);
Z_Free(rotsprite);
}*/
#endif
Z_Free(patch->columnofs);
Z_Free(patch->columns);
}
void Patch_Free(patch_t *patch)
{
Patch_FreeData(patch);
Z_Free(patch);
}
//
// Frees patches with a tag range.
//
static boolean Patch_FreeTagsCallback(void *mem)
{
patch_t *patch = (patch_t *)mem;
Patch_FreeData(patch);
return true;
}
void Patch_FreeTags(INT32 lowtag, INT32 hightag)
{
Z_IterateTags(lowtag, hightag, Patch_FreeTagsCallback);
}
#ifdef HWRENDER
//
// Allocates a hardware patch.
//
void *Patch_AllocateHardwarePatch(patch_t *patch)
{
if (!patch->hardware)
{
GLPatch_t *glPatch = static_cast<GLPatch_t*>(Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, &patch->hardware));
glPatch->mipmap = static_cast<GLMipmap_t*>(Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, &glPatch->mipmap));
}
return (void *)(patch->hardware);
}
//
// Creates a hardware patch.
//
void *Patch_CreateGL(patch_t *patch)
{
GLPatch_t *glPatch = (GLPatch_t *)Patch_AllocateHardwarePatch(patch);
if (!glPatch->mipmap->data) // Run HWR_MakePatch in all cases, to recalculate some things
HWR_MakePatch(patch, glPatch, glPatch->mipmap, false);
return glPatch;
}
#endif // HWRENDER
// 1 MB should be enough for most cases
static std::vector<UINT8> imgbuf(1024 * 1024);
//
// R_MaskedFlatToPatch
//
// Convert raw pixels to a patch.
//
void *R_PixelsToPatch(UINT8 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize)
{
INT16 x, y;
UINT8 *img, *imgptr;
UINT8 *colpointers, *startofspan;
size_t size = 0;
if (!raw)
return NULL;
// Allocate a staging buffer with the maximum size needed for a patch of the same size as the input.
// round up to nearest multiple of 254-pixel posts, plus 1 more 254-pixel post for paranoia reasons
size_t maxcolumnsize = (2 + (height - 1) / 256) * 256;
// the patch header, and width columns of the max column size
size_t maxoutsize = maxcolumnsize * width + (8 + 4 * width);
// so, a 512x512 flat should maximally need 393,760 (384.53 KiB) bytes.
// quite a bit smaller than 64 megabytes, and much less annoying to the windows debug allocator!
imgbuf.resize(maxoutsize);
imgptr = imgbuf.data();
// Write image size and offset
WRITEINT16(imgptr, width);
WRITEINT16(imgptr, height);
WRITEINT16(imgptr, leftoffset);
WRITEINT16(imgptr, topoffset);
// 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;
// Write column pointer
WRITEINT32(colpointers, imgptr - imgbuf.data());
// Write pixels
for (y = 0; y < height; y++)
{
UINT8 pixel = raw[((y * width) + x)];
UINT8 opaque = (pixel != 0xf7); // If not 247 (0xf7), we have a pixel
// End span if we have a transparent pixel
if (!opaque)
{
if (startofspan)
WRITEUINT8(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)
WRITEUINT8(imgptr, 0);
if (y > 254)
{
// Make sure we're aligned to 254
if (lastStartY < 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
lastStartY = 254;
}
// Write stopgap empty spans if needed
writeY = y - lastStartY;
while (writeY > 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
writeY -= 254;
}
}
startofspan = imgptr;
WRITEUINT8(imgptr, writeY);
imgptr += 2;
spanSize = 0;
lastStartY = y;
}
// Write the pixel
WRITEUINT8(imgptr, pixel);
spanSize++;
startofspan[1] = spanSize;
}
if (startofspan)
WRITEUINT8(imgptr, 0);
WRITEUINT8(imgptr, 0xFF);
}
size = imgptr-imgbuf.data();
img = static_cast<UINT8*>(Z_Malloc(size, PU_STATIC, NULL));
memcpy(img, imgbuf.data(), size);
if (destsize != NULL)
*destsize = size;
patch_t *converted = Patch_Create((softwarepatch_t *)img, size, NULL);
#ifdef HWRENDER
Patch_CreateGL(converted);
#endif
Z_Free(img);
return converted;
}
//
// R_MaskedFlatToPatch
//
// Convert a masked flat to a patch.
// Explanation of "masked" flats in R_PatchToMaskedFlat.
//
void *R_MaskedFlatToPatch(UINT16 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize)
{
INT16 x, y;
UINT8 *img, *imgptr;
UINT8 *colpointers, *startofspan;
size_t size = 0;
if (!raw)
return NULL;
// Allocate a staging buffer with the maximum size needed for a patch of the same size as the input.
// round up to nearest multiple of 254-pixel posts, plus 1 more 254-pixel post for paranoia reasons
size_t maxcolumnsize = (2 + (height - 1) / 256) * 256;
// the patch header, and width columns of the max column size
size_t maxoutsize = maxcolumnsize * width + (8 + 4 * width);
// so, a 512x512 flat should maximally need 393,760 (384.53 KiB) bytes.
// quite a bit smaller than 64 megabytes, and much less annoying to the windows debug allocator!
imgbuf.resize(maxoutsize);
imgptr = imgbuf.data();
// Write image size and offset
WRITEINT16(imgptr, width);
WRITEINT16(imgptr, height);
WRITEINT16(imgptr, leftoffset);
WRITEINT16(imgptr, topoffset);
// 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;
// Write column pointer
WRITEINT32(colpointers, imgptr - imgbuf.data());
// Write pixels
for (y = 0; y < height; y++)
{
UINT16 pixel = raw[((y * width) + x)];
UINT8 paletteIndex = (pixel & 0xFF);
UINT8 opaque = (pixel != 0xFF00); // If 1, we have a pixel
// End span if we have a transparent pixel
if (!opaque)
{
if (startofspan)
WRITEUINT8(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)
WRITEUINT8(imgptr, 0);
if (y > 254)
{
// Make sure we're aligned to 254
if (lastStartY < 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
lastStartY = 254;
}
// Write stopgap empty spans if needed
writeY = y - lastStartY;
while (writeY > 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
writeY -= 254;
}
}
startofspan = imgptr;
WRITEUINT8(imgptr, writeY);
imgptr += 2;
spanSize = 0;
lastStartY = y;
}
// Write the pixel
WRITEUINT8(imgptr, paletteIndex);
spanSize++;
startofspan[1] = spanSize;
}
if (startofspan)
WRITEUINT8(imgptr, 0);
WRITEUINT8(imgptr, 0xFF);
}
size = imgptr-imgbuf.data();
img = static_cast<UINT8*>(Z_Malloc(size, PU_STATIC, NULL));
memcpy(img, imgbuf.data(), size);
if (destsize != NULL)
*destsize = size;
patch_t *converted = Patch_Create((softwarepatch_t *)img, size, NULL);
#ifdef HWRENDER
Patch_CreateGL(converted);
#endif
Z_Free(img);
return converted;
}
UINT16 R_GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip)
{
fixed_t ofs;
column_t *column;
UINT8 *source = NULL;
INT16 width;
if (patch == NULL)
I_Error("GetPatchPixel: patch == NULL");
width = patch->width;
if (x >= 0 && x < width)
{
INT32 colx = flip ? (width-1)-x : x;
INT32 topdelta, prevdelta = -1;
column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[colx]));
while (column->topdelta != 0xff)
{
topdelta = column->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
ofs = (y - topdelta);
if (y >= topdelta && ofs < column->length)
{
source = (UINT8 *)(column) + 3;
return source[ofs];
}
column = (column_t *)((UINT8 *)column + column->length + 4);
}
}
return 0xFF00;
}
void R_PatchToPixels(patch_t *patch, UINT8 *dst)
{
const INT16 width = patch->width;
// init out destination to cyan so we can easily check for it when we convert it back to a patch
memset(dst, 0xf7, (width * patch->height)); // should this be here or should this be taken care of before passing it?
for (INT32 x = 0; x < width; ++x)
{
INT32 y = 0;
INT32 prevdelta = -1;
column_t *column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[x]));
while (column->topdelta != 0xff)
{
INT32 topdelta = column->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
for (; y-topdelta < column->length; ++y)
{
UINT8 pixel = 0;
if (y >= topdelta)
{
UINT8 *source = (UINT8*)(column) + 3;
pixel = source[y-topdelta];
}
dst[(y*width) + x] = pixel;
}
column = (column_t *)((UINT8 *)column + column->length + 4);
}
}
}