ericw-tools/light/imglib.cc

730 lines
31 KiB
C++

#include <light/imglib.hh>
#include <light/entities.hh>
#include <map>
#include <vector>
/*
============================================================================
PALETTE
============================================================================
*/
uint8_t thepalette[768] = // Quake palette
{
0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171,171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63,47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27,27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,
139,107,107,151,115,115,163,123,123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71,7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79,0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,
55,0,75,59,7,87,67,7,95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19,87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255,243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123,95,183,135,107,195,147,123,211,163,139,227,179,151,
171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119,83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107,143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35,19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83,107,87,71,95,75,59,83,63,
51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107,95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255,243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55,0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,
43,175,47,47,159,47,47,143,47,47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23,7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123,59,183,155,55,199,195,55,231,227,87,127,191,255,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255,243,147,255,247,199,255,255,255,159,91,83
};
uint8_t hexen2palette[768] = //mxd
{
0,0,0,0,0,0,8,8,8,16,16,16,24,24,24,32,32,32,40,40,40,48,48,48,56,56,56,64,64,64,72,72,72,80,80,80,84,84,84,88,88,88,96,96,96,104,104,104,112,112,112,120,120,120,128,128,128,136,136,136,148,148,148,156,156,156,168,168,168,180,180,180,184,184,184,196,196,196,204,204,204,212,212,212,224,224,224,232,232,232,240,240,240,252,252,252,8,8,12,16,16,20,24,24,28,28,32,36,36,36,44,44,44,52,48,52,60,56,56,68,64,64,72,76,
76,88,92,92,104,108,112,128,128,132,152,152,156,176,168,172,196,188,196,220,32,24,20,40,32,28,48,36,32,52,44,40,60,52,44,68,56,52,76,64,56,84,72,64,92,76,72,100,84,76,108,92,84,112,96,88,120,104,96,128,112,100,136,116,108,144,124,112,20,24,20,28,32,28,32,36,32,40,44,40,44,48,44,48,56,48,56,64,56,64,68,64,68,76,68,84,92,84,104,112,104,120,128,120,140,148,136,156,164,152,172,180,168,188,196,184,48,32,8,60,40,8,
72,48,16,84,56,20,92,64,28,100,72,36,108,80,44,120,92,52,136,104,60,148,116,72,160,128,84,168,136,92,180,144,100,188,152,108,196,160,116,204,168,124,16,20,16,20,28,20,24,32,24,28,36,28,32,44,32,36,48,36,40,56,40,44,60,44,48,68,48,52,76,52,60,84,60,68,92,64,76,100,72,84,108,76,92,116,84,100,128,92,24,12,8,32,16,8,40,20,8,52,24,12,60,28,12,68,32,12,76,36,16,84,44,20,92,48,24,100,56,28,112,64,32,120,72,36,128,80,
44,144,92,56,168,112,72,192,132,88,24,4,4,36,4,4,48,0,0,60,0,0,68,0,0,80,0,0,88,0,0,100,0,0,112,0,0,132,0,0,152,0,0,172,0,0,192,0,0,212,0,0,232,0,0,252,0,0,16,12,32,28,20,48,32,28,56,40,36,68,52,44,80,60,56,92,68,64,104,80,72,116,88,84,128,100,96,140,108,108,152,120,116,164,132,132,176,144,144,188,156,156,200,172,172,212,36,20,4,52,24,4,68,32,4,80,40,0,100,48,4,124,60,4,140,72,4,156,88,8,172,100,8,188,116,12,
204,128,12,220,144,16,236,160,20,252,184,56,248,200,80,248,220,120,20,16,4,28,24,8,36,32,8,44,40,12,52,48,16,56,56,16,64,64,20,68,72,24,72,80,28,80,92,32,84,104,40,88,116,44,92,128,52,92,140,52,92,148,56,96,160,64,60,16,16,72,24,24,84,28,28,100,36,36,112,44,44,124,52,48,140,64,56,152,76,64,44,20,8,56,28,12,72,32,16,84,40,20,96,44,28,112,52,32,124,56,40,140,64,48,24,20,16,36,28,20,44,36,28,56,44,32,64,52,36,72,
60,44,80,68,48,92,76,52,100,84,60,112,92,68,120,100,72,132,112,80,144,120,88,152,128,96,160,136,104,168,148,112,36,24,12,44,32,16,52,40,20,60,44,20,72,52,24,80,60,28,88,68,28,104,76,32,148,96,56,160,108,64,172,116,72,180,124,80,192,132,88,204,140,92,216,156,108,60,20,92,100,36,116,168,72,164,204,108,192,4,84,4,4,132,4,0,180,0,0,216,0,4,4,144,16,68,204,36,132,224,88,168,232,216,4,4,244,72,0,252,128,0,252,172,24,252,252,252
};
uint8_t quake2palette[768] = //mxd
{
0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171,171,171,187,187,187,203,203,203,219,219,219,235,235,235,99,75,35,91,67,31,83,63,31,79,59,27,71,55,27,63,47,23,59,43,23,51,39,19,47,35,19,43,31,19,39,27,15,35,23,15,27,19,11,23,15,11,19,15,7,15,11,7,95,95,111,91,91,103,91,83,95,87,79,91,83,75,83,79,71,75,71,63,67,63,59,59,59,55,55,51,47,47,47,43,43,
39,39,39,35,35,35,27,27,27,23,23,23,19,19,19,143,119,83,123,99,67,115,91,59,103,79,47,207,151,75,167,123,59,139,103,47,111,83,39,235,159,39,203,139,35,175,119,31,147,99,27,119,79,23,91,59,15,63,39,11,35,23,7,167,59,43,159,47,35,151,43,27,139,39,19,127,31,15,115,23,11,103,23,7,87,19,0,75,15,0,67,15,0,59,15,0,51,11,0,43,11,0,35,11,0,27,7,0,19,7,0,123,95,75,115,87,67,107,83,63,103,79,59,95,71,55,87,67,51,83,63,
47,75,55,43,67,51,39,63,47,35,55,39,27,47,35,23,39,27,19,31,23,15,23,15,11,15,11,7,111,59,23,95,55,23,83,47,23,67,43,23,55,35,19,39,27,15,27,19,11,15,11,7,179,91,79,191,123,111,203,155,147,215,187,183,203,215,223,179,199,211,159,183,195,135,167,183,115,151,167,91,135,155,71,119,139,47,103,127,23,83,111,19,75,103,15,67,91,11,63,83,7,55,75,7,47,63,7,39,51,0,31,43,0,23,31,0,15,19,0,7,11,0,0,0,139,87,87,131,79,79,
123,71,71,115,67,67,107,59,59,99,51,51,91,47,47,87,43,43,75,35,35,63,31,31,51,27,27,43,19,19,31,15,15,19,11,11,11,7,7,0,0,0,151,159,123,143,151,115,135,139,107,127,131,99,119,123,95,115,115,87,107,107,79,99,99,71,91,91,67,79,79,59,67,67,51,55,55,43,47,47,35,35,35,27,23,23,19,15,15,11,159,75,63,147,67,55,139,59,47,127,55,39,119,47,35,107,43,27,99,35,23,87,31,19,79,27,15,67,23,11,55,19,11,43,15,7,31,11,7,23,7,0,
11,0,0,0,0,0,119,123,207,111,115,195,103,107,183,99,99,167,91,91,155,83,87,143,75,79,127,71,71,115,63,63,103,55,55,87,47,47,75,39,39,63,35,31,47,27,23,35,19,15,23,11,7,7,155,171,123,143,159,111,135,151,99,123,139,87,115,131,75,103,119,67,95,111,59,87,103,51,75,91,39,63,79,27,55,67,19,47,59,11,35,47,7,27,35,0,19,23,0,11,15,0,0,255,0,35,231,15,63,211,27,83,187,39,95,167,47,95,143,51,95,123,51,255,255,255,255,255,
211,255,255,167,255,255,127,255,255,83,255,255,39,255,235,31,255,215,23,255,191,15,255,171,7,255,147,0,239,127,0,227,107,0,211,87,0,199,71,0,183,59,0,171,43,0,155,31,0,143,23,0,127,15,0,115,7,0,95,0,0,71,0,0,47,0,0,27,0,0,239,0,0,55,55,255,255,0,0,0,0,255,43,43,35,27,27,23,19,19,15,235,151,127,195,115,83,159,87,51,123,63,27,235,211,199,199,171,155,167,139,119,135,107,87,159,91,83
};
void // WHO TOUCHED MY PALET?
LoadPalette(bspdata_t *bspdata)
{
// Load Quake 2 palette
if (bspdata->loadversion->game->id == GAME_QUAKE_II) {
uint8_t *palette;
char path[1024];
char colormap[] = "pics/colormap.pcx";
sprintf(path, "%s%s", gamedir, colormap);
if (FileTime(path) == -1 || !LoadPCX(path, nullptr, &palette, nullptr, nullptr)) {
if (Q_strcasecmp(gamedir, basedir)) {
sprintf(path, "%s%s", basedir, colormap);
if (FileTime(path) == -1 || !LoadPCX(path, nullptr, &palette, nullptr, nullptr)) {
logprint("WARNING: failed to load palette from '%s%s' or '%s%s'.\nUsing built-in palette.\n", gamedir, colormap, basedir, colormap);
palette = quake2palette;
}
} else {
logprint("WARNING: failed to load palette from '%s%s'.\nUsing built-in palette.\n", gamedir, colormap);
palette = quake2palette;
}
}
for (int i = 0; i < 768; i++)
thepalette[i] = palette[i];
} else if (bspdata->loadversion->game->id == GAME_HEXEN_II) {
// Copy Hexen 2 palette
for (int i = 0; i < 768; i++)
thepalette[i] = hexen2palette[i];
}
}
qvec3f Palette_GetColor(const int i)
{
return qvec3f((float)thepalette[3 * i],
(float)thepalette[3 * i + 1],
(float)thepalette[3 * i + 2]);
}
qvec4f Texture_GetColor(const rgba_miptex_t *tex, const int i)
{
const uint8_t *data = (uint8_t*)tex + tex->offset;
return qvec4f{ (float)data[i * 4],
(float)data[i * 4 + 1],
(float)data[i * 4 + 2],
(float)data[i * 4 + 3] };
}
/*
============================================================================
PCX IMAGE
============================================================================
*/
//mxd. Copied from https://github.com/qbism/q2tools-220/blob/f8f582fc02196955584542c95de8a45a138c9e42/common/lbmlib.c#L383
typedef struct
{
char manufacturer;
char version;
char encoding;
char bits_per_pixel;
unsigned short xmin, ymin, xmax, ymax;
unsigned short hres, vres;
unsigned char palette[48];
char reserved;
char color_planes;
unsigned short bytes_per_line;
unsigned short palette_type;
char filler[58];
unsigned char data; // unbounded
} pcx_t;
/*
==============
LoadPCX
==============
*/
qboolean
LoadPCX(const char *filename, uint8_t **pixels, uint8_t **palette, int *width, int *height)
{
uint8_t *raw;
int runLength;
// Load the file
if (FileTime(filename) == -1) {
logprint("LoadPCX: Failed to load '%s'. File does not exist.\n", filename);
return false; //mxd. Because LoadFile will throw Error if the file doesn't exist...
}
const int len = LoadFile(filename, reinterpret_cast<void **>(&raw));
if (len < 1) {
logprint("LoadPCX: Failed to load '%s'. File is empty.\n", filename);
return false;
}
// Parse the PCX file
pcx_t *pcx = reinterpret_cast<pcx_t *>(raw);
raw = &pcx->data;
pcx->xmin = LittleShort(pcx->xmin);
pcx->ymin = LittleShort(pcx->ymin);
pcx->xmax = LittleShort(pcx->xmax);
pcx->ymax = LittleShort(pcx->ymax);
pcx->hres = LittleShort(pcx->hres);
pcx->vres = LittleShort(pcx->vres);
pcx->bytes_per_line = LittleShort(pcx->bytes_per_line);
pcx->palette_type = LittleShort(pcx->palette_type);
if (pcx->manufacturer != 0x0a
|| pcx->version != 5
|| pcx->encoding != 1
|| pcx->bits_per_pixel != 8
|| pcx->xmax >= 640
|| pcx->ymax >= 480) {
logprint("LoadPCX: Failed to load '%s'. Unsupported PCX file.\n", filename);
return false; //mxd
}
if (palette) {
*palette = static_cast<uint8_t*>(malloc(768));
memcpy(*palette, reinterpret_cast<uint8_t *>(pcx) + len - 768, 768);
}
if (width)
*width = pcx->xmax + 1;
if (height)
*height = pcx->ymax + 1;
if (!pixels)
return true; // No target array specified, so skip reading pixels
const int numbytes = (pcx->ymax + 1) * (pcx->xmax + 1); //mxd
uint8_t *out = static_cast<uint8_t*>(malloc(numbytes));
if (!out) {
logprint("LoadPCX: Failed to load '%s'. Couldn't allocate %i bytes of memory.\n", filename, numbytes);
return false; //mxd
}
*pixels = out;
uint8_t *pix = out;
for (int y = 0; y <= pcx->ymax; y++, pix += pcx->xmax + 1) {
for (int x = 0; x <= pcx->xmax; ) {
int dataByte = *raw++;
if ((dataByte & 0xC0) == 0xC0) {
runLength = dataByte & 0x3F;
dataByte = *raw++;
} else {
runLength = 1;
}
while (runLength-- > 0)
pix[x++] = dataByte;
}
}
if (raw - reinterpret_cast<uint8_t *>(pcx) > len) {
logprint("LoadPCX: File '%s' was malformed.\n", filename);
return false; //mxd
}
free(pcx);
return true;
}
/*
============================================================================
TARGA IMAGE
============================================================================
*/
//mxd. Copied from https://github.com/qbism/q2tools-220/blob/f8f582fc02196955584542c95de8a45a138c9e42/common/lbmlib.c#L625
typedef struct _TargaHeader {
unsigned char id_length, colormap_type, image_type;
unsigned short colormap_index, colormap_length;
unsigned char colormap_size;
unsigned short x_origin, y_origin, width, height;
unsigned char pixel_size, attributes;
} TargaHeader;
int
fgetLittleShort(FILE *f)
{
const uint8_t b1 = fgetc(f);
const uint8_t b2 = fgetc(f);
return static_cast<short>(b1 + b2 * 256);
}
int
fgetLittleLong(FILE *f)
{
const uint8_t b1 = fgetc(f);
const uint8_t b2 = fgetc(f);
const uint8_t b3 = fgetc(f);
const uint8_t b4 = fgetc(f);
return b1 + (b2 << 8) + (b3 << 16) + (b4 << 24);
}
/*
=============
LoadTGA
=============
*/
qboolean
LoadTGA(const char *filename, uint8_t **pixels, int *width, int *height)
{
uint8_t *pixbuf;
int row, column;
TargaHeader targa_header;
FILE *fin = fopen(filename, "rb");
if (!fin) {
logprint("LoadTGA: Failed to load '%s'. File does not exist.\n", filename);
return false; //mxd
}
targa_header.id_length = fgetc(fin);
targa_header.colormap_type = fgetc(fin);
targa_header.image_type = fgetc(fin);
targa_header.colormap_index = fgetLittleShort(fin);
targa_header.colormap_length = fgetLittleShort(fin);
targa_header.colormap_size = fgetc(fin);
targa_header.x_origin = fgetLittleShort(fin);
targa_header.y_origin = fgetLittleShort(fin);
targa_header.width = fgetLittleShort(fin);
targa_header.height = fgetLittleShort(fin);
targa_header.pixel_size = fgetc(fin);
targa_header.attributes = fgetc(fin);
if (targa_header.image_type != 2 && targa_header.image_type != 10) {
logprint("LoadTGA: Failed to load '%s'. Only type 2 and 10 targa RGB images supported.\n", filename);
return false; //mxd
}
if (targa_header.colormap_type != 0 || (targa_header.pixel_size != 32 && targa_header.pixel_size != 24)) {
logprint("LoadTGA: Failed to load '%s'. Only 32 or 24 bit images supported (no colormaps).\n", filename);
return false; //mxd
}
const int columns = targa_header.width;
const int rows = targa_header.height;
const int numPixels = columns * rows;
if (width)
*width = columns;
if (height)
*height = rows;
uint8_t *targa_rgba = static_cast<uint8_t*>(malloc(numPixels * 4));
*pixels = targa_rgba;
if (targa_header.id_length != 0)
fseek(fin, targa_header.id_length, SEEK_CUR); // skip TARGA image comment
if (targa_header.image_type == 2) { // Uncompressed, RGB images
for (row = rows - 1; row >= 0; row--) {
pixbuf = targa_rgba + row * columns * 4;
for (column = 0; column < columns; column++) {
unsigned char red, green, blue, alphabyte;
switch (targa_header.pixel_size) {
case 24:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 32:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
alphabyte = getc(fin);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
break;
default:
logprint("LoadTGA: unsupported pixel size: %i\n", targa_header.pixel_size); //mxd
return false;
}
}
}
} else if (targa_header.image_type == 10) { // Runlength encoded RGB images
unsigned char red, green, blue, alphabyte, j;
for (row = rows - 1; row >= 0; row--) {
pixbuf = targa_rgba + row * columns * 4;
for (column = 0; column<columns; ) {
const unsigned char packetHeader = getc(fin);
const unsigned char packetSize = 1 + (packetHeader & 0x7f);
if (packetHeader & 0x80) { // run-length packet
switch (targa_header.pixel_size) {
case 24:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
alphabyte = 255;
break;
case 32:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
alphabyte = getc(fin);
break;
default:
logprint("LoadTGA: unsupported pixel size: %i\n", targa_header.pixel_size); //mxd
return false;
}
for (j = 0; j<packetSize; j++) {
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
column++;
if (column == columns) { // run spans across rows
column = 0;
if (row>0)
row--;
else
goto breakOut;
pixbuf = targa_rgba + row * columns * 4;
}
}
} else { // non run-length packet
for (j = 0; j<packetSize; j++) {
switch (targa_header.pixel_size) {
case 24:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 32:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
alphabyte = getc(fin);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
break;
default:
logprint("LoadTGA: unsupported pixel size: %i\n", targa_header.pixel_size); //mxd
return false;
}
column++;
if (column == columns) { // pixel packet run spans across rows
column = 0;
if (row>0)
row--;
else
goto breakOut;
pixbuf = targa_rgba + row * columns * 4;
}
}
}
}
breakOut:;
}
}
fclose(fin);
return true; //mxd
}
/*
============================================================================
WAL IMAGE
============================================================================
*/
qboolean LoadWAL(const char *filename, uint8_t **pixels, int *width, int *height)
{
if (FileTime(filename) == -1) {
logprint("LoadWAL: Failed to load '%s'. File does not exist.\n", filename);
return false; // Because LoadFile will throw an Error if the file doesn't exist...
}
q2_miptex_t *mt;
const int len = LoadFile(filename, static_cast<void *>(&mt));
if (len < 1) {
logprint("LoadWAL: Failed to load '%s'. File is empty.\n", filename);
return false;
}
const int w = LittleLong(mt->width);
const int h = LittleLong(mt->height);
const int offset = LittleLong(mt->offsets[0]);
const int numbytes = w * h;
const int numpixels = numbytes * 4; // RGBA
if (width) *width = w;
if (height) *height = h;
uint8_t *out = static_cast<uint8_t*>(malloc(numpixels));
if (!out) {
logprint("LoadWAL: Failed to load '%s'. Couldn't allocate %i bytes of memory.\n", filename, numpixels);
return false;
}
*pixels = out;
for (int i = 0; i < numbytes; i++) {
const int palindex = reinterpret_cast<uint8_t*>(mt)[offset + i];
out[i * 4] = thepalette[palindex * 3];
out[i * 4 + 1] = thepalette[palindex * 3 + 1];
out[i * 4 + 2] = thepalette[palindex * 3 + 2];
out[i * 4 + 3] = (palindex == 255 ? 0 : 255); // Last palette index is transparent color
}
free(mt);
return true;
}
/*
==============================================================================
Load (Quake 2) / Convert (Quake, Hexen 2) textures from paletted to RGBA (mxd)
==============================================================================
*/
static void
WriteRGBATextureData(mbsp_t *bsp, const std::vector<rgba_miptex_t*> &tex_mips, const std::vector<uint8_t*> &tex_bytes)
{
// Step 1: create header and write it...
const int headersize = 4 + tex_mips.size() * 4;
dmiptexlump_t *miplmp = static_cast<dmiptexlump_t*>(malloc(headersize));
miplmp->nummiptex = tex_mips.size();
// Write data offsets to the header...
int totalsize = headersize; // total size of miptex_t + palette bytes
const int miptexsize = sizeof(rgba_miptex_t);
for (unsigned int i = 0; i < tex_mips.size(); i++) {
if (tex_mips[i] == nullptr) {
miplmp->dataofs[i] = -1;
} else {
miplmp->dataofs[i] = totalsize;
totalsize += miptexsize + (tex_mips[i]->width * tex_mips[i]->height) * 4; // RGBA
}
}
// Step 2: write rgba_miptex_t and palette bytes to uint8_t array
uint8_t *texdata, *texdatastart;
texdata = texdatastart = static_cast<uint8_t*>(malloc(totalsize));
memcpy(texdata, miplmp, headersize);
texdata += headersize;
for (unsigned int i = 0; i < tex_mips.size(); i++) {
if (tex_mips[i] == nullptr)
continue;
// Write rgba_miptex_t
memcpy(texdata, tex_mips[i], miptexsize);
texdata += miptexsize;
// Write RGBA pixels
const int numpixels = (tex_mips[i]->width * tex_mips[i]->height) * 4; // RGBA
memcpy(texdata, tex_bytes[i], numpixels);
texdata += numpixels;
}
// Store in bsp->drgbatexdata...
bsp->drgbatexdata = reinterpret_cast<dmiptexlump_t*>(texdatastart);
bsp->rgbatexdatasize = totalsize;
}
static void AddTextureName(std::map<std::string, std::string> &texturenames, const char *texture)
{
// See if an earlier texinfo allready got the value
if (texturenames.find(texture) != texturenames.end())
return;
char path[4][1024];
static const qboolean is_mod = Q_strcasecmp(gamedir, basedir);
sprintf(path[0], "%stextures/%s.tga", gamedir, texture); // TGA, in mod dir...
sprintf(path[1], "%stextures/%s.tga", basedir, texture); // TGA, in game dir...
sprintf(path[2], "%stextures/%s.wal", gamedir, texture); // WAL, in mod dir...
sprintf(path[3], "%stextures/%s.wal", basedir, texture); // WAL, in game dir...
int c;
for (c = 0; c < 4; c++) {
// Skip paths at even indexes when running from game folder...
if ((is_mod || c % 2 == 0) && FileTime(path[c]) != -1) {
texturenames[std::string{ texture }] = std::string{ path[c] };
break;
}
}
if (c == 4) {
if (is_mod)
logprint("WARNING: failed to find texture '%s'. Checked paths:\n'%s'\n'%s'\n'%s'\n'%s'\n", texture, path[0], path[1], path[2], path[3]);
else
logprint("WARNING: failed to find texture '%s'. Checked paths:\n'%s'\n'%s'\n", texture, path[0], path[2]);
// Store to preserve offset...
texturenames[std::string{ texture }] = std::string{};
}
}
static void // Loads textures from disk and stores them in bsp->drgbatexdata (Quake 2)
LoadTextures(mbsp_t *bsp)
{
logprint("--- LoadTextures ---\n");
if (bsp->texdatasize) {
Error("ERROR: Expected an empty dtexdata lump...\n");
return;
}
// Step 1: gather all loadable textures...
std::map<std::string, std::string> texturenames; // <texture name, texture file path>
for (int i = 0; i < bsp->numtexinfo; i++)
AddTextureName(texturenames, bsp->texinfo[i].texture);
// Step 2: gather textures used by _project_texture. Yes, this means parsing dentdata twice...
auto entdicts = EntData_Parse(bsp->dentdata);
for (auto &entdict : entdicts) {
if (EntDict_StringForKey(entdict, "classname").find("light") == 0) {
auto tex = EntDict_StringForKey(entdict, "_project_texture");
if (!tex.empty()) AddTextureName(texturenames, tex.c_str());
}
}
// Step 3: load and convert to miptex_t, store texturename indices...
std::map<std::string, int> indicesbytexturename;
std::vector<rgba_miptex_t*> tex_mips{};
std::vector<uint8_t*> tex_bytes{};
const int miptexsize = sizeof(rgba_miptex_t);
int counter = 0;
for (auto pair : texturenames) {
// Store texturename index...
indicesbytexturename[std::string{ pair.first }] = counter++;
// Add nullptrs to keep texture index in case of load problems...
tex_mips.push_back(nullptr);
tex_bytes.push_back(nullptr);
// Find file extension
const int dpos = pair.second.rfind('.');
if (dpos == -1) {
if (!pair.second.empty()) // Missing texture warning was already displayed
logprint("WARNING: unexpected texture filename: '%s'\n", pair.second.c_str());
continue;
}
const std::string ext = pair.second.substr(dpos + 1);
// Load images as RGBA
int width, height;
uint8_t *pixels;
if (string_iequals(ext, "tga")) {
if (!LoadTGA(pair.second.c_str(), &pixels, &width, &height))
continue;
} else if (string_iequals(ext, "wal")) {
if (!LoadWAL(pair.second.c_str(), &pixels, &width, &height))
continue;
} else {
logprint("WARNING: unsupported image format: '%s'\n", pair.second.c_str());
continue;
}
// Create rgba_miptex_t...
rgba_miptex_t *tex = static_cast<rgba_miptex_t*>(malloc(miptexsize));
strcpy(tex->name, pair.first.c_str());
tex->width = width;
tex->height = height;
tex->offset = miptexsize;
// Replace nullptrs with actual data...
tex_mips[tex_mips.size() - 1] = tex;
tex_bytes[tex_bytes.size() - 1] = pixels;
}
// Sanity checks...
Q_assert(tex_mips.size() == tex_bytes.size());
Q_assert(texturenames.size() == tex_bytes.size());
Q_assert(texturenames.size() == indicesbytexturename.size());
// Step 4: write data to bsp...
WriteRGBATextureData(bsp, tex_mips, tex_bytes);
// Step 5: set miptex indices to gtexinfo_t
for (int i = 0; i < bsp->numtexinfo; i++) {
gtexinfo_t *info = &bsp->texinfo[i];
const auto pair = indicesbytexturename.find(info->texture);
if(pair != indicesbytexturename.end())
info->miptex = pair->second;
}
}
static void // Converts paletted bsp->dtexdata textures to RGBA bsp->drgbatexdata textures (Quake / Hexen2)
ConvertTextures(mbsp_t *bsp)
{
if (!bsp->texdatasize) return;
logprint("--- ConvertTextures ---\n");
std::map<int, std::string> texturenamesbyindex;
std::vector<rgba_miptex_t*> tex_mips{};
std::vector<uint8_t*> tex_bytes{};
const int miptexsize = sizeof(rgba_miptex_t);
// Step 1: store texture data and RGBA bytes in temporary arrays...
for (int i = 0; i < bsp->dtexdata->nummiptex; i++) {
const int ofs = bsp->dtexdata->dataofs[i];
// Pad to keep offsets...
if (ofs < 0) {
tex_mips.push_back(nullptr);
tex_bytes.push_back(nullptr);
continue;
}
miptex_t *miptex = (miptex_t *)((uint8_t *)bsp->dtexdata + ofs);
// Create rgba_miptex_t...
rgba_miptex_t *tex = static_cast<rgba_miptex_t*>(malloc(miptexsize));
strcpy(tex->name, miptex->name);
tex->width = miptex->width;
tex->height = miptex->height;
tex->offset = miptexsize;
// Store texturename index...
texturenamesbyindex[i] = std::string{ tex->name };
// Convert to RGBA
const int numpalpixels = tex->width * tex->height;
uint8_t *pixels = static_cast<uint8_t*>(malloc(numpalpixels * 4)); //RGBA
const uint8_t *data = reinterpret_cast<uint8_t*>(miptex) + miptex->offsets[0];
for (int c = 0; c < numpalpixels; c++) {
const uint8_t palindex = data[c];
auto color = Palette_GetColor(palindex);
for (int d = 0; d < 3; d++)
pixels[c * 4 + d] = static_cast<uint8_t>(color[d]);
pixels[c * 4 + 3] = static_cast<uint8_t>(palindex == 255 ? 0 : 255);
}
// Store...
tex_mips.push_back(tex);
tex_bytes.push_back(pixels);
}
// Sanity checks...
Q_assert(tex_mips.size() == tex_bytes.size());
Q_assert(bsp->dtexdata->nummiptex == tex_mips.size());
// Step 2: write data to bsp...
WriteRGBATextureData(bsp, tex_mips, tex_bytes);
// Step 3: set texturenames to gmiptex_t
for (int i = 0; i < bsp->numtexinfo; i++) {
gtexinfo_t *info = &bsp->texinfo[i];
const auto pair = texturenamesbyindex.find(info->miptex);
if(pair != texturenamesbyindex.end())
strcpy(info->texture, pair->second.c_str());
}
}
void // Expects correct palette and game/mod paths to be set
LoadOrConvertTextures(mbsp_t *bsp)
{
// Load or convert textures...
if (bsp->loadversion->game->id == GAME_QUAKE_II)
LoadTextures(bsp);
else if (bsp->texdatasize > 0)
ConvertTextures(bsp);
else
logprint("WARNING: failed to load or convert textures.\n");
}