#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 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 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 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 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 inline void CQ_VectorClear (T a[CQ_DIM]) { for (int x = 0; x < CQ_DIM; x++) { a[x] = (T)0; } } template 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