713 lines
31 KiB
C++
713 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};
|
|
|
|
const std::filesystem::path colormap = std::filesystem::path("pics") / "colormap.pcx";
|
|
|
|
void // WHO TOUCHED MY PALET?
|
|
LoadPalette(bspdata_t *bspdata)
|
|
{
|
|
// Load Quake 2 palette
|
|
if (bspdata->loadversion->game->id == GAME_QUAKE_II) {
|
|
uint8_t *palette;
|
|
std::filesystem::path path = gamedir / colormap;
|
|
|
|
if (!std::filesystem::exists(path) || !LoadPCX(path, nullptr, &palette, nullptr, nullptr)) {
|
|
if (gamedir != basedir) {
|
|
path = basedir / colormap;
|
|
if (!std::filesystem::exists(path) || !LoadPCX(path, nullptr, &palette, nullptr, nullptr)) {
|
|
LogPrint("INFO: failed to load palette from '{}' or '{}'.\nUsing built-in palette.\n",
|
|
gamedir / colormap, path);
|
|
palette = quake2palette;
|
|
}
|
|
} else {
|
|
LogPrint("INFO: failed to load palette from '{}'.\nUsing built-in palette.\n", path);
|
|
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 size_t i)
|
|
{
|
|
const uint8_t *data = tex.data.get();
|
|
return {(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
|
|
struct pcx_t
|
|
{
|
|
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
|
|
};
|
|
|
|
/*
|
|
==============
|
|
LoadPCX
|
|
==============
|
|
*/
|
|
bool LoadPCX(const std::filesystem::path &filename, uint8_t **pixels, uint8_t **palette, int *width, int *height)
|
|
{
|
|
uint8_t *raw;
|
|
int runLength;
|
|
|
|
// Load the file
|
|
if (!std::filesystem::exists(filename)) {
|
|
FLogPrint("Failed to load '{}'. 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.string().c_str(), reinterpret_cast<void **>(&raw));
|
|
if (len < 1) {
|
|
FLogPrint("Failed to load '{}'. 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) {
|
|
FLogPrint("Failed to load '{}'. Unsupported PCX file.\n", filename);
|
|
return false; // mxd
|
|
}
|
|
|
|
if (palette) {
|
|
*palette = new uint8_t[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 = new uint8_t[numbytes];
|
|
if (!out) {
|
|
FLogPrint("Failed to load '{}'. Couldn't allocate {} 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) {
|
|
FLogPrint("File '{}' was malformed.\n", filename);
|
|
return false; // mxd
|
|
}
|
|
|
|
delete[] pcx;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
============================================================================
|
|
TARGA IMAGE
|
|
============================================================================
|
|
*/
|
|
|
|
// mxd. Copied from
|
|
// https://github.com/qbism/q2tools-220/blob/f8f582fc02196955584542c95de8a45a138c9e42/common/lbmlib.c#L625
|
|
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;
|
|
};
|
|
|
|
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
|
|
=============
|
|
*/
|
|
bool LoadTGA(const std::filesystem::path &filename, uint8_t **pixels, int *width, int *height)
|
|
{
|
|
uint8_t *pixbuf;
|
|
int row, column;
|
|
TargaHeader targa_header;
|
|
|
|
qfile_t fin = SafeOpenRead(filename);
|
|
if (!fin) {
|
|
FLogPrint("Failed to load '{}'. File does not exist.\n", filename);
|
|
return false; // mxd
|
|
}
|
|
|
|
targa_header.id_length = fgetc(fin.get());
|
|
targa_header.colormap_type = fgetc(fin.get());
|
|
targa_header.image_type = fgetc(fin.get());
|
|
|
|
targa_header.colormap_index = fgetLittleShort(fin.get());
|
|
targa_header.colormap_length = fgetLittleShort(fin.get());
|
|
targa_header.colormap_size = fgetc(fin.get());
|
|
targa_header.x_origin = fgetLittleShort(fin.get());
|
|
targa_header.y_origin = fgetLittleShort(fin.get());
|
|
targa_header.width = fgetLittleShort(fin.get());
|
|
targa_header.height = fgetLittleShort(fin.get());
|
|
targa_header.pixel_size = fgetc(fin.get());
|
|
targa_header.attributes = fgetc(fin.get());
|
|
|
|
if (targa_header.image_type != 2 && targa_header.image_type != 10) {
|
|
FLogPrint("Failed to load '{}'. 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)) {
|
|
FLogPrint("Failed to load '{}'. 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 = new uint8_t[numPixels * 4];
|
|
*pixels = targa_rgba;
|
|
|
|
if (targa_header.id_length != 0)
|
|
SafeSeek(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.get());
|
|
green = getc(fin.get());
|
|
red = getc(fin.get());
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
case 32:
|
|
blue = getc(fin.get());
|
|
green = getc(fin.get());
|
|
red = getc(fin.get());
|
|
alphabyte = getc(fin.get());
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alphabyte;
|
|
break;
|
|
default:
|
|
FLogPrint("unsupported pixel size: {}\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.get());
|
|
const unsigned char packetSize = 1 + (packetHeader & 0x7f);
|
|
if (packetHeader & 0x80) { // run-length packet
|
|
switch (targa_header.pixel_size) {
|
|
case 24:
|
|
blue = getc(fin.get());
|
|
green = getc(fin.get());
|
|
red = getc(fin.get());
|
|
alphabyte = 255;
|
|
break;
|
|
case 32:
|
|
blue = getc(fin.get());
|
|
green = getc(fin.get());
|
|
red = getc(fin.get());
|
|
alphabyte = getc(fin.get());
|
|
break;
|
|
default:
|
|
FLogPrint("unsupported pixel size: {}\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.get());
|
|
green = getc(fin.get());
|
|
red = getc(fin.get());
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
case 32:
|
|
blue = getc(fin.get());
|
|
green = getc(fin.get());
|
|
red = getc(fin.get());
|
|
alphabyte = getc(fin.get());
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alphabyte;
|
|
break;
|
|
default:
|
|
FLogPrint("unsupported pixel size: {}\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:;
|
|
}
|
|
}
|
|
|
|
return true; // mxd
|
|
}
|
|
|
|
/*
|
|
============================================================================
|
|
WAL IMAGE
|
|
============================================================================
|
|
*/
|
|
|
|
bool LoadWAL(const std::filesystem::path &filename, uint8_t **pixels, int *width, int *height)
|
|
{
|
|
if (!std::filesystem::exists(filename)) {
|
|
FLogPrint("Failed to load '{}'. 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.string().c_str(), static_cast<void *>(&mt));
|
|
if (len < 1) {
|
|
FLogPrint("Failed to load '{}'. 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 = new uint8_t[numpixels];
|
|
if (!out) {
|
|
FLogPrint("Failed to load '{}'. Couldn't allocate {} 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
|
|
}
|
|
|
|
delete[] mt;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==============================================================================
|
|
Load (Quake 2) / Convert (Quake, Hexen 2) textures from paletted to RGBA (mxd)
|
|
==============================================================================
|
|
*/
|
|
|
|
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;
|
|
|
|
static const bool is_mod = gamedir != basedir;
|
|
const char *TEXTURES_PATH = "textures";
|
|
std::filesystem::path path[] = {
|
|
(gamedir / TEXTURES_PATH / texture).replace_extension("tga"),
|
|
(basedir / TEXTURES_PATH / texture).replace_extension("tga"),
|
|
(gamedir / TEXTURES_PATH / texture).replace_extension("wal"),
|
|
(basedir / TEXTURES_PATH / texture).replace_extension("wal"),
|
|
};
|
|
|
|
int c;
|
|
for (c = 0; c < 4; c++) {
|
|
// Skip paths at even indexes when running from game folder...
|
|
if ((is_mod || c % 2 == 0) && std::filesystem::exists(path[c])) {
|
|
texturenames[texture] = path[c].string();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (c == 4) {
|
|
if (is_mod)
|
|
LogPrint("WARNING: failed to find texture '{}'. Checked paths:\n'{}'\n'{}'\n'{}'\n'{}'\n", texture, path[0],
|
|
path[1], path[2], path[3]);
|
|
else
|
|
LogPrint("WARNING: failed to find texture '{}'. Checked paths:\n'{}'\n'{}'\n", texture, path[0], path[2]);
|
|
|
|
// Store to preserve offset...
|
|
texturenames[texture] = {};
|
|
}
|
|
}
|
|
|
|
static void // Loads textures from disk and stores them in bsp->drgbatexdata (Quake 2)
|
|
LoadTextures(mbsp_t *bsp)
|
|
{
|
|
LogPrint("--- LoadTextures ---\n");
|
|
|
|
if (bsp->dtex.textures.size()) {
|
|
FError("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 (auto &texinfo : bsp->texinfo)
|
|
AddTextureName(texturenames, texinfo.texture.data());
|
|
|
|
// 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) {
|
|
const 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;
|
|
auto &tex_mips = bsp->drgbatexdata;
|
|
int counter = 0;
|
|
|
|
for (auto pair : texturenames) {
|
|
// Store texturename index...
|
|
indicesbytexturename[pair.first] = counter++;
|
|
|
|
// Add empty to keep texture index in case of load problems...
|
|
auto &tex = tex_mips.emplace_back();
|
|
|
|
// Find file extension
|
|
const size_t dpos = pair.second.rfind('.');
|
|
if (dpos == std::string::npos) {
|
|
if (!pair.second.empty()) // Missing texture warning was already displayed
|
|
LogPrint("WARNING: unexpected texture filename: '{}'\n", pair.second);
|
|
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: '{}'\n", pair.second);
|
|
continue;
|
|
}
|
|
|
|
// Create rgba_miptex_t...
|
|
tex.name = pair.first;
|
|
tex.width = width;
|
|
tex.height = height;
|
|
tex.data = std::unique_ptr<uint8_t[]>(pixels);
|
|
}
|
|
|
|
// Sanity checks...
|
|
Q_assert(tex_mips.size() == texturenames.size());
|
|
Q_assert(texturenames.size() == indicesbytexturename.size());
|
|
|
|
// Step 4: set miptex indices to gtexinfo_t
|
|
for (auto &info : bsp->texinfo) {
|
|
const auto pair = indicesbytexturename.find(info.texture.data());
|
|
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->dtex.textures.size())
|
|
return;
|
|
|
|
LogPrint("--- ConvertTextures ---\n");
|
|
|
|
std::map<int, std::string> texturenamesbyindex;
|
|
auto &tex_mips = bsp->drgbatexdata;
|
|
|
|
// Step 1: store texture data and RGBA bytes in temporary arrays...
|
|
for (size_t i = 0; i < bsp->dtex.textures.size(); i++) {
|
|
auto &miptex = bsp->dtex.textures[i];
|
|
|
|
// Add empty to keep texture index in case of load problems...
|
|
auto &tex = tex_mips.emplace_back();
|
|
|
|
if (!miptex.data[0]) {
|
|
continue;
|
|
}
|
|
|
|
// Create rgba_miptex_t...
|
|
tex.name = miptex.name;
|
|
tex.width = miptex.width;
|
|
tex.height = miptex.height;
|
|
|
|
// Store texturename index...
|
|
texturenamesbyindex[i] = tex.name;
|
|
|
|
// Convert to RGBA
|
|
const size_t numpalpixels = tex.width * tex.height;
|
|
const uint8_t *data = miptex.data[0].get();
|
|
uint8_t *pixels = new uint8_t[numpalpixels * 4]; // RGBA
|
|
|
|
for (size_t 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);
|
|
}
|
|
|
|
tex.data = std::unique_ptr<uint8_t[]>(pixels);
|
|
}
|
|
|
|
// Sanity checks...
|
|
Q_assert(bsp->dtex.textures.size() == tex_mips.size());
|
|
|
|
// Step 2: set texturenames to gmiptex_t
|
|
for (auto &info : bsp->texinfo) {
|
|
const auto pair = texturenamesbyindex.find(info.miptex);
|
|
if (pair != texturenamesbyindex.end())
|
|
strcpy(info.texture.data(), 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->dtex.textures.size() > 0)
|
|
ConvertTextures(bsp);
|
|
else
|
|
LogPrint("WARNING: failed to load or convert textures.\n");
|
|
} |