vhlt/hlrad/loadtextures.cpp
2016-09-21 00:07:53 +03:00

1507 lines
40 KiB
C++

#include "qrad.h"
#ifdef HLRAD_TEXTURE
#ifdef WORDS_BIGENDIAN
#error "HLRAD_TEXTURE doesn't support WORDS_BIGENDIAN, because I have no big endian machine to test it"
#endif
int g_numtextures;
radtexture_t *g_textures;
typedef struct waddir_s
{
struct waddir_s *next;
char path[_MAX_PATH];
} waddir_t;
waddir_t *g_waddirs = NULL;
void AddWadFolder (const char *path)
{
waddir_t *waddir;
waddir = (waddir_t *)malloc (sizeof (waddir_t));
hlassume (waddir != NULL, assume_NoMemory);
{
waddir_t **pos;
for (pos = &g_waddirs; *pos; pos = &(*pos)->next)
;
waddir->next = *pos;
*pos = waddir;
}
safe_snprintf (waddir->path, _MAX_PATH, "%s", path);
}
typedef struct
{
int filepos;
int disksize;
int size;
char type;
char compression;
char pad1, pad2;
char name[16];
} lumpinfo_t;
typedef struct wadfile_s
{
struct wadfile_s *next;
char path[_MAX_PATH];
FILE *file;
int filesize;
int numlumps;
lumpinfo_t *lumpinfos;
} wadfile_t;
wadfile_t *g_wadfiles = NULL;
bool g_wadfiles_opened;
static int CDECL lump_sorter_by_name (const void *lump1, const void *lump2)
{
lumpinfo_t *plump1 = (lumpinfo_t *)lump1;
lumpinfo_t *plump2 = (lumpinfo_t *)lump2;
return strcasecmp (plump1->name, plump2->name);
}
void OpenWadFile (const char *name
#ifdef ZHLT_NOWADDIR
, bool fullpath = false
#endif
)
{
int i;
wadfile_t *wad;
wad = (wadfile_t *)malloc (sizeof (wadfile_t));
hlassume (wad != NULL, assume_NoMemory);
{
wadfile_t **pos;
for (pos = &g_wadfiles; *pos; pos = &(*pos)->next)
;
wad->next = *pos;
*pos = wad;
}
#ifdef ZHLT_NOWADDIR
if (fullpath)
{
safe_snprintf (wad->path, _MAX_PATH, "%s", name);
wad->file = fopen (wad->path, "rb");
if (!wad->file)
{
Error ("Couldn't open %s", wad->path);
}
}
else
{
#endif
waddir_t *dir;
for (dir = g_waddirs; dir; dir = dir->next)
{
safe_snprintf (wad->path, _MAX_PATH, "%s\\%s", dir->path, name);
wad->file = fopen (wad->path, "rb");
if (wad->file)
{
break;
}
}
if (!dir)
{
Fatal (assume_COULD_NOT_LOCATE_WAD, "Could not locate wad file %s", name);
return;
}
#ifdef ZHLT_NOWADDIR
}
#endif
Log ("Using Wadfile: %s\n", wad->path);
wad->filesize = q_filelength (wad->file);
struct
{
char identification[4];
int numlumps;
int infotableofs;
} wadinfo;
if (wad->filesize < (int)sizeof (wadinfo))
{
Error ("Invalid wad file '%s'.", wad->path);
}
SafeRead (wad->file, &wadinfo, sizeof (wadinfo));
wadinfo.numlumps = LittleLong(wadinfo.numlumps);
wadinfo.infotableofs = LittleLong(wadinfo.infotableofs);
if (strncmp (wadinfo.identification, "WAD2", 4) && strncmp (wadinfo.identification, "WAD3", 4))
Error ("%s isn't a Wadfile!", wad->path);
wad->numlumps = wadinfo.numlumps;
if (wad->numlumps < 0 || wadinfo.infotableofs < 0 || wadinfo.infotableofs + wad->numlumps * (int)sizeof (lumpinfo_t) > wad->filesize)
{
Error ("Invalid wad file '%s'.", wad->path);
}
wad->lumpinfos = (lumpinfo_t *)malloc (wad->numlumps * sizeof (lumpinfo_t));
hlassume (wad->lumpinfos != NULL, assume_NoMemory);
if (fseek (wad->file, wadinfo.infotableofs, SEEK_SET))
Error ("File read failure: %s", wad->path);
for (i = 0; i < wad->numlumps; i++)
{
SafeRead (wad->file, &wad->lumpinfos[i], sizeof (lumpinfo_t));
if (!TerminatedString(wad->lumpinfos[i].name, 16))
{
wad->lumpinfos[i].name[16 - 1] = 0;
Warning("Unterminated texture name : wad[%s] texture[%d] name[%s]\n", wad->path, i, wad->lumpinfos[i].name);
}
wad->lumpinfos[i].filepos = LittleLong(wad->lumpinfos[i].filepos);
wad->lumpinfos[i].disksize = LittleLong(wad->lumpinfos[i].disksize);
wad->lumpinfos[i].size = LittleLong(wad->lumpinfos[i].size);
}
qsort (wad->lumpinfos, wad->numlumps, sizeof (lumpinfo_t), lump_sorter_by_name);
}
void TryOpenWadFiles ()
{
if (!g_wadfiles_opened)
{
g_wadfiles_opened = true;
#ifdef ZHLT_NOWADDIR
char filename[_MAX_PATH];
safe_snprintf(filename, _MAX_PATH, "%s.wa_", g_Mapname);
if (q_exists (filename))
{
OpenWadFile (filename, true);
}
else
{
Warning ("Couldn't open %s", filename);
#endif
Log ("Opening wad files from directories:\n");
if (!g_waddirs)
{
Warning ("No wad directories have been set.");
}
else
{
waddir_t *dir;
for (dir = g_waddirs; dir; dir = dir->next)
{
Log (" %s\n", dir->path);
}
}
const char *value = ValueForKey (&g_entities[0], "wad");
char path[MAX_VAL];
int i, j;
for (i = 0, j = 0; i < strlen(value) + 1; i++)
{
if (value[i] == ';' || value[i] == '\0')
{
path[j] = '\0';
if (path[0])
{
char name[MAX_VAL];
ExtractFile (path, name);
DefaultExtension (name, ".wad");
OpenWadFile (name);
}
j = 0;
}
else
{
path[j] = value[i];
j++;
}
}
#ifdef ZHLT_NOWADDIR
}
#endif
CheckFatal ();
}
}
void TryCloseWadFiles ()
{
if (g_wadfiles_opened)
{
g_wadfiles_opened = false;
wadfile_t *wadfile, *next;
for (wadfile = g_wadfiles; wadfile; wadfile = next)
{
next = wadfile->next;
free (wadfile->lumpinfos);
fclose (wadfile->file);
free (wadfile);
}
g_wadfiles = NULL;
}
}
void DefaultTexture (radtexture_t *tex, const char *name)
{
int i;
tex->width = 16;
tex->height = 16;
strcpy (tex->name, name);
tex->name[16 - 1] = '\0';
tex->canvas = (byte *)malloc (tex->width * tex->height);
hlassume (tex->canvas != NULL, assume_NoMemory);
for (i = 0; i < 256; i++)
{
VectorFill (tex->palette[i], 0x80);
}
for (i = 0; i < tex->width * tex->height; i++)
{
tex->canvas[i] = 0x00;
}
}
void LoadTexture (radtexture_t *tex, const miptex_t *mt, int size)
{
int i, j;
const miptex_t *header = mt;
const byte *data = (const byte *)mt;
tex->width = header->width;
tex->height = header->height;
strcpy (tex->name, header->name);
tex->name[16 - 1] = '\0';
if (tex->width <= 0 || tex->height <= 0 ||
tex->width % (2 * 1 << (MIPLEVELS - 1)) != 0 || tex->height % (2 * (1 << (MIPLEVELS - 1))) != 0)
{
Error ("Texture '%s': dimension (%dx%d) is not multiple of %d.", tex->name, tex->width, tex->height, 2 * (1 << (MIPLEVELS - 1)));
}
int mipsize;
for (mipsize = 0, i = 0; i < MIPLEVELS; i++)
{
if ((int)mt->offsets[i] != (int)sizeof (miptex_t) + mipsize)
{
Error ("Texture '%s': unexpected miptex offset.", tex->name);
}
mipsize += (tex->width >> i) * (tex->height >> i);
}
if (size < (int)sizeof (miptex_t) + mipsize + 2 + 256 * 3)
{
Error ("Texture '%s': no enough data.", tex->name);
}
if (*(unsigned short *)&data[sizeof (miptex_t) + mipsize] != 256)
{
Error ("Texture '%s': palette size is not 256.", tex->name);
}
tex->canvas = (byte *)malloc (tex->width * tex->height);
hlassume (tex->canvas != NULL, assume_NoMemory);
for (i = 0; i < tex->height; i++)
{
for (j = 0; j < tex->width; j++)
{
tex->canvas[i * tex->width + j] = data[sizeof (miptex_t) + i * tex->width + j];
}
}
for (i = 0; i < 256; i++)
{
for (j = 0; j < 3; j++)
{
tex->palette[i][j] = data[sizeof (miptex_t) + mipsize + 2 + i * 3 + j];
}
}
}
void LoadTextureFromWad (radtexture_t *tex, const miptex_t *header)
{
tex->width = header->width;
tex->height = header->height;
strcpy (tex->name, header->name);
tex->name[16 - 1] = '\0';
wadfile_t *wad;
for (wad = g_wadfiles; wad; wad = wad->next)
{
lumpinfo_t temp, *found;
strcpy (temp.name, tex->name);
found = (lumpinfo_t *)bsearch (&temp, wad->lumpinfos, wad->numlumps, sizeof (lumpinfo_t), lump_sorter_by_name);
if (found)
{
Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': found in '%s'.\n", tex->name, wad->path);
if (found->type != 67 || found->compression != 0)
continue;
if (found->disksize < (int)sizeof (miptex_t) || found->filepos < 0 || found->filepos + found->disksize > wad->filesize)
{
Warning ("Texture '%s': invalid texture data in '%s'.", tex->name, wad->path);
continue;
}
miptex_t *mt = (miptex_t *)malloc (found->disksize);
hlassume (mt != NULL, assume_NoMemory);
if (fseek (wad->file, found->filepos, SEEK_SET))
Error ("File read failure");
SafeRead (wad->file, mt, found->disksize);
if (!TerminatedString(mt->name, 16))
{
Warning("Texture '%s': invalid texture data in '%s'.", tex->name, wad->path);
free (mt);
continue;
}
Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': name '%s', width %d, height %d.\n", tex->name, mt->name, mt->width, mt->height);
if (strcasecmp (mt->name, tex->name))
{
Warning("Texture '%s': texture name '%s' differs from its reference name '%s' in '%s'.", tex->name, mt->name, tex->name, wad->path);
}
LoadTexture (tex, mt, found->disksize);
free (mt);
break;
}
}
if (!wad)
{
Warning ("Texture '%s': texture is not found in wad files.", tex->name);
DefaultTexture (tex, tex->name);
return;
}
}
void LoadTextures ()
{
if (!g_notextures)
{
Log ("Load Textures:\n");
}
g_numtextures = g_texdatasize? ((dmiptexlump_t *)g_dtexdata)->nummiptex: 0;
g_textures = (radtexture_t *)malloc (g_numtextures * sizeof (radtexture_t));
hlassume (g_textures != NULL, assume_NoMemory);
int i;
for (i = 0; i < g_numtextures; i++)
{
int offset = ((dmiptexlump_t *)g_dtexdata)->dataofs[i];
int size = g_texdatasize - offset;
radtexture_t *tex = &g_textures[i];
if (g_notextures)
{
DefaultTexture (tex, "DEFAULT");
}
else if (offset < 0 || size < (int)sizeof (miptex_t))
{
Warning ("Invalid texture data in '%s'.", g_source);
DefaultTexture (tex, "");
}
else
{
miptex_t *mt = (miptex_t *)&g_dtexdata[offset];
if (mt->offsets[0])
{
Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': found in '%s'.\n", mt->name, g_source);
Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': name '%s', width %d, height %d.\n", mt->name, mt->name, mt->width, mt->height);
LoadTexture (tex, mt, size);
}
else
{
TryOpenWadFiles ();
LoadTextureFromWad (tex, mt);
}
}
#ifdef HLRAD_REFLECTIVITY
{
vec3_t total;
VectorClear (total);
for (int j = 0; j < tex->width * tex->height; j++)
{
vec3_t reflectivity;
if (tex->name[0] == '{' && tex->canvas[j] == 0xFF)
{
VectorFill (reflectivity, 0.0);
}
else
{
VectorScale (tex->palette[tex->canvas[j]], 1.0/255.0, reflectivity);
for (int k = 0; k < 3; k++)
{
reflectivity[k] = pow (reflectivity[k], g_texreflectgamma);
}
VectorScale (reflectivity, g_texreflectscale, reflectivity);
}
VectorAdd (total, reflectivity, total);
}
VectorScale (total, 1.0 / (double)(tex->width * tex->height), total);
VectorCopy (total, tex->reflectivity);
Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': reflectivity is (%f,%f,%f).\n",
tex->name, tex->reflectivity[0], tex->reflectivity[1], tex->reflectivity[2]);
if (VectorMaximum (tex->reflectivity) > 1.0 + NORMAL_EPSILON)
{
Warning ("Texture '%s': reflectivity (%f,%f,%f) greater than 1.0.", tex->name, tex->reflectivity[0], tex->reflectivity[1], tex->reflectivity[2]);
}
}
#endif
}
if (!g_notextures)
{
Log ("%i textures referenced\n", g_numtextures);
TryCloseWadFiles ();
}
}
#ifdef ZHLT_EMBEDLIGHTMAP
// color quantization algorithm
#define CQ_DIM 3
template<class T, class T2, class T3> inline void CQ_VectorSubtract (const T a[CQ_DIM], const T2 b[CQ_DIM], T3 c[CQ_DIM])
{
for (int x = 0; x < CQ_DIM; x++)
{
c[x] = a[x] - b[x];
}
}
template<class T, class T2, class T3> inline void CQ_VectorAdd (const T a[CQ_DIM], const T2 b[CQ_DIM], T3 c[CQ_DIM])
{
for (int x = 0; x < CQ_DIM; x++)
{
c[x] = a[x] + b[x];
}
}
template<class T, class T2> inline void CQ_VectorScale (const T a[CQ_DIM], const T2 b, T c[CQ_DIM])
{
for (int x = 0; x < CQ_DIM; x++)
{
c[x] = a[x] * b;
}
}
template<class T, class T2> inline void CQ_VectorCopy (const T a[CQ_DIM], T2 b[CQ_DIM])
{
for (int x = 0; x < CQ_DIM; x++)
{
b[x] = a[x];
}
}
template<class T> inline void CQ_VectorClear (T a[CQ_DIM])
{
for (int x = 0; x < CQ_DIM; x++)
{
a[x] = (T)0;
}
}
template<class T> inline T CQ_DotProduct (const T a[CQ_DIM], const T b[CQ_DIM])
{
T dot = (T)0;
for (int x = 0; x < CQ_DIM; x++)
{
dot += a[x] * b[x];
}
return dot;
}
typedef struct
{
int axis;
int dist;
double numpoints[2];
}
cq_splitter_t; // partition the space into { point: point[axis] < dist } and { point: point[axis] >= dist }
typedef struct cq_node_s
{
bool isleafnode;
cq_node_s *parentnode;
cq_node_s *childrennode[2];
int numpoints; // numpoints > 0
unsigned char (*refpoints)[CQ_DIM];
double centerofpoints[CQ_DIM];
bool needsplit;
cq_splitter_t bestsplitter;
double splitpriority;
}
cq_node_t; // a cuboid region; the root node is the entire cube whose size is 255
typedef struct cq_searchnode_s
{
bool isleafnode;
cq_searchnode_s *childrennode[2];
int planeaxis;
int planedist;
int result;
}
cq_searchnode_t;
static void CQ_SelectPartition (cq_node_t *node)
{
CQ_VectorClear (node->centerofpoints);
for (int i = 0; i < node->numpoints; i++)
{
CQ_VectorAdd (node->centerofpoints, node->refpoints[i], node->centerofpoints);
}
CQ_VectorScale (node->centerofpoints, 1 / (double)node->numpoints, node->centerofpoints);
node->needsplit = false;
for (int k = 0; k < CQ_DIM; k++)
{
double count;
double counts[256];
double sum[CQ_DIM];
double sums[256][CQ_DIM];
double bucketsums[256][CQ_DIM];
int bucketsizes[256];
const unsigned char (*nodepoints)[CQ_DIM] = node->refpoints;
const int nodenumpoints = node->numpoints;
memset (bucketsums, 0, 256 * sizeof (double [CQ_DIM]));
memset (bucketsizes, 0, 256 * sizeof (int));
for (int i = 0; i < nodenumpoints; i++)
{
int j = nodepoints[i][k];
bucketsizes[j]++;
CQ_VectorAdd (bucketsums[j], nodepoints[i], bucketsums[j]);
}
int min = 256;
int max = -1;
count = 0;
CQ_VectorClear (sum);
for (int j = 0; j < 256; j++)
{
counts[j] = count;
CQ_VectorCopy (sum, sums[j]);
count += bucketsizes[j];
CQ_VectorAdd (sum, bucketsums[j], sum);
if (bucketsizes[j] > 0)
{
if (j < min)
{
min = j;
}
if (j > max)
{
max = j;
}
}
}
if (max < min)
{
Error ("CQ_SelectPartition: internal error");
}
// sweep along the axis and find the plane that maximize square error reduction
for (int j = min + 1; j < max + 1; j++)
{
double priority = 0; // the decrease in total square deviation
priority -= CQ_DotProduct (sum, sum) / count;
priority += CQ_DotProduct (sums[j], sums[j]) / counts[j];
double remain[CQ_DIM];
CQ_VectorSubtract (sum, sums[j], remain); // sums and counts are precise (have no round-off error)
priority += CQ_DotProduct (remain, remain) / (count - counts[j]);
if (node->needsplit == false ||
priority > node->splitpriority + 0.1 ||
priority >= node->splitpriority - 0.1
&& fabs (counts[j] - count / 2) < fabs (node->bestsplitter.numpoints[0] - count / 2))
{
node->needsplit = true;
node->splitpriority = priority;
node->bestsplitter.axis = k;
node->bestsplitter.dist = j;
node->bestsplitter.numpoints[0] = counts[j];
node->bestsplitter.numpoints[1] = count - counts[j];
}
}
}
}
static cq_searchnode_t *CQ_AllocSearchTree (int maxcolors)
{
cq_searchnode_t *searchtree;
searchtree = (cq_searchnode_t *)malloc ((2 * maxcolors - 1) * sizeof (cq_searchnode_t));
hlassume (searchtree != NULL, assume_NoMemory);
return searchtree;
}
static void CQ_FreeSearchTree (cq_searchnode_t *searchtree)
{
free (searchtree);
}
static void CQ_CreatePalette (int numpoints, const unsigned char (*points)[CQ_DIM],
int maxcolors, unsigned char (*colors_out)[CQ_DIM], int &numcolors_out,
cq_searchnode_t *searchtree_out) //[2 * maxcolors - 1]
{
if (numpoints <= 0 || maxcolors <= 0)
{
numcolors_out = 0;
return;
}
unsigned char (*pointarray)[CQ_DIM];
pointarray = (unsigned char (*)[CQ_DIM])malloc (numpoints * sizeof (unsigned char [CQ_DIM]));
hlassume (pointarray != NULL, assume_NoMemory);
memcpy (pointarray, points, numpoints * sizeof (unsigned char [CQ_DIM]));
cq_node_t *n;
cq_searchnode_t *s;
int numnodes = 0;
int maxnodes = 2 * maxcolors - 1;
cq_node_t *nodes = (cq_node_t *)malloc (maxnodes * sizeof (cq_node_t));
hlassume (nodes != NULL, assume_NoMemory);
n = &nodes[0];
numnodes++;
n->isleafnode = true;
n->parentnode = NULL;
n->numpoints = numpoints;
n->refpoints = pointarray;
CQ_SelectPartition (n);
for (int i = 1; i < maxcolors; i++)
{
bool needsplit;
double bestpriority;
cq_node_t *bestnode;
needsplit = false;
for (int j = 0; j < numnodes; j++)
{
n = &nodes[j];
if (!n->isleafnode || !n->needsplit)
{
continue;
}
if (needsplit == false || n->splitpriority > bestpriority + 0.1)
{
needsplit = true;
bestpriority = n->splitpriority;
bestnode = n;
}
}
if (!needsplit)
{
break;
}
bestnode->isleafnode = false;
for (int k = 0; k < 2; k++)
{
n = &nodes[numnodes];
numnodes++;
if (numnodes > maxnodes)
{
Error ("CQ_CreatePalette: internal error");
}
bestnode->childrennode[k] = n;
n->isleafnode = true;
n->parentnode = bestnode;
n->numpoints = 0;
n->refpoints = NULL;
}
// partition the points using the best splitter
{
const int splitaxis = bestnode->bestsplitter.axis;
const int splitdist = bestnode->bestsplitter.dist;
const int numpoints = bestnode->numpoints;
unsigned char (*points)[CQ_DIM] = bestnode->refpoints;
unsigned char (*left)[CQ_DIM];
unsigned char (*right)[CQ_DIM];
left = &bestnode->refpoints[0];
right = &bestnode->refpoints[bestnode->numpoints - 1];
while (1)
{
while ((*left)[splitaxis] < splitdist)
{
left++;
}
while ((*right)[splitaxis] >= splitdist)
{
right--;
}
if (left >= right)
{
break;
}
unsigned char tmp[CQ_DIM];
CQ_VectorCopy (*left, tmp);
CQ_VectorCopy (*right, *left);
CQ_VectorCopy (tmp, *right);
}
if (right != left - 1)
{
Error ("CQ_CreatePalette: internal error");
}
bestnode->childrennode[0]->numpoints = left - bestnode->refpoints;
bestnode->childrennode[0]->refpoints = bestnode->refpoints;
bestnode->childrennode[1]->numpoints = &bestnode->refpoints[bestnode->numpoints] - left;
bestnode->childrennode[1]->refpoints = left;
if (bestnode->childrennode[0]->numpoints <= 0 ||
bestnode->childrennode[0]->numpoints != bestnode->bestsplitter.numpoints[0])
{
Error ("CQ_CreatePalette: internal error");
}
if (bestnode->childrennode[1]->numpoints <= 0 ||
bestnode->childrennode[1]->numpoints != bestnode->bestsplitter.numpoints[1])
{
Error ("CQ_CreatePalette: internal error");
}
}
CQ_SelectPartition (bestnode->childrennode[0]);
CQ_SelectPartition (bestnode->childrennode[1]);
}
for (int i = 0; i < numnodes; i++)
{
n = &nodes[i];
s = &searchtree_out[i];
s->isleafnode = n->isleafnode;
if (!n->isleafnode)
{
s->planeaxis = n->bestsplitter.axis;
s->planedist = n->bestsplitter.dist;
s->childrennode[0] = &searchtree_out[n->childrennode[0] - nodes];
s->childrennode[1] = &searchtree_out[n->childrennode[1] - nodes];
}
}
numcolors_out = 0;
n = &nodes[0];
while (1)
{
while (!n->isleafnode)
{
n = n->childrennode[0];
}
s = &searchtree_out[n - nodes];
s->result = numcolors_out;
for (int k = 0; k < CQ_DIM; k++)
{
int val = (int)floor (n->centerofpoints[k] + 0.5 + 0.00001);
val = qmax (0, qmin (val, 255));
colors_out[numcolors_out][k] = val;
}
numcolors_out++;
while (n->parentnode)
{
if (n == n->parentnode->childrennode[0])
{
break;
}
n = n->parentnode;
}
if (!n->parentnode)
{
break;
}
n = n->parentnode->childrennode[1];
}
if (2 * numcolors_out - 1 != numnodes)
{
Error ("CQ_CreatePalette: internal error");
}
free (pointarray);
free (nodes);
}
static void CQ_MapPoint_r (int *bestdist, int *best,
cq_searchnode_t *node, const unsigned char (*colors)[CQ_DIM],
const unsigned char point[CQ_DIM], int searchradius)
{
while (!node->isleafnode)
{
int dist = point[node->planeaxis] - node->planedist;
if (dist <= -searchradius)
{
node = node->childrennode[0];
}
else if (dist >= searchradius - 1)
{
node = node->childrennode[1];
}
else
{
CQ_MapPoint_r (bestdist, best, node->childrennode[0], colors, point, searchradius);
CQ_MapPoint_r (bestdist, best, node->childrennode[1], colors, point, searchradius);
return;
}
}
int dist = 0;
for (int k = 0; k < CQ_DIM; k++)
{
dist += (colors[node->result][k] - point[k]) * (colors[node->result][k] - point[k]);
}
if (dist <= *bestdist)
{
if (dist < *bestdist || node->result < *best)
{
*bestdist = dist;
*best = node->result;
}
}
}
static int CQ_MapPoint (const unsigned char point[CQ_DIM], const unsigned char (*colors)[CQ_DIM], int numcolors, cq_searchnode_t *searchtree)
{
if (numcolors <= 0)
{
Error ("CQ_MapPoint: internal error");
}
cq_searchnode_t *node;
int bestdist;
int best;
int searchradius;
for (node = searchtree; !node->isleafnode; )
{
node = node->childrennode[point[node->planeaxis] >= node->planedist];
}
best = node->result;
bestdist = 0;
for (int k = 0; k < CQ_DIM; k++)
{
bestdist += (colors[best][k] - point[k]) * (colors[best][k] - point[k]);
}
searchradius = (int)ceil(sqrt ((double)bestdist) + 0.1);
CQ_MapPoint_r (&bestdist, &best, searchtree, colors, point, searchradius);
return best;
}
// =====================================================================================
// EmbedLightmapInTextures
// check for "zhlt_embedlightmap" and update g_dfaces, g_texinfo, g_dtexdata and g_dlightdata
// =====================================================================================
#define RADTEXTURES_MAX 2048 // should be smaller than 62 * 62 and smaller than MAX_MAP_TEXTURES
static int g_newtextures_num = 0;
static byte *g_newtextures_data[RADTEXTURES_MAX];
static int g_newtextures_size[RADTEXTURES_MAX];
int NewTextures_GetCurrentMiptexIndex ()
{
dmiptexlump_t *texdata = (dmiptexlump_t *)g_dtexdata;
return texdata->nummiptex + g_newtextures_num;
}
void NewTextures_PushTexture (int size, void *data)
{
if (g_newtextures_num >= RADTEXTURES_MAX)
{
Error ("the number of textures created by hlrad has exceeded its internal limit(%d).", (int)RADTEXTURES_MAX);
}
g_newtextures_data[g_newtextures_num] = (byte *)malloc (size);
hlassume (g_newtextures_data[g_newtextures_num] != NULL, assume_NoMemory);
memcpy (g_newtextures_data[g_newtextures_num], data, size);
g_newtextures_size[g_newtextures_num] = size;
g_newtextures_num++;
}
void NewTextures_Write ()
{
if (!g_newtextures_num)
{
return;
}
int i;
dmiptexlump_t *texdata = (dmiptexlump_t *)g_dtexdata;
byte *dataaddr = (byte *)&texdata->dataofs[texdata->nummiptex];
int datasize = (g_dtexdata + g_texdatasize) - dataaddr;
byte *newdataaddr = (byte *)&texdata->dataofs[texdata->nummiptex + g_newtextures_num];
hlassume (g_texdatasize + (newdataaddr - dataaddr) <= g_max_map_miptex, assume_MAX_MAP_MIPTEX);
memmove (newdataaddr, dataaddr, datasize);
g_texdatasize += newdataaddr - dataaddr;
for (i = 0; i < texdata->nummiptex; i++)
{
if (texdata->dataofs[i] < 0) // bad texture
{
continue;
}
texdata->dataofs[i] += newdataaddr - dataaddr;
}
hlassume (texdata->nummiptex + g_newtextures_num < MAX_MAP_TEXTURES, assume_MAX_MAP_TEXTURES);
for (i = 0; i < g_newtextures_num; i++)
{
hlassume (g_texdatasize + g_newtextures_size[i] <= g_max_map_miptex, assume_MAX_MAP_MIPTEX);
memcpy (g_dtexdata + g_texdatasize, g_newtextures_data[i], g_newtextures_size[i]);
texdata->dataofs[texdata->nummiptex + i] = g_texdatasize;
g_texdatasize += g_newtextures_size[i];
}
texdata->nummiptex += g_newtextures_num;
for (int i = 0; i < g_newtextures_num; i++)
{
free (g_newtextures_data[i]);
}
g_newtextures_num = 0;
}
static unsigned int Hash (int size, void *data)
{
unsigned int hash = 0;
for (int i = 0; i < size; i++)
{
hash = 31 * hash + ((unsigned char *)data)[i];
}
return hash;
}
static void GetLightInt (dface_t *face, const int texsize[2], int ix, int iy, vec3_t &light)
{
ix = qmax (0, qmin (ix, texsize[0]));
iy = qmax (0, qmin (iy, texsize[1]));
VectorClear (light);
if (face->lightofs < 0)
{
return;
}
for (int k = 0; k < MAXLIGHTMAPS && face->styles[k] != 255; k++)
{
byte *samples = &g_dlightdata[face->lightofs + k * (texsize[0] + 1) * (texsize[1] + 1) * 3];
if (face->styles[k] == 0)
{
VectorAdd (light, &samples[(iy * (texsize[0] + 1) + ix) * 3], light);
}
}
}
static void GetLight (dface_t *face, const int texsize[2], double x, double y, vec3_t &light)
{
int ix, iy;
double dx, dy;
ix = (int)floor (x);
iy = (int)floor (y);
dx = x - ix;
dx = qmax (0, qmin (dx, 1));
dy = y - iy;
dy = qmax (0, qmin (dy, 1));
// do bilinear interpolation
vec3_t light00, light10, light01, light11;
GetLightInt (face, texsize, ix, iy, light00);
GetLightInt (face, texsize, ix + 1, iy, light10);
GetLightInt (face, texsize, ix, iy + 1, light01);
GetLightInt (face, texsize, ix + 1, iy + 1, light11);
vec3_t light0, light1;
VectorScale (light00, 1 - dy, light0);
VectorMA (light0, dy, light01, light0);
VectorScale (light10, 1 - dy, light1);
VectorMA (light1, dy, light11, light1);
VectorScale (light0, 1 - dx, light);
VectorMA (light, dx, light1, light);
}
static bool GetValidTextureName (int miptex, char name[16])
{
int numtextures = g_texdatasize? ((dmiptexlump_t *)g_dtexdata)->nummiptex: 0;
int offset;
int size;
miptex_t *mt;
if (miptex < 0 || miptex >= numtextures)
{
return false;
}
offset = ((dmiptexlump_t *)g_dtexdata)->dataofs[miptex];
size = g_texdatasize - offset;
if (offset < 0 || g_dtexdata + offset < (byte *)&((dmiptexlump_t *)g_dtexdata)->dataofs[numtextures] ||
size < (int)sizeof (miptex_t))
{
return false;
}
mt = (miptex_t *)&g_dtexdata[offset];
safe_strncpy (name, mt->name, 16);
if (strcmp (name, mt->name))
{
return false;
}
if (strlen (name) >= 5 && !strncasecmp (&name[1], "_rad", 4))
{
return false;
}
return true;
}
void EmbedLightmapInTextures ()
{
if (!g_lightdatasize)
{
// hlrad hasn't run
return;
}
if (!g_texdatasize)
{
// texdata hasn't been initialized
return;
}
if (g_notextures)
{
// hlrad didn't load the wad files
return;
}
int i, j, k;
int miplevel;
int count = 0;
int count_bytes = 0;
bool logged = false;
for (i = 0; i < g_numfaces; i++)
{
dface_t *f = &g_dfaces[i];
if (f->lightofs == -1) // some faces don't have lightmap
{
continue;
}
if (f->texinfo < 0 || f->texinfo >= g_numtexinfo)
{
continue;
}
entity_t *ent = g_face_entity[i];
int originaltexinfonum = f->texinfo;
texinfo_t *originaltexinfo = &g_texinfo[originaltexinfonum];
char texname[16];
if (!GetValidTextureName (originaltexinfo->miptex, texname))
{
continue;
}
radtexture_t *tex = &g_textures[originaltexinfo->miptex];
if (ent == &g_entities[0]) // world
{
continue;
}
if (!strncmp (texname, "sky", 3)
|| originaltexinfo->flags & TEX_SPECIAL) // skip special surfaces
{
continue;
}
if (!IntForKey (ent, "zhlt_embedlightmap"))
{
continue;
}
if (!logged)
{
Log ("\n");
Log ("Embed Lightmap : ");
Developer (DEVELOPER_LEVEL_MESSAGE, "\n");
logged = true;
}
bool poweroftwo = DEFAULT_EMBEDLIGHTMAP_POWEROFTWO;
vec_t denominator = DEFAULT_EMBEDLIGHTMAP_DENOMINATOR;
vec_t gamma = DEFAULT_EMBEDLIGHTMAP_GAMMA;
int resolution = DEFAULT_EMBEDLIGHTMAP_RESOLUTION;
if (IntForKey (ent, "zhlt_embedlightmapresolution"))
{
resolution = IntForKey (ent, "zhlt_embedlightmapresolution");
if (resolution <= 0 || resolution > TEXTURE_STEP || ((resolution - 1) & resolution) != 0)
{
Error ("resolution cannot be %d; valid values are 1, 2, 4 ... %d.", resolution, (int)TEXTURE_STEP);
}
}
// calculate texture size and allocate memory for all miplevels
int texturesize[2];
float (*texture)[5]; // red, green, blue and alpha channel; the last one is number of samples
byte (*texturemips[MIPLEVELS])[4]; // red, green, blue and alpha channel
int s, t;
int texmins[2];
int texmaxs[2];
int texsize[2]; // texturesize = (texsize + 1) * TEXTURE_STEP
int side[2];
GetFaceExtents (i, texmins, texmaxs);
texsize[0] = texmaxs[0] - texmins[0];
texsize[1] = texmaxs[1] - texmins[1];
if (texsize[0] < 0 || texsize[1] < 0 || texsize[0] > MAX_SURFACE_EXTENT || texsize[1] > MAX_SURFACE_EXTENT)
{
Warning ("skipped a face with bad surface extents @ (%4.3f %4.3f %4.3f)", g_face_centroids[i][0], g_face_centroids[i][1], g_face_centroids[i][2]);
continue;
}
for (k = 0; k < 2; k++)
{
texturesize[k] = (texsize[k] + 1) * TEXTURE_STEP;
if (texturesize[k] < texsize[k] * TEXTURE_STEP + resolution * 4)
{
texturesize[k] = texsize[k] * TEXTURE_STEP + resolution * 4; // prevent edge bleeding
}
texturesize[k] = (texturesize[k] + resolution - 1) / resolution;
texturesize[k] += 15 - (texturesize[k] + 15) % 16; // must be multiples of 16
if (poweroftwo)
{
for (j = 0; j <= 30; j++)
{
if ((1 << j) >= texturesize[k])
{
texturesize[k] = (1 << j);
break;
}
}
}
side[k] = (texturesize[k] * resolution - texsize[k] * TEXTURE_STEP) / 2;
}
texture = (float (*)[5])malloc (texturesize[0] * texturesize[1] * sizeof (float [5]));
hlassume (texture != NULL, assume_NoMemory);
for (miplevel = 0; miplevel < MIPLEVELS; miplevel++)
{
texturemips[miplevel] = (byte (*)[4])malloc ((texturesize[0] >> miplevel) * (texturesize[1] >> miplevel) * sizeof (byte [4]));
hlassume (texturemips[miplevel] != NULL, assume_NoMemory);
}
// calculate the texture
for (t = 0; t < texturesize[1]; t++)
{
for (s = 0; s < texturesize[0]; s++)
{
float (*dest)[5] = &texture[t * texturesize[0] + s];
VectorFill (*dest, 0);
(*dest)[3] = 0;
(*dest)[4] = 0;
}
}
for (t = -side[1]; t < texsize[1] * TEXTURE_STEP + side[1]; t++)
{
for (s = -side[0]; s < texsize[0] * TEXTURE_STEP + side[0]; s++)
{
double s_vec, t_vec;
double src_s, src_t;
int src_is, src_it;
byte src_index;
byte src_color[3];
double dest_s, dest_t;
int dest_is, dest_it;
float (*dest)[5];
double light_s, light_t;
vec3_t light;
s_vec = s + texmins[0] * TEXTURE_STEP + 0.5;
t_vec = t + texmins[1] * TEXTURE_STEP + 0.5;
if (resolution == 1)
{
dest_s = s_vec;
dest_t = t_vec;
}
else // the final blurred texture is shifted by a half pixel so that lightmap samples align with the center of pixels
{
dest_s = s_vec / resolution + 0.5;
dest_t = t_vec / resolution + 0.5;
}
dest_s = dest_s - texturesize[0] * floor (dest_s / texturesize[0]);
dest_t = dest_t - texturesize[1] * floor (dest_t / texturesize[1]);
dest_is = (int)floor (dest_s); // dest_is = dest_s % texturesize[0]
dest_it = (int)floor (dest_t); // dest_it = dest_t % texturesize[1]
dest_is = qmax (0, qmin (dest_is, texturesize[0] - 1));
dest_it = qmax (0, qmin (dest_it, texturesize[1] - 1));
dest = &texture[dest_it * texturesize[0] + dest_is];
src_s = s_vec;
src_t = t_vec;
src_s = src_s - tex->width * floor (src_s / tex->width);
src_t = src_t - tex->height * floor (src_t / tex->height);
src_is = (int)floor (src_s); // src_is = src_s % tex->width
src_it = (int)floor (src_t); // src_it = src_t % tex->height
src_is = qmax (0, qmin (src_is, tex->width - 1));
src_it = qmax (0, qmin (src_it, tex->height - 1));
src_index = tex->canvas[src_it * tex->width + src_is];
VectorCopy (tex->palette[src_index], src_color);
// get light from the center of the destination pixel
light_s = (s_vec + resolution * (dest_is + 0.5 - dest_s)) / TEXTURE_STEP - texmins[0];
light_t = (t_vec + resolution * (dest_it + 0.5 - dest_t)) / TEXTURE_STEP - texmins[1];
GetLight (f, texsize, light_s, light_t, light);
(*dest)[4] += 1;
if (!(texname[0] == '{' && src_index == 255))
{
for (k = 0; k < 3; k++)
{
float v = src_color[k] * pow (light[k] / denominator, gamma);
(*dest)[k] += 255 * qmax (0, qmin (v, 255));
}
(*dest)[3] += 255;
}
}
}
for (t = 0; t < texturesize[1]; t++)
{
for (s = 0; s < texturesize[0]; s++)
{
float (*src)[5] = &texture[t * texturesize[0] + s];
byte (*dest)[4] = &texturemips[0][t * texturesize[0] + s];
if ((*src)[4] == 0) // no samples (outside face range?)
{
VectorFill (*dest, 0);
(*dest)[3] = 255;
}
else
{
if ((*src)[3] / (*src)[4] <= 0.4 * 255) // transparent
{
VectorFill (*dest, 0);
(*dest)[3] = 0;
}
else // normal
{
for (j = 0; j < 3; j++)
{
int val = (int)floor ((*src)[j] / (*src)[3] + 0.5);
(*dest)[j] = qmax (0, qmin (val, 255));
}
(*dest)[3] = 255;
}
}
}
}
for (miplevel = 1; miplevel < MIPLEVELS; miplevel++)
{
for (t = 0; t < (texturesize[1] >> miplevel); t++)
{
for (s = 0; s < (texturesize[0] >> miplevel); s++)
{
byte (*src[4])[4];
byte (*dest)[4];
double average[4];
dest = &texturemips[miplevel][t * (texturesize[0] >> miplevel) + s];
src[0] = &texturemips[miplevel - 1][(2 * t) * (texturesize[0] >> (miplevel - 1)) + (2 * s)];
src[1] = &texturemips[miplevel - 1][(2 * t) * (texturesize[0] >> (miplevel - 1)) + (2 * s + 1)];
src[2] = &texturemips[miplevel - 1][(2 * t + 1) * (texturesize[0] >> (miplevel - 1)) + (2 * s)];
src[3] = &texturemips[miplevel - 1][(2 * t + 1) * (texturesize[0] >> (miplevel - 1)) + (2 * s + 1)];
VectorClear (average);
average[3] = 0;
for (k = 0; k < 4; k++)
{
for (j = 0; j < 3; j++)
{
average[j] += (*src[k])[3] * (*src[k])[j];
}
average[3] += (*src[k])[3];
}
if (average[3] / 4 <= 0.4 * 255)
{
VectorClear (*dest);
(*dest)[3] = 0;
}
else
{
for (j = 0; j < 3; j++)
{
int val = (int)floor (average[j] / average[3] + 0.5);
(*dest)[j] = qmax (0, qmin (val, 255));
}
(*dest)[3] = 255;
}
}
}
}
// create its palette
byte palette[256][3];
cq_searchnode_t *palettetree = CQ_AllocSearchTree (256);
int paletteoffset;
int palettenumcolors;
{
int palettemaxcolors;
int numsamplepoints;
unsigned char (*samplepoints)[3];
if (texname[0] == '{')
{
paletteoffset = 0;
palettemaxcolors = 255;
VectorCopy (tex->palette[255], palette[255]); // the transparency color
}
/*else if (texname[0] == '!')
{
paletteoffset = 16; // because the 4th entry and the 5th entry are reserved for fog color and fog density
for (j = 0; j < 16; j++)
{
VectorCopy (tex->palette[j], palette[j]);
}
palettemaxcolors = 256 - 16;
}*/
else
{
paletteoffset = 0;
palettemaxcolors = 256;
}
samplepoints = (unsigned char (*)[3])malloc (texturesize[0] * texturesize[1] * sizeof (unsigned char [3]));
hlassume (samplepoints != NULL, assume_NoMemory);
numsamplepoints = 0;
for (t = 0; t < texturesize[1]; t++)
{
for (s = 0; s < texturesize[0]; s++)
{
byte (*src)[4] = &texturemips[0][t * texturesize[0] + s];
if ((*src)[3] > 0)
{
VectorCopy (*src, samplepoints[numsamplepoints]);
numsamplepoints++;
}
}
}
CQ_CreatePalette (numsamplepoints, samplepoints, palettemaxcolors, &palette[paletteoffset], palettenumcolors, palettetree);
for (j = palettenumcolors; j < palettemaxcolors; j++)
{
VectorClear (palette[paletteoffset + j]);
}
free (samplepoints);
}
// emit a texinfo
hlassume (g_numtexinfo < MAX_MAP_TEXINFO, assume_MAX_MAP_TEXINFO);
f->texinfo = g_numtexinfo;
texinfo_t *info = &g_texinfo[g_numtexinfo];
g_numtexinfo++;
*info = g_texinfo[originaltexinfonum];
if (resolution != 1)
{
// apply a scale and a shift over the original vectors
for (k = 0; k < 2; k++)
{
VectorScale (info->vecs[k], 1.0 / resolution, info->vecs[k]);
info->vecs[k][3] = info->vecs[k][3] / resolution + 0.5;
}
}
info->miptex = NewTextures_GetCurrentMiptexIndex ();
// emit a texture
int miptexsize;
miptexsize = (int)sizeof (miptex_t);
for (miplevel = 0; miplevel < MIPLEVELS; miplevel++)
{
miptexsize += (texturesize[0] >> miplevel) * (texturesize[1] >> miplevel);
}
miptexsize += 2 + 256 * 3 + 2;
miptex_t *miptex = (miptex_t *)malloc (miptexsize);
hlassume (miptex != NULL, assume_NoMemory);
memset (miptex, 0, sizeof (miptex_t));
miptex->width = texturesize[0];
miptex->height = texturesize[1];
byte *p = (byte *)miptex + sizeof (miptex_t);
for (miplevel = 0; miplevel < MIPLEVELS; miplevel++)
{
miptex->offsets[miplevel] = p - (byte *)miptex;
for (int t = 0; t < (texturesize[1] >> miplevel); t++)
{
for (int s = 0; s < (texturesize[0] >> miplevel); s++)
{
byte (*src)[4] = &texturemips[miplevel][t * (texturesize[0] >> miplevel) + s];
if ((*src)[3] > 0)
{
if (palettenumcolors)
{
unsigned char point[3];
VectorCopy (*src, point);
*p = paletteoffset + CQ_MapPoint (point, &palette[paletteoffset], palettenumcolors, palettetree);
}
else // this should never happen
{
*p = paletteoffset + 0;
}
}
else
{
*p = 255;
}
p++;
}
}
}
*(short *)p = 256;
p += 2;
memcpy (p, palette, 256 * 3);
p += 256 * 3;
*(short *)p = 0;
p += 2;
if (p != (byte *)miptex + miptexsize)
{
Error ("EmbedLightmapInTextures: internal error");
}
if (texname[0] == '{')
{
strcpy (miptex->name, "{_rad");
}
/*else if (texname[0] == '!')
{
strcpy (miptex->name, "!_rad");
}*/
else
{
strcpy (miptex->name, "__rad");
}
if (originaltexinfonum < 0 || originaltexinfonum > 99999)
{
Error ("EmbedLightmapInTextures: internal error: texinfo out of range");
}
miptex->name[5] = '0' + (originaltexinfonum / 10000) % 10; // store the original texinfo
miptex->name[6] = '0' + (originaltexinfonum / 1000) % 10;
miptex->name[7] = '0' + (originaltexinfonum / 100) % 10;
miptex->name[8] = '0' + (originaltexinfonum / 10) % 10;
miptex->name[9] = '0' + (originaltexinfonum) % 10;
char table[62];
for (int k = 0; k < 62; k++)
{
table[k] = k >= 36? 'a' + (k - 36): k >= 10? 'A' + (k - 10): '0' + k; // same order as the ASCII table
}
miptex->name[10] = '\0';
miptex->name[11] = '\0';
miptex->name[12] = '\0';
miptex->name[13] = '\0';
miptex->name[14] = '\0';
miptex->name[15] = '\0';
unsigned int hash = Hash (miptexsize, miptex);
miptex->name[10] = table[(hash / 62 / 62) % 52 + 10];
miptex->name[11] = table[(hash / 62) % 62];
miptex->name[12] = table[(hash) % 62];
miptex->name[13] = table[(count / 62) % 62];
miptex->name[14] = table[(count) % 62];
miptex->name[15] = '\0';
NewTextures_PushTexture (miptexsize, miptex);
count++;
count_bytes += miptexsize;
Developer (DEVELOPER_LEVEL_MESSAGE, "Created texture '%s' for face (texture %s) at (%4.3f %4.3f %4.3f)\n", miptex->name, texname, g_face_centroids[i][0], g_face_centroids[i][1], g_face_centroids[i][2]);
free (miptex);
CQ_FreeSearchTree (palettetree);
free (texture);
for (miplevel = 0; miplevel < MIPLEVELS; miplevel++)
{
free (texturemips[miplevel]);
}
}
NewTextures_Write (); // update texdata now
if (logged)
{
Log ("added %d texinfos and textures (%d bytes)\n", count, count_bytes);
}
}
#endif
#endif