// 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 #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); } else 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"); else { if (strcmp(sprinfoToken,"{")==0) { 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 (strcmp(sprinfoToken,"}")!=0) { if (stricmp(sprinfoToken, "XPIVOT")==0) { Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); frameXPivot = atoi(sprinfoToken); } else if (stricmp(sprinfoToken, "YPIVOT")==0) { Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); frameYPivot = atoi(sprinfoToken); } else if (stricmp(sprinfoToken, "ROTAXIS")==0) { Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); if ((stricmp(sprinfoToken, "X")==0) || (stricmp(sprinfoToken, "XAXIS")==0) || (stricmp(sprinfoToken, "ROLL")==0)) frameRotAxis = ROTAXIS_X; else if ((stricmp(sprinfoToken, "Y")==0) || (stricmp(sprinfoToken, "YAXIS")==0) || (stricmp(sprinfoToken, "PITCH")==0)) frameRotAxis = ROTAXIS_Y; else if ((stricmp(sprinfoToken, "Z")==0) || (stricmp(sprinfoToken, "ZAXIS")==0) || (stricmp(sprinfoToken, "YAW")==0)) 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); M_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(i); break; } } } // allocate a spriteinfo info = static_cast(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 (strcmp(sprinfoToken,"{")==0) { 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 (strcmp(sprinfoToken,"}")!=0) { if (stricmp(sprinfoToken, "SKIN")==0) { 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); M_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 (stricmp(sprinfoToken, "FRAME")==0) { 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]; M_Memcpy(&skin->sprinfo, info, sizeof(spriteinfo_t)); } } else M_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 (!stricmp(sprinfoToken, "SPRITE")) R_ParseSpriteInfo(false); else if (!stricmp(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((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(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(Z_Calloc(colsize, PU_PATCH_DATA, NULL)); M_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 if (patch->columnofs) Z_Free(patch->columnofs); if (patch->columns) 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(Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, &patch->hardware)); glPatch->mipmap = static_cast(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 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! while (imgbuf.size() < maxoutsize) { imgbuf.resize(imgbuf.size() * 2); } 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(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! while (imgbuf.size() < maxoutsize) { imgbuf.resize(imgbuf.size() * 2); } 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(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); } } }