/* =========================================================================== Copyright (C) 2023 the OpenMoHAA team 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 =========================================================================== */ // DESCRIPTION: // light style code #include "cg_local.h" /* =================================================================== LIGHT STYLE CODE =================================================================== */ #define MAX_LIGHTSTYLE_LENGTH 128 #define MAX_LIGHTSTYLE_NAME_LENGTH 64 typedef struct { char name[MAX_LIGHTSTYLE_NAME_LENGTH]; qboolean hasalpha; int length; float map[MAX_LIGHTSTYLE_LENGTH][4]; } clightstyle_t; clightstyle_t cg_lightstyle[MAX_LIGHTSTYLES * 2]; /* ================ CG_LightStyleColor ================ */ qboolean CG_LightStyleColor(int style, int realtime, vec4_t color, qboolean clamp) { clightstyle_t *ls; int i, time; float frac; qboolean at_end; time = realtime / 50; frac = (realtime - (time * 50.0f)) / 50.0f; memset(color, 0, sizeof(color)); if ((style < 0) || (style >= (MAX_LIGHTSTYLES * 2))) { cgi.DPrintf("CG_LightStyleColor: style out of range.\n"); return qtrue; } ls = &cg_lightstyle[style]; if (!ls->length) { cgi.DPrintf("CG_LightStyleColor: style %d has zero length.\n", style); return qtrue; } // by default we are not at the end at_end = qfalse; if (clamp) { if (time >= ls->length) { time = ls->length - 1; at_end = qtrue; } } else { time %= ls->length; } // // only lerp if we are before the end // if (time == ls->length - 1) { memcpy(color, ls->map[time], 4 * sizeof(ls->map[time][0])); } else { for (i = 0; i < 4; i++) { color[i] = ls->map[time][i] + frac * (ls->map[time + 1][i] - ls->map[time][i]); } } if (!ls->hasalpha) { color[3] = 1; } return at_end; } /* ============= LoadTGA ============= */ static qboolean LoadTGA(const char *name, byte **pic, int *width, int *height, qboolean *hasalpha) { int columns, rows, numPixels; byte *pixbuf; int row, column; byte *buf_p; byte *buffer; TargaHeader targa_header; byte *targa_rgba; byte targa_palette[256 * 4]; int i; *pic = NULL; *hasalpha = false; // // load the file // cgi.FS_ReadFile(name, (void **)&buffer, qtrue); if (!buffer) { cgi.DPrintf("TGA File not found: %s\n", name); return false; } 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++ / 8; 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 != 1 && targa_header.image_type != 2 && targa_header.image_type != 10) { cgi.DPrintf("LoadTGA: Only type 1, 2 and 10 targa RGB images supported\n"); return false; } if ((targa_header.colormap_type != 0 && targa_header.colormap_type != 1) || (targa_header.pixel_size != 32 && targa_header.pixel_size != 24 && targa_header.pixel_size != 8)) { cgi.DPrintf("LoadTGA: Only 24, 32 and colormap images supported (no colormaps)\n"); return false; } columns = targa_header.width; rows = targa_header.height; numPixels = columns * rows; if (width) { *width = columns; } if (height) { *height = rows; } targa_rgba = (byte *)cgi.Malloc(numPixels * 4); *pic = targa_rgba; if (targa_header.id_length != 0) { buf_p += targa_header.id_length; // skip TARGA image comment } for (i = 0; i < targa_header.colormap_length; i++) { byte blue = *buf_p++; byte green = *buf_p++; byte red = *buf_p++; byte alpha = 0xff; if (targa_header.colormap_size == 4) { alpha = *buf_p++; } targa_palette[i * 4 + 0] = red; targa_palette[i * 4 + 1] = green; targa_palette[i * 4 + 2] = blue; targa_palette[i * 4 + 3] = alpha; if (alpha != 0xff) { *hasalpha = true; } } if (targa_header.image_type == 1) { for (row = rows - 1; row >= 0; row--) { pixbuf = targa_rgba + row * columns; for (column = 0; column < columns; column++) { switch (targa_header.pixel_size) { case 8: *pixbuf++ = targa_palette[*buf_p * 4 + 0]; *pixbuf++ = targa_palette[*buf_p * 4 + 1]; *pixbuf++ = targa_palette[*buf_p * 4 + 2]; *pixbuf++ = targa_palette[*buf_p * 4 + 3]; buf_p++; break; } } } } else 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 = *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; if (alphabyte != 0xff) { *hasalpha = true; } 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 32: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = *buf_p++; if (alphabyte != 0xff) { *hasalpha = true; } break; case 24: default: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = 255; 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) { default: 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; if (alphabyte != 0xff) { *hasalpha = true; } 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:; } } cgi.FS_FreeFile(buffer); return true; } void createLightstyleFromTGA(int num, const char *filename) { byte *data = 0; int i, j, width, height; qboolean hasalpha; if (!LoadTGA(filename, &data, &width, &height, &hasalpha)) { return; } if (width > MAX_LIGHTSTYLE_LENGTH) { cgi.DPrintf("createLightstyleFromTGA: Light style too large, truncating.\n", filename); width = MAX_LIGHTSTYLE_LENGTH; } strncpy(cg_lightstyle[num].name, filename, MAX_LIGHTSTYLE_NAME_LENGTH); cg_lightstyle[num].hasalpha = hasalpha; cg_lightstyle[num].length = width; for (i = 0; i < width; i++) { for (j = 0; j < 4; j++) { cg_lightstyle[num].map[i][j] = (float)data[(i * 4) + j] / 255.0f; } } if (data) { cgi.Free(data); } } void CG_SetLightStyle(int num, const char *s) { const char *r, *g, *b, *a; int j, k, n; size_t len; float t; if ((num < 0) || (num >= (MAX_LIGHTSTYLES * 2))) { cgi.DPrintf("CG_SetLightStyle: num out of range.\n"); return; } len = strlen(s); if (!Q_stricmp(&s[len - 4], ".tga")) { createLightstyleFromTGA(num, s); return; } if (!s) { return; } r = strchr(s, 'R'); g = strchr(s, 'G'); b = strchr(s, 'B'); a = strchr(s, 'A'); n = 0; if (r) { n++; } if (g) { n++; } if (b) { n++; } if (a) { n++; } if (!n) { n++; } if (!a) { cg_lightstyle[num].hasalpha = false; } else { cg_lightstyle[num].hasalpha = true; } j = strlen(s) / n; if (j >= MAX_LIGHTSTYLE_LENGTH) { cgi.DPrintf("svc_lightstyle length=%i", j); j = MAX_LIGHTSTYLE_LENGTH - 1; } if (!r && !g && !b && !a) { cg_lightstyle[num].length = j; for (k = 0; k < j; k++) { t = (float)(s[k] - 'a') * 127.5f / 12.5; if (t > 255) { t = 255.0f; } cg_lightstyle[num].map[k][0] = t / 255.0f; cg_lightstyle[num].map[k][1] = t / 255.0f; cg_lightstyle[num].map[k][2] = t / 255.0f; cg_lightstyle[num].map[k][3] = 1; } } else { n = 0; // red k = 0; t = 0; if (r) { r++; while ((r[k] >= 'a') && (r[k] <= 'z')) { t = (float)(r[k] - 'a') * 127.5f / 12.5; if (t > 255) { t = 255.0f; } cg_lightstyle[num].map[k][0] = t / 255.0f; k++; } n = k; } while (k < MAX_LIGHTSTYLE_LENGTH) { cg_lightstyle[num].map[k++][0] = t / 255.0f; } // green k = 0; t = 0; if (g) { g++; while ((g[k] >= 'a') && (g[k] <= 'z')) { t = (float)(g[k] - 'a') * 127.5f / 12.5; if (t > 255) { t = 255.0f; } cg_lightstyle[num].map[k][1] = t / 255.0f; k++; } n = Q_max(n, k); } while (k < MAX_LIGHTSTYLE_LENGTH) { cg_lightstyle[num].map[k++][1] = t / 255.0f; } // blue k = 0; t = 0; if (b) { b++; while ((b[k] >= 'a') && (b[k] <= 'z')) { t = (float)(b[k] - 'a') * 127.5f / 12.5; if (t > 255) { t = 255.0f; } cg_lightstyle[num].map[k][2] = t / 255.0f; k++; } n = Q_max(n, k); } while (k < MAX_LIGHTSTYLE_LENGTH) { cg_lightstyle[num].map[k++][2] = t / 255.0f; } // alpha k = 0; t = 255; if (a) { a++; while ((a[k] >= 'a') && (a[k] <= 'z')) { t = (float)(a[k] - 'a') * 127.5f / 25; if (t > 255) { t = 255.0f; } cg_lightstyle[num].map[k][3] = t / 255.0f; k++; } n = Q_max(n, k); } while (k < MAX_LIGHTSTYLE_LENGTH) { cg_lightstyle[num].map[k++][3] = t / 255.0f; } cg_lightstyle[num].length = n; } } /* ================ CG_GetLightStyle ================ */ int CG_GetLightStyle(const char *name) { int i; for (i = MAX_LIGHTSTYLES; i < MAX_LIGHTSTYLES * 2; i++) { if (!Q_stricmp(cg_lightstyle[i].name, name)) { return i; } } return -1; } /* ================ CG_GetFreeLightStyle ================ */ int CG_GetFreeLightStyle(void) { int i; for (i = MAX_LIGHTSTYLES; i < MAX_LIGHTSTYLES * 2; i++) { if (!cg_lightstyle[i].name[0]) { return i; } } return -1; } /* ================ CG_ClearLightStyles ================ */ void CG_ClearLightStyles(void) { memset(cg_lightstyle, 0, sizeof(cg_lightstyle)); } /* ================ CG_RegisterLightStyle ================ */ int CG_RegisterLightStyle(const char *name) { int num; num = CG_GetLightStyle(name); if (num == -1) { num = CG_GetFreeLightStyle(); if (num != -1) { CG_SetLightStyle(num, name); } else { cgi.DPrintf("CG_RegisterLightStyle: no free spots for lightstyle %s.\n"); } } return num; }