mirror of
https://github.com/odamex/odamex
synced 2026-08-19 02:23:09 -04:00
1206 lines
29 KiB
C++
1206 lines
29 KiB
C++
// Emacs style mode select -*- C++ -*-
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id$
|
|
//
|
|
// Copyright (C) 1998-2006 by Randy Heit (ZDoom).
|
|
// Copyright (C) 2006-2026 by The Odamex Team.
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// Revision 1.3 1997/01/29 20:10
|
|
// DESCRIPTION:
|
|
// Preparation of data for rendering,
|
|
// generation of lookups, caching, retrieval by name.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
#include "odamex.h"
|
|
|
|
#include "i_system.h"
|
|
#include "z_zone.h"
|
|
|
|
|
|
#include "w_wad.h"
|
|
|
|
#include "r_local.h"
|
|
|
|
#include "r_sky.h"
|
|
|
|
#include "cmdlib.h"
|
|
|
|
#include "r_data.h"
|
|
|
|
#include "v_palette.h"
|
|
#include "v_video.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <cmath>
|
|
|
|
#include <algorithm>
|
|
#include <unordered_set>
|
|
|
|
//
|
|
// Graphics.
|
|
// DOOM 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.
|
|
//
|
|
|
|
int firstflat;
|
|
int lastflat;
|
|
int numflats;
|
|
|
|
int firstspritelump;
|
|
int lastspritelump;
|
|
int numspritelumps;
|
|
|
|
int numtextures;
|
|
texture_t** textures;
|
|
|
|
int* texturewidthmask;
|
|
|
|
// needed for texture pegging
|
|
fixed_t* textureheight;
|
|
static int* texturecompositesize;
|
|
static unsigned **texturecolumnofs;
|
|
static byte** texturecomposite;
|
|
fixed_t* texturescalex;
|
|
fixed_t* texturescaley;
|
|
|
|
// for global animation
|
|
bool* flatwarp;
|
|
byte** warpedflats;
|
|
int* flatwarpedwhen;
|
|
int* flattranslation;
|
|
|
|
using texhash_t = std::unordered_map<OLumpName, int32_t>;
|
|
texhash_t texturehash;
|
|
|
|
//
|
|
// R_CalculateNewPatchSize
|
|
//
|
|
// Helper function for converting raw patches that use post_t into patches
|
|
// that use tallpost_t. Returns the lump size of the converted patch.
|
|
//
|
|
size_t R_CalculateNewPatchSize(patch_t *patch, size_t length)
|
|
{
|
|
if (!patch)
|
|
return 0;
|
|
|
|
// sanity check to see if the postofs array fits in the patch lump
|
|
if (length < patch->width() * sizeof(unsigned int))
|
|
return 0;
|
|
|
|
int numposts = 0, numpixels = 0;
|
|
unsigned int *postofs = (unsigned int *)((byte*)patch + 8);
|
|
|
|
for (int i = 0; i < patch->width(); i++)
|
|
{
|
|
size_t ofs = LELONG(postofs[i]);
|
|
|
|
// check that the offset is valid
|
|
if (ofs >= length)
|
|
return 0;
|
|
|
|
post_t *post = (post_t*)((byte*)patch + ofs);
|
|
|
|
while (post->topdelta != 0xFF)
|
|
{
|
|
if (ofs + post->length >= length)
|
|
return 0; // patch is corrupt
|
|
|
|
numposts++;
|
|
numpixels += post->length;
|
|
post = (post_t*)((byte*)post + post->length + 4);
|
|
}
|
|
}
|
|
|
|
// 8 byte patch header
|
|
// 4 * width bytes for column offset table
|
|
// 4 bytes per post for post header
|
|
// 1 byte per pixel
|
|
// 2 bytes per column for termination
|
|
return 8 + 4 * patch->width() + 4 * numposts + numpixels + 2 * patch->width();
|
|
}
|
|
|
|
//
|
|
// R_ConvertPatch
|
|
//
|
|
// Converts a patch that uses post_t posts into a patch that uses tallpost_t.
|
|
//
|
|
void R_ConvertPatch(patch_t* newpatch, patch_t* rawpatch, const unsigned int lump)
|
|
{
|
|
if (!rawpatch || !newpatch)
|
|
return;
|
|
|
|
memcpy(newpatch, rawpatch, 8); // copy the patch header
|
|
|
|
uint32_t* rawpostofs = rawpatch->ofs();
|
|
uint32_t* newpostofs = newpatch->ofs();
|
|
|
|
uint32_t curofs = rawpatch->datastart(); // keep track of the column offset
|
|
|
|
for (int i = 0; i < rawpatch->width(); i++)
|
|
{
|
|
int newpost_top = -1;
|
|
int newpost_len = 0;
|
|
|
|
int abs_offset = 0;
|
|
|
|
newpostofs[i] = LELONG(curofs); // write the new offset for this column
|
|
post_t* rawpost = rawpatch->post(LELONG(rawpostofs[i]));
|
|
tallpost_t* newpost = newpatch->tallpost(curofs);
|
|
|
|
while (!rawpost->end())
|
|
{
|
|
// handle DeePsea tall patches where topdelta is treated as a relative
|
|
// offset instead of an absolute offset
|
|
abs_offset = rawpost->abs(abs_offset);
|
|
if (newpost_top == -1)
|
|
newpost_top = abs_offset;
|
|
|
|
// watch for column overruns
|
|
int length = rawpost->length;
|
|
if (abs_offset + length > rawpatch->height())
|
|
length = rawpatch->height() - abs_offset;
|
|
|
|
if (length < 0)
|
|
{
|
|
I_Error("{}: Patch {} appears to be corrupted.", __FUNCTION__,
|
|
W_LumpName(lump));
|
|
}
|
|
|
|
// copy the pixels in the post
|
|
memcpy(newpost->data() + newpost_len, rawpost->data(), length);
|
|
newpost_len += length;
|
|
|
|
// Should we finish the post?
|
|
if (rawpost->next()->end() ||
|
|
abs_offset + length != rawpost->next()->abs(abs_offset))
|
|
{
|
|
newpost->topdelta = newpost_top;
|
|
newpost->length = newpost_len;
|
|
|
|
curofs += newpost->size();
|
|
newpost = newpost->next();
|
|
|
|
newpost_top = -1;
|
|
newpost_len = 0;
|
|
}
|
|
|
|
rawpost = rawpost->next();
|
|
}
|
|
|
|
newpost->writeend();
|
|
curofs += 2;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
// Rewritten by Lee Killough for performance and to fix Medusa bug
|
|
//
|
|
|
|
void R_DrawColumnInCache(const tallpost_t *post, byte *cache,
|
|
int originy, int cacheheight, byte *marks)
|
|
{
|
|
while (!post->end())
|
|
{
|
|
int count = post->length;
|
|
int position = post->topdelta + originy;
|
|
|
|
if (position < 0)
|
|
{
|
|
count += position;
|
|
position = 0;
|
|
}
|
|
|
|
if (position + count > cacheheight)
|
|
count = cacheheight - position;
|
|
|
|
if (count > 0)
|
|
{
|
|
memcpy(cache + position, post->data(), count);
|
|
|
|
// killough 4/9/98: remember which cells in column have been drawn,
|
|
// so that column can later be converted into a series of posts, to
|
|
// fix the Medusa bug.
|
|
|
|
memset(marks + position, 0xFF, count);
|
|
}
|
|
|
|
post = post->next();
|
|
}
|
|
}
|
|
|
|
// Some vanilla textures use patch offsets that were ignored by the vanilla executable
|
|
// In Odamex, these cause issues and need to be set to zero manually
|
|
void R_VanillaTextureHacks(texture_t* tex)
|
|
{
|
|
if (tex->name == "SKY1" &&
|
|
tex->height == 128 &&
|
|
tex->patchcount == 1 &&
|
|
tex->patches[0].originy == -8)
|
|
{
|
|
tex->patches[0].originy = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// R_GenerateComposite
|
|
// Using the texture definition,
|
|
// the composite texture is created from the patches,
|
|
// and each column is cached.
|
|
//
|
|
// Rewritten by Lee Killough for performance and to fix Medusa bug
|
|
|
|
void R_GenerateComposite (int texnum)
|
|
{
|
|
byte *block = (byte *)Z_Malloc (texturecompositesize[texnum], PU_STATIC,
|
|
(void **) &texturecomposite[texnum]);
|
|
texturecomposite[texnum] = block;
|
|
texture_t *texture = textures[texnum];
|
|
|
|
R_VanillaTextureHacks(texture);
|
|
|
|
// Composite the columns together.
|
|
texpatch_t *texpatch = texture->patches;
|
|
|
|
// killough 4/9/98: marks to identify transparent regions in merged textures
|
|
auto marks = std::make_unique<byte[]>(texture->width * texture->height);
|
|
memset(marks.get(), 0, texture->width * texture->height);
|
|
|
|
for (int i = texture->patchcount; --i >=0; texpatch++)
|
|
{
|
|
patch_t *patch = W_CachePatch(texpatch->patch);
|
|
int x1 = texpatch->originx, x2 = x1 + patch->width();
|
|
const int *cofs = patch->columnofs-x1;
|
|
if (x1<0)
|
|
x1 = 0;
|
|
if (x2 > texture->width)
|
|
x2 = texture->width;
|
|
|
|
for (; x1 < x2 ; x1++)
|
|
{
|
|
// killough 1/25/98, 4/9/98: Fix medusa bug.
|
|
tallpost_t *srcpost = (tallpost_t*)((byte*)patch + LELONG(cofs[x1]));
|
|
tallpost_t *destpost = (tallpost_t*)(block + texturecolumnofs[texnum][x1]);
|
|
|
|
R_DrawColumnInCache(srcpost, destpost->data(), texpatch->originy, texture->height,
|
|
&marks[x1 * texture->height]);
|
|
}
|
|
}
|
|
|
|
// killough 4/9/98: Next, convert multipatched columns into true columns,
|
|
// to fix Medusa bug while still allowing for transparent regions.
|
|
|
|
auto tmpdata = std::make_unique<byte[]>(texture->height); // temporary post data
|
|
for (int i = 0; i < texture->width; i++)
|
|
{
|
|
tallpost_t *post = (tallpost_t *)(block + texturecolumnofs[texnum][i]);
|
|
const byte *mark = &marks[i * texture->height];
|
|
int j = 0;
|
|
|
|
// save column in temporary so we can shuffle it around
|
|
memcpy(tmpdata.get(), post->data(), texture->height);
|
|
|
|
// reconstruct the column by scanning transparency marks
|
|
while (true)
|
|
{
|
|
while (j < texture->height && !mark[j]) // skip transparent pixels
|
|
j++;
|
|
if (j >= texture->height) // if at end of column
|
|
{
|
|
post->writeend(); // end-of-column marker
|
|
break;
|
|
}
|
|
|
|
post->topdelta = j; // starting offset of post
|
|
|
|
// count opaque pixels
|
|
for (post->length = 0; j < texture->height && mark[j]; j++)
|
|
post->length++;
|
|
|
|
// copy opaque pixels from the temporary back into the column
|
|
memcpy(post->data(), tmpdata.get() + post->topdelta, post->length);
|
|
post = post->next();
|
|
}
|
|
}
|
|
|
|
// Now that the texture has been built in column cache,
|
|
// it is purgable from zone memory.
|
|
|
|
Z_ChangeTag(block, PU_CACHE);
|
|
}
|
|
|
|
//
|
|
// R_GenerateLookup
|
|
//
|
|
// Rewritten by Lee Killough for performance and to fix Medusa bug
|
|
//
|
|
|
|
void R_GenerateLookup(int texnum, int *const errors)
|
|
{
|
|
const texture_t *texture = textures[texnum];
|
|
|
|
// Composited texture not created yet.
|
|
|
|
// killough 4/9/98: keep count of posts in addition to patches.
|
|
// Part of fix for medusa bug for multipatched 2s normals.
|
|
auto postcount = std::make_unique<uint16_t[]>(texture->width);
|
|
|
|
memset(postcount.get(), 0, sizeof(uint16_t) * texture->width);
|
|
|
|
const texpatch_t *texpatch = texture->patches;
|
|
|
|
for (int i = 0; i < texture->patchcount; i++)
|
|
{
|
|
const int patchnum = texpatch->patch;
|
|
const patch_t *patch = W_CachePatch(patchnum);
|
|
int x1 = texpatch++->originx, x2 = x1 + patch->width(), x = x1;
|
|
const int *cofs = patch->columnofs-x1;
|
|
|
|
if (x2 > texture->width)
|
|
x2 = texture->width;
|
|
if (x1 < 0)
|
|
x = 0;
|
|
for (; x < x2; x++)
|
|
{
|
|
// killough 4/9/98: keep a count of the number of posts in column,
|
|
// to fix Medusa bug while allowing for transparent multipatches.
|
|
|
|
const tallpost_t *post = (tallpost_t*)((byte*)patch + LELONG(cofs[x]));
|
|
|
|
// NOTE: this offset will be rewritten later if a composite is generated
|
|
// for this texture (eg, there's more than one patch)
|
|
texturecolumnofs[texnum][x] = (byte *)post - (byte *)patch;
|
|
|
|
while (!post->end())
|
|
{
|
|
postcount[x]++;
|
|
post = post->next();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now count the number of columns that are covered by more than one patch.
|
|
// Fill in the lump / offset, so columns with only a single patch are all done.
|
|
|
|
texturecomposite[texnum] = 0;
|
|
int csize = 0;
|
|
|
|
int x = texture->width;
|
|
while (--x >= 0)
|
|
{
|
|
// killough 1/25/98, 4/9/98:
|
|
//
|
|
// Fix Medusa bug, by adding room for column header
|
|
// and trailer bytes for each post in merged column.
|
|
// For now, just allocate conservatively 4 bytes
|
|
// per post per patch per column, since we don't
|
|
// yet know how many posts the merged column will
|
|
// require, and it's bounded above by this limit.
|
|
|
|
texturecolumnofs[texnum][x] = csize;
|
|
|
|
// 4 header bytes per post + column height + 2 byte terminator
|
|
csize += 4 * postcount[x] + 2 + texture->height;
|
|
}
|
|
|
|
texturecompositesize[texnum] = csize;
|
|
}
|
|
|
|
//
|
|
// R_GetPatchColumn
|
|
//
|
|
tallpost_t* R_GetPatchColumn(int lumpnum, int colnum)
|
|
{
|
|
patch_t* patch = W_CachePatch(lumpnum, PU_CACHE);
|
|
return (tallpost_t*)((byte*)patch + LELONG(patch->columnofs[colnum]));
|
|
}
|
|
|
|
//
|
|
// R_GetPatchColumnData
|
|
//
|
|
byte* R_GetPatchColumnData(int lumpnum, int colnum)
|
|
{
|
|
return R_GetPatchColumn(lumpnum, colnum)->data();
|
|
}
|
|
|
|
//
|
|
// R_GetTextureColumn
|
|
//
|
|
tallpost_t* R_GetTextureColumn(int texnum, int colnum)
|
|
{
|
|
short width = textures[texnum]->width;
|
|
int mask = texturewidthmask[texnum];
|
|
if (mask + 1 == width)
|
|
colnum &= mask;
|
|
else
|
|
colnum -= width * std::floor((float)colnum / (float)width);
|
|
int ofs = texturecolumnofs[texnum][colnum];
|
|
|
|
if (!texturecomposite[texnum])
|
|
R_GenerateComposite(texnum);
|
|
|
|
return (tallpost_t*)(texturecomposite[texnum] + ofs);
|
|
}
|
|
|
|
//
|
|
// R_GetTextureColumnData
|
|
//
|
|
byte* R_GetTextureColumnData(int texnum, int colnum)
|
|
{
|
|
return R_GetTextureColumn(texnum, colnum)->data();
|
|
}
|
|
|
|
|
|
//
|
|
// R_InitTextures
|
|
// Initializes the texture list
|
|
// with the textures from the world map.
|
|
//
|
|
static inline void RegisterTexture(texture_t* texture, int i, byte scalex = 0, byte scaley = 0)
|
|
{
|
|
texturecolumnofs[i] = new unsigned int[texture->width];
|
|
|
|
int j;
|
|
for (j = 1; j*2 <= texture->width; j <<= 1)
|
|
;
|
|
texturewidthmask[i] = j-1;
|
|
|
|
textureheight[i] = texture->height << FRACBITS;
|
|
|
|
// [RH] Special for beta 29: Values of 0 will use the tx/ty cvars
|
|
// to determine scaling instead of defaulting to 8. I will likely
|
|
// remove this once I finish the betas, because by then, users
|
|
// should be able to actually create scaled textures.
|
|
texturescalex[i] = scalex ? scalex << (FRACBITS - 3) : FRACUNIT;
|
|
texturescaley[i] = scaley ? scaley << (FRACBITS - 3) : FRACUNIT;
|
|
}
|
|
|
|
struct texlump_t
|
|
{
|
|
int32_t lumpnum = -1;
|
|
int32_t* data = nullptr;
|
|
int32_t* directory = nullptr;
|
|
int numtextures = 0;
|
|
int maxoff = 0;
|
|
|
|
explicit texlump_t(const char* name) : lumpnum(W_CheckNumForName(name))
|
|
{
|
|
if (lumpnum != -1)
|
|
{
|
|
maxoff = W_LumpLength(lumpnum);
|
|
data = static_cast<int32_t*>(W_CacheLumpNum(lumpnum, PU_STATIC));
|
|
numtextures = LELONG(*data);
|
|
directory = data + 1;
|
|
}
|
|
}
|
|
|
|
~texlump_t()
|
|
{
|
|
if (data)
|
|
Z_Free(data);
|
|
}
|
|
};
|
|
|
|
static int32_t R_LoadTextureLump(const texlump_t& texlump, const nonstd::span<const int> patchlookup, int texnum, texhash_t& texhash)
|
|
{
|
|
int32_t* directory = texlump.directory;
|
|
int i;
|
|
for (i = texnum; i < texnum + texlump.numtextures; i++, directory++)
|
|
{
|
|
const int32_t offset = LELONG(*directory);
|
|
|
|
if (offset > texlump.maxoff)
|
|
I_FatalError("R_InitTextures: bad texture directory");
|
|
|
|
maptexture_t* mtexture = (maptexture_t *) ( (byte *)texlump.data + offset);
|
|
|
|
texture_t* texture = textures[i] = (texture_t *)
|
|
Z_Malloc (sizeof(texture_t)
|
|
+ sizeof(texpatch_t)*(SAFESHORT(mtexture->patchcount)-1),
|
|
PU_STATIC, nullptr);
|
|
|
|
texture->width = SAFESHORT(mtexture->width);
|
|
texture->height = SAFESHORT(mtexture->height);
|
|
texture->patchcount = SAFESHORT(mtexture->patchcount);
|
|
|
|
texture->name = mtexture->name;
|
|
|
|
const mappatch_t* mpatch = &mtexture->patches[0];
|
|
texpatch_t* patch = &texture->patches[0];
|
|
|
|
for (int j = 0; j < texture->patchcount ; j++, mpatch++, patch++)
|
|
{
|
|
patch->originx = LESHORT(mpatch->originx);
|
|
patch->originy = LESHORT(mpatch->originy);
|
|
const int16_t patchnum = LESHORT(mpatch->patch);
|
|
if (patchnum >= 0 && static_cast<size_t>(patchnum) < patchlookup.size())
|
|
patch->patch = patchlookup[patchnum];
|
|
else
|
|
patch->patch = -1;
|
|
if (patch->patch == -1)
|
|
{
|
|
patch->patch = W_CheckNumForName("TNT1A0", ns_sprites);
|
|
PrintFmt(PRINT_WARNING, "R_InitTextures: Missing patch in texture {}\n", texture->name);
|
|
// [EB] Make missing patches non-fatal
|
|
// other ports have annoyingly started doing this
|
|
// and so we do to if we want all the wads to work...
|
|
// errors++;
|
|
}
|
|
}
|
|
|
|
RegisterTexture(texture, i, mtexture->scalex, mtexture->scaley);
|
|
|
|
if (texhash.find(texture->name) == texhash.end())
|
|
texhash[texture->name] = i;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
void R_InitTextures()
|
|
{
|
|
std::vector<int> patchlookup;
|
|
|
|
int numpatches;
|
|
int tx_numtextures;
|
|
|
|
int errors = 0;
|
|
|
|
// for TX_START/TX_END
|
|
int first_tx;
|
|
|
|
// Load the patch names from pnames.lmp.
|
|
{
|
|
char *names = (char *)W_CacheLumpName ("PNAMES", PU_STATIC);
|
|
char *name_p = names+4;
|
|
|
|
numpatches = LELONG ( *((int *)names) );
|
|
|
|
// Put a guard here in case one of the pair is missing.
|
|
const int tx_startlump = W_CheckNumForName("TX_START");
|
|
const int tx_endlump = W_CheckNumForName("TX_END");
|
|
if (tx_startlump != -1 && tx_endlump != -1 && tx_endlump > tx_startlump)
|
|
{
|
|
first_tx = tx_startlump + 1;
|
|
tx_numtextures = tx_endlump - tx_startlump - 1;
|
|
}
|
|
else
|
|
{
|
|
first_tx = 0;
|
|
tx_numtextures = 0;
|
|
}
|
|
|
|
patchlookup.resize(numpatches);
|
|
|
|
for (int i = 0; i < numpatches; i++)
|
|
{
|
|
patchlookup[i] = W_CheckNumForName(name_p + i*8);
|
|
|
|
// [EB] some wads use the texture namespace but then still use those in pnames
|
|
if (patchlookup[i] == -1)
|
|
patchlookup[i] = W_CheckNumForName(name_p + i*8, ns_textures);
|
|
|
|
if (patchlookup[i] == -1)
|
|
{
|
|
// killough 4/17/98:
|
|
// Some wads use sprites as wall patches, so repeat check and
|
|
// look for sprites this time, but only if there were no wall
|
|
// patches found. This is the same as allowing for both, except
|
|
// that wall patches always win over sprites, even when they
|
|
// appear first in a wad. This is a kludgy solution to the wad
|
|
// lump namespace problem.
|
|
|
|
patchlookup[i] = W_CheckNumForName(name_p + i*8, ns_sprites);
|
|
}
|
|
}
|
|
Z_Free (names);
|
|
}
|
|
|
|
texturehash.clear();
|
|
const texlump_t texture1("TEXTURE1");
|
|
const texlump_t texture2("TEXTURE2");
|
|
|
|
// denis - fix memory leaks
|
|
for (int i = 0; i < numtextures; i++)
|
|
{
|
|
delete[] texturecolumnofs[i];
|
|
}
|
|
|
|
// denis - fix memory leaks
|
|
delete[] textures;
|
|
delete[] texturecolumnofs;
|
|
delete[] texturecomposite;
|
|
delete[] texturecompositesize;
|
|
delete[] texturewidthmask;
|
|
delete[] textureheight;
|
|
delete[] texturescalex;
|
|
delete[] texturescaley;
|
|
|
|
numtextures = texture1.numtextures + texture2.numtextures + tx_numtextures;
|
|
|
|
const int first_pname_tex = numtextures;
|
|
const int numpnamestextures = std::count_if(patchlookup.begin(), patchlookup.end(), [](const int patch){ return patch != -1; });
|
|
numtextures += numpnamestextures;
|
|
|
|
textures = new texture_t *[numtextures];
|
|
texturecolumnofs = new unsigned int *[numtextures];
|
|
texturecomposite = new byte *[numtextures];
|
|
texturecompositesize = new int[numtextures];
|
|
texturewidthmask = new int[numtextures];
|
|
textureheight = new fixed_t[numtextures];
|
|
texturescalex = new fixed_t[numtextures];
|
|
texturescaley = new fixed_t[numtextures];
|
|
|
|
texhash_t texturehash2;
|
|
// [EB] texture1 goes to texturehash2 because .insert only inserts for keys that don't already exist
|
|
// and we need texture2 to override texture1
|
|
int texnum = R_LoadTextureLump(texture1, patchlookup, 0, texturehash2);
|
|
texnum = R_LoadTextureLump(texture2, patchlookup, texnum, texturehash);
|
|
texturehash.insert(texturehash2.begin(), texturehash2.end());
|
|
|
|
const auto createTexture = [&](int textureIndex,
|
|
int patchLump,
|
|
bool overwriteHash)
|
|
{
|
|
const patch_t* patch = W_CachePatch(patchLump, PU_CACHE);
|
|
|
|
texture_t* texture =
|
|
textures[textureIndex] =
|
|
static_cast<texture_t*>(Z_Malloc(sizeof(texture_t), PU_STATIC, nullptr));
|
|
|
|
texture->name = lumpinfo[patchLump].name;
|
|
texture->width = patch->width();
|
|
texture->height = patch->height();
|
|
texture->patchcount = 1;
|
|
|
|
texture->patches->patch = patchLump;
|
|
texture->patches->originx = 0;
|
|
texture->patches->originy = 0;
|
|
|
|
RegisterTexture(texture, textureIndex);
|
|
|
|
if (overwriteHash)
|
|
texturehash[texture->name] = textureIndex;
|
|
else
|
|
texturehash.try_emplace(texture->name, textureIndex);
|
|
};
|
|
|
|
// TX_ marker (texture namespace) parsed here
|
|
for (int i = texnum, j = 0; j < tx_numtextures; i++, j++)
|
|
{
|
|
createTexture(i, first_tx + j, true);
|
|
}
|
|
|
|
for (int i = first_pname_tex, j = 0; j < numpatches; j++)
|
|
{
|
|
if (patchlookup[j] != -1)
|
|
{
|
|
createTexture(i, patchlookup[j], false);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (errors)
|
|
I_FatalError("{} errors in R_InitTextures.", errors);
|
|
|
|
if (clientside) // server doesn't need to load patches ever
|
|
{
|
|
// Precalculate whatever possible.
|
|
for (int i = 0; i < numtextures; i++)
|
|
R_GenerateLookup(i, &errors);
|
|
}
|
|
|
|
if (errors)
|
|
PrintFmt(PRINT_WARNING, "{} errors encountered during texture generation.", errors);
|
|
|
|
// Create translation table for global animation.
|
|
|
|
texturetranslation = std::make_unique<int[]>(numtextures+1);
|
|
|
|
for (int i = 0; i < numtextures; i++)
|
|
texturetranslation[i] = i;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// R_InitFlats
|
|
//
|
|
void R_InitFlats (void)
|
|
{
|
|
firstflat = W_GetNumForName ("F_START") + 1;
|
|
lastflat = W_GetNumForName ("F_END") - 1;
|
|
|
|
if(firstflat >= lastflat)
|
|
I_Error("no flats");
|
|
|
|
numflats = lastflat - firstflat + 1;
|
|
|
|
delete[] flattranslation;
|
|
|
|
// Create translation table for global animation.
|
|
flattranslation = new int[numflats+1];
|
|
|
|
for (int i = 0; i < numflats; i++)
|
|
flattranslation[i] = i;
|
|
|
|
delete[] flatwarp;
|
|
|
|
flatwarp = new bool[numflats+1];
|
|
memset (flatwarp, 0, sizeof(bool) * (numflats+1));
|
|
|
|
delete[] warpedflats;
|
|
|
|
warpedflats = new byte *[numflats+1];
|
|
memset (warpedflats, 0, sizeof(byte *) * (numflats+1));
|
|
|
|
delete[] flatwarpedwhen;
|
|
|
|
flatwarpedwhen = new int[numflats+1];
|
|
memset (flatwarpedwhen, 0xff, sizeof(int) * (numflats+1));
|
|
}
|
|
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
void R_InitSpriteLumps (void)
|
|
{
|
|
firstspritelump = W_GetNumForName ("S_START") + 1;
|
|
lastspritelump = W_GetNumForName ("S_END") - 1;
|
|
|
|
numspritelumps = lastspritelump - firstspritelump + 1;
|
|
|
|
if(firstspritelump > lastspritelump)
|
|
I_Error("no sprite lumps");
|
|
|
|
// [RH] Rather than maintaining separate spritewidth, spriteoffset,
|
|
// and spritetopoffset arrays, this data has now been moved into
|
|
// the sprite frame definition and gets initialized by
|
|
// R_InstallSpriteLump(), so there really isn't anything to do here.
|
|
}
|
|
|
|
|
|
struct FakeCmap
|
|
{
|
|
OLumpName name;
|
|
argb_t blend_color;
|
|
};
|
|
|
|
static FakeCmap* fakecmaps = NULL;
|
|
|
|
size_t numfakecmaps;
|
|
int firstfakecmap;
|
|
shademap_t realcolormaps;
|
|
|
|
|
|
void R_ForceDefaultColormap(const char* name)
|
|
{
|
|
const byte* data = (byte*)W_CacheLumpName(name, PU_CACHE);
|
|
memcpy(realcolormaps.colormap, data, (NUMCOLORMAPS+1)*256);
|
|
|
|
#if 0
|
|
// Setup shademap to mirror colormapped colors:
|
|
for (int m = 0; m < (NUMCOLORMAPS+1); ++m)
|
|
for (int c = 0; c < 256; ++c)
|
|
realcolormaps.shademap[m*256+c] = V_Palette.shade(realcolormaps.colormap[m*256+c]);
|
|
#else
|
|
BuildDefaultShademap(V_GetDefaultPalette(), realcolormaps);
|
|
#endif
|
|
|
|
fakecmaps[0].name = name;
|
|
fakecmaps[0].blend_color = argb_t(0, 255, 255, 255);
|
|
}
|
|
|
|
void R_SetDefaultColormap(const char* name)
|
|
{
|
|
if (fakecmaps[0].name == name)
|
|
R_ForceDefaultColormap(name);
|
|
}
|
|
|
|
void R_ReinitColormap()
|
|
{
|
|
if (fakecmaps == NULL)
|
|
return;
|
|
|
|
OLumpName name = fakecmaps[0].name;
|
|
if (name.empty())
|
|
name = "COLORMAP";
|
|
|
|
R_ForceDefaultColormap(name.c_str());
|
|
}
|
|
|
|
|
|
//
|
|
// R_ShutdownColormaps
|
|
//
|
|
// Frees the memory allocated specifically for the colormaps.
|
|
//
|
|
void R_ShutdownColormaps()
|
|
{
|
|
if (realcolormaps.colormap)
|
|
{
|
|
Z_Free(realcolormaps.colormap);
|
|
realcolormaps.colormap = NULL;
|
|
}
|
|
|
|
if (realcolormaps.shademap)
|
|
{
|
|
Z_Free(realcolormaps.shademap);
|
|
realcolormaps.shademap = NULL;
|
|
}
|
|
|
|
if (fakecmaps)
|
|
{
|
|
delete [] fakecmaps;
|
|
fakecmaps = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// R_InitColormaps
|
|
//
|
|
void R_InitColormaps()
|
|
{
|
|
// [RH] Try and convert BOOM colormaps into blending values.
|
|
// This is a really rough hack, but it's better than
|
|
// not doing anything with them at all (right?)
|
|
int lastfakecmap = W_CheckNumForName("C_END");
|
|
firstfakecmap = W_CheckNumForName("C_START");
|
|
|
|
if (firstfakecmap == -1 || lastfakecmap == -1)
|
|
numfakecmaps = 1;
|
|
else
|
|
{
|
|
if (firstfakecmap > lastfakecmap)
|
|
I_Error("no fake cmaps");
|
|
|
|
numfakecmaps = lastfakecmap - firstfakecmap;
|
|
}
|
|
|
|
realcolormaps.colormap = (byte*)Z_Malloc(256*(NUMCOLORMAPS+1)*numfakecmaps, PU_STATIC,0);
|
|
realcolormaps.shademap = (argb_t*)Z_Malloc(256*sizeof(argb_t)*(NUMCOLORMAPS+1)*numfakecmaps, PU_STATIC,0);
|
|
|
|
delete[] fakecmaps;
|
|
fakecmaps = new FakeCmap[numfakecmaps];
|
|
|
|
R_ForceDefaultColormap("COLORMAP");
|
|
|
|
if (numfakecmaps > 1)
|
|
{
|
|
const palette_t* pal = V_GetDefaultPalette();
|
|
|
|
for (unsigned i = ++firstfakecmap, j = 1; j < numfakecmaps; i++, j++)
|
|
{
|
|
if (W_LumpLength(i) >= (NUMCOLORMAPS+1)*256)
|
|
{
|
|
byte* map = (byte*)W_CacheLumpNum(i, PU_CACHE);
|
|
byte* colormap = realcolormaps.colormap+(NUMCOLORMAPS+1)*256*j;
|
|
argb_t* shademap = realcolormaps.shademap+(NUMCOLORMAPS+1)*256*j;
|
|
|
|
// Copy colormap data:
|
|
memcpy(colormap, map, (NUMCOLORMAPS+1)*256);
|
|
|
|
int r = pal->basecolors[*map].getr();
|
|
int g = pal->basecolors[*map].getg();
|
|
int b = pal->basecolors[*map].getb();
|
|
|
|
W_GetOLumpName(fakecmaps[j].name, i);
|
|
|
|
for (int k = 1; k < 256; k++)
|
|
{
|
|
r = (r + pal->basecolors[map[k]].getr()) >> 1;
|
|
g = (g + pal->basecolors[map[k]].getg()) >> 1;
|
|
b = (b + pal->basecolors[map[k]].getb()) >> 1;
|
|
}
|
|
// NOTE(jsd): This alpha value is used for 32bpp in water areas.
|
|
argb_t color = argb_t(64, r, g, b);
|
|
fakecmaps[j].blend_color = color;
|
|
|
|
// Set up shademap for the colormap:
|
|
for (int k = 0; k < 256; ++k)
|
|
shademap[k] = alphablend1a(pal->basecolors[map[0]], color, j * (256 / numfakecmaps));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// R_ColormapNumForname
|
|
//
|
|
// [RH] Returns an index into realcolormaps. Multiply it by
|
|
// 256*(NUMCOLORMAPS+1) to find the start of the colormap to use.
|
|
//
|
|
// COLORMAP always returns 0.
|
|
//
|
|
int R_ColormapNumForName(const char* name)
|
|
{
|
|
if (strnicmp(name, "COLORMAP", 8) != 0)
|
|
{
|
|
int lump = W_CheckNumForName(name, ns_colormaps);
|
|
|
|
if (lump != -1)
|
|
return lump - firstfakecmap + 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
// R_BlendForColormap
|
|
//
|
|
// Returns a blend value to approximate the given colormap index number.
|
|
// Invalid values return the color white with 0% opacity.
|
|
//
|
|
argb_t R_BlendForColormap(unsigned int index)
|
|
{
|
|
if (index > 0 && index < numfakecmaps)
|
|
return fakecmaps[index].blend_color;
|
|
|
|
return argb_t(0, 255, 255, 255);
|
|
}
|
|
|
|
|
|
//
|
|
// R_ColormapForBlend
|
|
//
|
|
// Returns the colormap index number that has the given blend color value.
|
|
//
|
|
int R_ColormapForBlend(const argb_t blend_color)
|
|
{
|
|
for (unsigned int i = 1; i < numfakecmaps; i++)
|
|
if (fakecmaps[i].blend_color == blend_color)
|
|
return i;
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// R_InitData
|
|
// Locates all the lumps
|
|
// that will be used by all views
|
|
// Must be called after W_Init.
|
|
//
|
|
void R_InitData()
|
|
{
|
|
R_InitTextures();
|
|
R_InitFlats();
|
|
R_InitSpriteLumps();
|
|
R_InitSkyDefs();
|
|
|
|
// haleyjd 01/28/10: also initialize tantoangle_acc table
|
|
Table_InitTanToAngle();
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// R_FlatNumForName
|
|
// Retrieval, get a flat number for a flat name.
|
|
//
|
|
int R_FlatNumForName (const char* name)
|
|
{
|
|
int i = W_CheckNumForName (name, ns_flats);
|
|
|
|
if (i == -1) // [RH] Default flat for not found ones
|
|
i = W_CheckNumForName ("-NOFLAT-", ns_flats);
|
|
|
|
if (i == -1) {
|
|
char namet[9];
|
|
|
|
strncpy (namet, name, 8);
|
|
namet[8] = 0;
|
|
|
|
I_Error("R_FlatNumForName: {} not found", namet);
|
|
}
|
|
|
|
return i - firstflat;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// R_CheckTextureNumForName
|
|
// Check whether texture is available.
|
|
// Filter out NoTexture indicator.
|
|
//
|
|
int R_CheckTextureNumForName (const OLumpName& name)
|
|
{
|
|
// "NoTexture" marker.
|
|
if (name[0] == '-')
|
|
return 0;
|
|
|
|
// [RH] Use a hash table instead of linear search
|
|
auto it = texturehash.find(name);
|
|
if (it == texturehash.end())
|
|
return -1;
|
|
|
|
return it->second;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// R_TextureNumForName
|
|
// Calls R_CheckTextureNumForName,
|
|
// aborts with error message.
|
|
//
|
|
int R_TextureNumForName (const OLumpName& name)
|
|
{
|
|
const int i = R_CheckTextureNumForName (name);
|
|
|
|
if (i == -1)
|
|
{
|
|
//I_Error ("R_TextureNumForName: %s not found", namet);
|
|
// [RH] Return empty texture if it wasn't found.
|
|
if (name == "")
|
|
PrintFmt(PRINT_WARNING, "Unnamed texture not found\n");
|
|
else
|
|
PrintFmt(PRINT_WARNING, "Texture {} not found\n", name);
|
|
return 0;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// R_PrecacheLevel
|
|
// Preloads all relevant graphics for the level.
|
|
//
|
|
// [RH] Rewrote this using Lee Killough's code in BOOM as an example.
|
|
|
|
void R_PrecacheLevel (void)
|
|
{
|
|
std::unique_ptr<byte[]> hitlist;
|
|
|
|
if (demoplayback)
|
|
return;
|
|
|
|
{
|
|
int size = (numflats > numsprites) ? numflats : numsprites;
|
|
|
|
hitlist = std::make_unique<byte[]>((numtextures > size) ? numtextures : size);
|
|
}
|
|
|
|
// Precache flats.
|
|
memset (hitlist.get(), 0, numflats);
|
|
|
|
for (int i = numsectors - 1; i >= 0; i--)
|
|
hitlist[sectors[i].floorpic] = hitlist[sectors[i].ceilingpic] = 1;
|
|
|
|
for (int i = numflats - 1; i >= 0; i--)
|
|
if (hitlist[i])
|
|
W_CacheLumpNum (firstflat + i, PU_CACHE);
|
|
|
|
std::vector<int> skytextures;
|
|
#ifdef CLIENT_APP
|
|
R_ActivateSkies(hitlist.get(), skytextures);
|
|
#endif
|
|
|
|
// Precache textures.
|
|
memset (hitlist.get(), 0, numtextures);
|
|
|
|
for (int i = numsides - 1; i >= 0; i--)
|
|
{
|
|
hitlist[sides[i].toptexture] =
|
|
hitlist[sides[i].midtexture] =
|
|
hitlist[sides[i].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 an episode dependend
|
|
// name.
|
|
//
|
|
// [RH] Possibly two sky textures now.
|
|
// [ML] 5/11/06 - Not anymore!
|
|
|
|
hitlist[sky2texture] = 1;
|
|
|
|
for (int skytexture : skytextures)
|
|
{
|
|
hitlist[skytexture] = 1;
|
|
}
|
|
|
|
for (int i = numtextures - 1; i >= 0; i--)
|
|
{
|
|
if (hitlist[i])
|
|
{
|
|
int j;
|
|
texture_t *texture = textures[i];
|
|
|
|
for (j = texture->patchcount - 1; j > 0; j--)
|
|
W_CachePatch(texture->patches[j].patch, PU_CACHE);
|
|
}
|
|
}
|
|
|
|
// Precache sprites.
|
|
{
|
|
AActor *actor;
|
|
TThinkerIterator<AActor> iterator;
|
|
std::unordered_set<int32_t> spriteHitlist;
|
|
|
|
// generate a unique list of all the sprites we hit in this level
|
|
while ( (actor = iterator.Next ()) )
|
|
{
|
|
// [CMB] spritenum_t can now be negative so a new structure is needed
|
|
// [CMB] sprites is a pointer in order by index
|
|
spriteHitlist.insert(actor->sprite);
|
|
}
|
|
|
|
// cache each of the sprites
|
|
for (auto sprite : spriteHitlist)
|
|
{
|
|
R_CacheSprite (&sprites[sprite]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Utility function,
|
|
// called by R_PointToAngle.
|
|
unsigned int SlopeDiv (unsigned int num, unsigned int den)
|
|
{
|
|
unsigned int ans;
|
|
|
|
if (den < 512)
|
|
return SLOPERANGE;
|
|
|
|
ans = (num << 3) / (den >> 8);
|
|
|
|
return ans <= SLOPERANGE ? ans : SLOPERANGE;
|
|
}
|
|
|
|
VERSION_CONTROL (r_data_cpp, "$Id$")
|