/* =========================================================================== Copyright (C) 2008 Leszek Godlewski This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "cm_local.h" #include "../client/client.h" byte cmf_dummy_trans_data; cfencemask_t cmf_dummy_trans; /* ==================== CM_LoadFTX ==================== */ qboolean CM_LoadFTX(const char *name, byte **pic, int *width, int *height) { int length; int numPixels; fileHandle_t h; ftx_t header; *pic = NULL; length = FS_FOpenFileRead(name, &h, qtrue, qtrue); if (length <= 0) { return qtrue; } FS_Read(&header, sizeof(ftx_t), h); header.has_alpha = LittleLong(header.has_alpha); header.height = LittleLong(header.height); header.width = LittleLong(header.width); if (!header.has_alpha) { FS_FCloseFile(h); return qfalse; } // Load the FTX data numPixels = header.width * header.height; *height = header.height; *width = header.width; *pic = Hunk_AllocateTempMemory(numPixels * 4); FS_Read(*pic, numPixels * 4, h); FS_FCloseFile(h); return qtrue; } /* ==================== CM_LoadTGA ==================== */ qboolean CM_LoadTGA(const char *name, byte **pic, int *width, int *height) { int columns, rows, numPixels; byte *pixbuf; int row, column; byte *buf_p; byte *buffer; int length; TargaHeader targa_header; byte *targa_rgba; *pic = NULL; // // load the file // length = FS_ReadFile(name, (void **)&buffer); if (!buffer) { return qfalse; } buf_p = buffer; targa_header.id_length = *buf_p++; targa_header.colormap_type = *buf_p++; targa_header.image_type = *buf_p++; targa_header.colormap_index = LittleShort(*((short *)buf_p)); buf_p += 2; targa_header.colormap_length = LittleShort(*((short *)buf_p)); buf_p += 2; targa_header.colormap_size = *buf_p++; targa_header.x_origin = LittleShort(*((short *)buf_p)); buf_p += 2; targa_header.y_origin = LittleShort(*((short *)buf_p)); buf_p += 2; targa_header.width = LittleShort(*((short *)buf_p)); buf_p += 2; targa_header.height = LittleShort(*((short *)buf_p)); buf_p += 2; targa_header.pixel_size = *buf_p++; targa_header.attributes = *buf_p++; if (targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3) { Com_Error(ERR_DROP, "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n"); } if (targa_header.colormap_type != 0) { Com_Error(ERR_DROP, "LoadTGA: colormaps not supported\n"); } if (targa_header.pixel_size != 32) { if (targa_header.pixel_size != 24 && targa_header.image_type != 3) { Com_Error(ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n"); } FS_FreeFile(buffer); return qfalse; } columns = targa_header.width; rows = targa_header.height; numPixels = columns * rows; if (width) { *width = columns; } if (height) { *height = rows; } targa_rgba = Hunk_AllocateTempMemory(numPixels * 4); *pic = targa_rgba; if (targa_header.id_length != 0) { buf_p += targa_header.id_length; // skip TARGA image comment } if (targa_header.image_type == 2 || targa_header.image_type == 3) { // 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 = *buf_p++; green = *buf_p++; red = *buf_p++; *pixbuf++ = red; *pixbuf++ = green; *pixbuf++ = blue; *pixbuf++ = 255; break; case 32: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = *buf_p++; *pixbuf++ = red; *pixbuf++ = green; *pixbuf++ = blue; *pixbuf++ = alphabyte; break; } } } } else if (targa_header.image_type == 10) { // Runlength encoded RGB images unsigned char red, green, blue, alphabyte, packetHeader, packetSize, j; for (row = rows - 1; row >= 0; row--) { pixbuf = targa_rgba + row * columns * 4; for (column = 0; column < columns;) { packetHeader = *buf_p++; packetSize = 1 + (packetHeader & 0x7f); if (packetHeader & 0x80) { // run-length packet switch (targa_header.pixel_size) { case 24: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = 255; break; case 32: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = *buf_p++; break; } 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 = *buf_p++; green = *buf_p++; red = *buf_p++; *pixbuf++ = red; *pixbuf++ = green; *pixbuf++ = blue; *pixbuf++ = 255; break; case 32: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = *buf_p++; *pixbuf++ = red; *pixbuf++ = green; *pixbuf++ = blue; *pixbuf++ = alphabyte; break; } 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:; } } FS_FreeFile(buffer); return qtrue; } /* ==================== CM_LoadFCM ==================== */ qboolean CM_LoadFCM(const char *szName, cfencemask_t **pMask) { unsigned int version; int length; int iDataSize; fileHandle_t h; fcm_t header; cfencemask_t *m; char tempName[256]; *pMask = NULL; COM_StripExtension(szName, tempName, sizeof(tempName)); strcat(tempName, ".fcm"); length = FS_FOpenFileRead(tempName, &h, qtrue, qtrue); if (length <= 0) { return qfalse; } FS_Read(&version, sizeof(int), h); if (LittleLong(version) != FENCEMASK_VERSION) { FS_FCloseFile(h); Com_Printf("CM_LoadFCM: %s is not correct version, skipping\n", tempName); return qfalse; } FS_Read(&header, sizeof(fcm_t), h); iDataSize = LittleLong(header.iWidth) * LittleLong(header.iHeight) + 7; if (iDataSize < 0) { iDataSize = LittleLong(header.iWidth) * LittleLong(header.iHeight) + 14; } iDataSize >>= 3; if (iDataSize != (length - sizeof(fcm_t) - sizeof(unsigned int))) { FS_FCloseFile(h); Com_Printf("CM_LoadFCM: size mismatch in %s, skipping\n", tempName); return qfalse; } m = (cfencemask_t *)Hunk_Alloc(sizeof(cfencemask_t), h_dontcare); *pMask = m; Q_strncpyz(m->name, szName, sizeof(m->name)); m->iWidth = header.iWidth; m->iHeight = header.iHeight; m->pData = (byte *)Hunk_Alloc(iDataSize, h_dontcare); FS_Read(m->pData, iDataSize, h); FS_FCloseFile(h); Com_sprintf(tempName, sizeof(tempName), "f%s", szName); UI_LoadResource(tempName); return qtrue; } /* ==================== CM_SaveFCM ==================== */ void CM_SaveFCM(const char *szName, cfencemask_t **pMask) { fileHandle_t h; unsigned int version; byte *pData; int i, j; int iMaskPos; char endline, solid, clear; char tempName[256]; COM_StripExtension(szName, tempName, sizeof(tempName)); strcat(tempName, ".fcm"); h = FS_FOpenFileWrite(tempName); if (!h) { return; } version = FENCEMASK_VERSION; FS_Write(&version, sizeof(unsigned int), h); FS_Write(&(*pMask)->iWidth, sizeof(int), h); FS_Write(&(*pMask)->iHeight, sizeof(int), h); iMaskPos = (*pMask)->iHeight * (*pMask)->iWidth + 7; if (iMaskPos < 0) { iMaskPos = (*pMask)->iHeight * (*pMask)->iWidth + 7 * 2; } FS_Write((*pMask)->pData, iMaskPos >> 3, h); FS_FCloseFile(h); // save debug info if (developer->integer && cm_FCMdebug->integer) { endline = '\n'; solid = 'X'; clear = '.'; COM_StripExtension(szName, tempName, sizeof(tempName)); strcat(tempName, ".fcm"); h = FS_FOpenFileWrite(tempName); if (!h) { return; } pData = (*pMask)->pData; for (i = 0; i < (*pMask)->iHeight; i++) { for (j = 0; j < (*pMask)->iWidth; j++) { int b1, b2; b1 = (j + (*pMask)->iWidth * i) & 7; b2 = pData[(j + (*pMask)->iWidth * i) >> 3]; if (b2 & (1 << b1)) { FS_Write(&solid, 1, h); } else { FS_Write(&clear, 1, h); } } FS_Write(&endline, 1, h); } FS_FCloseFile(h); } } /* ==================== CM_GenerateDummyFenceMask ==================== */ cfencemask_t *CM_GenerateDummyFenceMask(const char *szName, qboolean bOpaque) { cfencemask_t *pMask; pMask = Hunk_Alloc(sizeof(cfencemask_t) + sizeof(byte), h_dontcare); pMask->iWidth = 2; pMask->iHeight = 2; pMask->pData = (byte *)((char *)pMask + sizeof(cfencemask_t)); if (bOpaque) { *(byte *)pMask->pData = -1; } else { *(byte *)pMask->pData = 0; } return pMask; } /* ==================== CM_GenerateFenceMask ==================== */ qboolean CM_GenerateFenceMask(const char *szName, cfencemask_t **pMask) { int i; int iImageWidth; int iImageHeight; int iImageSize; int iMaskSize; int iWidth; int iHeight; byte *pImage; byte *pCurrImage; byte *pCurrMask; qboolean bHasTrans = qfalse; qboolean bHasOpaque = qfalse; char tempName[256]; *pMask = NULL; COM_StripExtension(szName, tempName, sizeof(tempName)); strcat(tempName, ".ftx"); if (!CM_LoadFTX(tempName, &pImage, &iImageWidth, &iImageHeight)) { return qfalse; } if (!pImage) { if (!CM_LoadTGA(szName, &pImage, &iImageWidth, &iImageHeight)) { return qfalse; } if (!pImage) { return qfalse; } } iWidth = iImageWidth; iHeight = iImageHeight; iImageSize = iImageWidth * iImageHeight; iMaskSize = iImageSize + 7; if (iMaskSize < 0) { iMaskSize = iImageSize + 7 * 2; } pCurrImage = pImage + 3; for (i = 0; i < iImageSize; i++, pCurrImage += sizeof(unsigned int)) { if (*pCurrImage >= 128) { // Treat pixels with > 128 alpha value as opaque bHasOpaque = qtrue; } else { bHasTrans = qtrue; } if (bHasOpaque && bHasTrans) { break; } } // load the mask if it has transparence and is opaque if (bHasOpaque && bHasTrans) { *pMask = Hunk_Alloc((iMaskSize >> 3) + sizeof(cfencemask_t), h_dontcare); Q_strncpyz((*pMask)->name, szName, sizeof((*pMask)->name)); (*pMask)->iWidth = iWidth; (*pMask)->iHeight = iHeight; (*pMask)->pData = (byte *)((byte *)*pMask + sizeof(**pMask)); pCurrMask = (*pMask)->pData; pCurrImage = pImage + 3; for (i = 0; i < iImageSize; i++, pCurrImage += sizeof(unsigned int)) { if (*pCurrImage >= 128) { pCurrMask[i >> 3] |= 1 << (i & 7); } } } else { *pMask = CM_GenerateDummyFenceMask(szName, bHasOpaque); } Hunk_FreeTempMemory(pImage); return qtrue; } /* ==================== CM_CheckFCMFileRefresh ==================== */ qboolean CM_CheckFCMFileRefresh(const char *szFile) { char tempName[256]; COM_StripExtension(szFile, tempName, sizeof(tempName)); strcat(tempName, ".fcm"); return FS_FileNewer(szFile, tempName) > 0; } /* ==================== CM_GetFenceMask ==================== */ cfencemask_t *CM_GetFenceMask(const char *szMaskName) { qboolean save; cfencemask_t *pMask; if (!szMaskName || !*szMaskName) { return NULL; } if (!memcmp(szMaskName, "nomask", 7)) { return NULL; } if (!memcmp(szMaskName, "ignore", 7)) { return &cmf_dummy_trans; } for (pMask = cm.fencemasks; pMask != NULL; pMask = pMask->pNext) { if (!strcmp(szMaskName, pMask->name)) { if (pMask->pData) { return pMask; } else { return NULL; } } } save = qfalse; if (developer->integer) { if (cm_FCMcacheall->integer) { save = qtrue; } else { if (CM_CheckFCMFileRefresh(szMaskName)) { save = qtrue; } } } if (save || !CM_LoadFCM(szMaskName, &pMask)) { if (!CM_GenerateFenceMask(szMaskName, &pMask)) { pMask = (cfencemask_t *)Hunk_Alloc(sizeof(cfencemask_t), h_dontcare); Q_strncpyz(pMask->name, szMaskName, sizeof(pMask->name)); pMask->pData = NULL; pMask->pNext = cm.fencemasks; cm.fencemasks = pMask; return NULL; } } if (save) { CM_SaveFCM(szMaskName, &pMask); } pMask->pNext = cm.fencemasks; cm.fencemasks = pMask; return pMask; } /* ==================== CM_TraceThroughFence ==================== */ qboolean CM_TraceThroughFence(traceWork_t *tw, cbrush_t *brush, cbrushside_t *side, float fTraceFraction) { int i; int iMaskPos; float fS; float fT; float *vNorm; vec3_t vPos; cfencemask_t *pMask; pMask = cm.shaders[side->shaderNum].mask; if (!pMask) { return qtrue; } if (pMask == &cmf_dummy_trans) { return qfalse; } if (!side->pEq) { return qtrue; } for (i = 0; i < 3; i++) { vNorm = side->plane->normal; vPos[i] = tw->start[i] + fTraceFraction * (tw->end[i] - tw->start[i]) - SURFACE_CLIP_EPSILON * vNorm[i]; } fS = DotProduct(vPos, side->pEq->fSeq) + side->pEq->fSeq[3]; fS = fS - floor(fS); fT = DotProduct(vPos, side->pEq->fTeq) + side->pEq->fTeq[3]; fT = fT - floor(fT); if (cm_FCMdebug->integer) { Com_Printf( "Trace ST coords: (%.2f %.2f) or (%i %i)\n", fS, fT, (int)((float)pMask->iWidth * fS), (int)((float)pMask->iHeight * fT) ); } iMaskPos = (int)((float)pMask->iWidth * fS) + pMask->iWidth * (int)((float)pMask->iHeight * fT); return (1 << (iMaskPos & 7)) & pMask->pData[iMaskPos >> 3]; }