summaryrefslogtreecommitdiff
path: root/src/renderergl2/tr_bsp.cpp
diff options
context:
space:
mode:
authorIronClawTrem <louie.nutman@gmail.com>2020-02-16 03:40:06 +0000
committerIronClawTrem <louie.nutman@gmail.com>2020-02-16 03:40:06 +0000
commit425decdf7e9284d15aa726e3ae96b9942fb0e3ea (patch)
tree6c0dd7edfefff1be7b9e75fe0b3a0a85fe1595f3 /src/renderergl2/tr_bsp.cpp
parentccb0b2e4d6674a7a00c9bf491f08fc73b6898c54 (diff)
create tremded branch
Diffstat (limited to 'src/renderergl2/tr_bsp.cpp')
-rw-r--r--src/renderergl2/tr_bsp.cpp3046
1 files changed, 3046 insertions, 0 deletions
diff --git a/src/renderergl2/tr_bsp.cpp b/src/renderergl2/tr_bsp.cpp
new file mode 100644
index 0000000..3bf74b5
--- /dev/null
+++ b/src/renderergl2/tr_bsp.cpp
@@ -0,0 +1,3046 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_map.c
+
+#include "tr_local.h"
+
+#define JSON_IMPLEMENTATION
+#include "qcommon/json.h"
+#undef JSON_IMPLEMENTATION
+
+/*
+
+Loads and prepares a map file for scene rendering.
+
+A single entry point:
+
+void RE_LoadWorldMap( const char *name );
+
+*/
+
+static world_t s_worldData;
+static byte *fileBase;
+
+int c_subdivisions;
+int c_gridVerts;
+
+//===============================================================================
+
+static void HSVtoRGB( float h, float s, float v, float rgb[3] )
+{
+ int i;
+ float f;
+ float p, q, t;
+
+ h *= 5;
+
+ i = floor( h );
+ f = h - i;
+
+ p = v * ( 1 - s );
+ q = v * ( 1 - s * f );
+ t = v * ( 1 - s * ( 1 - f ) );
+
+ switch ( i )
+ {
+ case 0:
+ rgb[0] = v;
+ rgb[1] = t;
+ rgb[2] = p;
+ break;
+ case 1:
+ rgb[0] = q;
+ rgb[1] = v;
+ rgb[2] = p;
+ break;
+ case 2:
+ rgb[0] = p;
+ rgb[1] = v;
+ rgb[2] = t;
+ break;
+ case 3:
+ rgb[0] = p;
+ rgb[1] = q;
+ rgb[2] = v;
+ break;
+ case 4:
+ rgb[0] = t;
+ rgb[1] = p;
+ rgb[2] = v;
+ break;
+ case 5:
+ rgb[0] = v;
+ rgb[1] = p;
+ rgb[2] = q;
+ break;
+ }
+}
+
+/*
+===============
+R_ColorShiftLightingBytes
+
+===============
+*/
+static void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) {
+ int shift, r, g, b;
+
+ // shift the color data based on overbright range
+ shift = r_mapOverBrightBits->integer - tr.overbrightBits;
+
+ // shift the data based on overbright range
+ r = in[0] << shift;
+ g = in[1] << shift;
+ b = in[2] << shift;
+
+ // Minimum values
+ if(r < r_mapLightmapMin->integer){
+ r = r_mapLightmapMin->integer;
+ }
+ if(g < r_mapLightmapMin->integer){
+ g = r_mapLightmapMin->integer;
+ }
+ if(b < r_mapLightmapMin->integer){
+ b = r_mapLightmapMin->integer;
+ }
+
+ // normalize by color instead of saturating to white
+ if ( ( r | g | b ) > 255 ) {
+ int max;
+
+ max = r > g ? r : g;
+ max = max > b ? max : b;
+ r = r * 255 / max;
+ g = g * 255 / max;
+ b = b * 255 / max;
+ }
+
+ out[0] = r;
+ out[1] = g;
+ out[2] = b;
+ out[3] = in[3];
+}
+
+
+/*
+===============
+R_ColorShiftLightingFloats
+
+===============
+*/
+static void R_ColorShiftLightingFloats(float in[4], float out[4])
+{
+ float r, g, b;
+ float scale = (1 << (r_mapOverBrightBits->integer - tr.overbrightBits)) / 255.0f;
+
+ r = in[0] * scale;
+ g = in[1] * scale;
+ b = in[2] * scale;
+
+ // Minimum values
+ if(r < r_mapLightmapMin->value / 255.0f){
+ r = r_mapLightmapMin->value / 255.0f;
+ }
+ if(g < r_mapLightmapMin->value / 255.0f){
+ g = r_mapLightmapMin->value / 255.0f;
+ }
+ if(b < r_mapLightmapMin->value / 255.0f){
+ b = r_mapLightmapMin->value / 255.0f;
+ }
+
+ // normalize by color instead of saturating to white
+ if ( r > 1 || g > 1 || b > 1 ) {
+ float max;
+
+ max = r > g ? r : g;
+ max = max > b ? max : b;
+ r = r / max;
+ g = g / max;
+ b = b / max;
+ }
+
+ out[0] = r;
+ out[1] = g;
+ out[2] = b;
+ out[3] = in[3];
+}
+
+// Modified from http://graphicrants.blogspot.jp/2009/04/rgbm-color-encoding.html
+void ColorToRGBM(const vec3_t color, unsigned char rgbm[4])
+{
+ vec3_t sample;
+ float maxComponent;
+
+ VectorCopy(color, sample);
+
+ maxComponent = MAX(sample[0], sample[1]);
+ maxComponent = MAX(maxComponent, sample[2]);
+ maxComponent = CLAMP(maxComponent, 1.0f/255.0f, 1.0f);
+
+ rgbm[3] = (unsigned char) ceil(maxComponent * 255.0f);
+ maxComponent = 255.0f / rgbm[3];
+
+ VectorScale(sample, maxComponent, sample);
+
+ rgbm[0] = (unsigned char) (sample[0] * 255);
+ rgbm[1] = (unsigned char) (sample[1] * 255);
+ rgbm[2] = (unsigned char) (sample[2] * 255);
+}
+
+void ColorToRGB16(const vec3_t color, uint16_t rgb16[3])
+{
+ rgb16[0] = color[0] * 65535.0f + 0.5f;
+ rgb16[1] = color[1] * 65535.0f + 0.5f;
+ rgb16[2] = color[2] * 65535.0f + 0.5f;
+}
+
+
+/*
+===============
+R_LoadLightmaps
+
+===============
+*/
+#define DEFAULT_LIGHTMAP_SIZE 128
+static void R_LoadLightmaps( lump_t *l, lump_t *surfs ) {
+ int/*imgFlags_t*/ imgFlags = IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE;
+ byte *buf, *buf_p;
+ dsurface_t *surf;
+ int len;
+ byte *image;
+ int i, j, numLightmaps, textureInternalFormat = 0;
+ int numLightmapsPerPage = 16;
+ float maxIntensity = 0;
+ double sumIntensity = 0;
+
+ len = l->filelen;
+ if ( !len ) {
+ return;
+ }
+ buf = fileBase + l->fileofs;
+
+ // we are about to upload textures
+ R_IssuePendingRenderCommands();
+
+ tr.lightmapSize = DEFAULT_LIGHTMAP_SIZE;
+ numLightmaps = len / (tr.lightmapSize * tr.lightmapSize * 3);
+
+ // check for deluxe mapping
+ if (numLightmaps <= 1)
+ {
+ tr.worldDeluxeMapping = false;
+ }
+ else
+ {
+ tr.worldDeluxeMapping = true;
+ for( i = 0, surf = (dsurface_t *)(fileBase + surfs->fileofs);
+ i < surfs->filelen / sizeof(dsurface_t); i++, surf++ ) {
+ int lightmapNum = LittleLong( surf->lightmapNum );
+
+ if ( lightmapNum >= 0 && (lightmapNum & 1) != 0 ) {
+ tr.worldDeluxeMapping = false;
+ break;
+ }
+ }
+ }
+
+ image = (byte*)ri.Malloc(tr.lightmapSize * tr.lightmapSize * 4 * 2);
+
+ if (tr.worldDeluxeMapping)
+ numLightmaps >>= 1;
+
+ // Use fat lightmaps of an appropriate size.
+ if (r_mergeLightmaps->integer)
+ {
+ int maxLightmapsPerAxis = glConfig.maxTextureSize / tr.lightmapSize;
+ int lightmapCols = 4, lightmapRows = 4;
+
+ // Increase width at first, then height.
+ while (lightmapCols * lightmapRows < numLightmaps && lightmapCols != maxLightmapsPerAxis)
+ lightmapCols <<= 1;
+
+ while (lightmapCols * lightmapRows < numLightmaps && lightmapRows != maxLightmapsPerAxis)
+ lightmapRows <<= 1;
+
+ tr.fatLightmapCols = lightmapCols;
+ tr.fatLightmapRows = lightmapRows;
+ numLightmapsPerPage = lightmapCols * lightmapRows;
+
+ tr.numLightmaps = (numLightmaps + (numLightmapsPerPage - 1)) / numLightmapsPerPage;
+ }
+ else
+ {
+ tr.numLightmaps = numLightmaps;
+ }
+
+ tr.lightmaps = (image_t**)ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low );
+
+ if (tr.worldDeluxeMapping)
+ tr.deluxemaps = (image_t**)ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low );
+
+ textureInternalFormat = GL_RGBA8;
+ if (r_hdr->integer)
+ {
+ // Check for the first hdr lightmap, if it exists, use GL_RGBA16 for textures.
+ char filename[MAX_QPATH];
+
+ Com_sprintf(filename, sizeof(filename), "maps/%s/lm_0000.hdr", s_worldData.baseName);
+ if (ri.FS_FileExists(filename))
+ textureInternalFormat = GL_RGBA16;
+ }
+
+ if (r_mergeLightmaps->integer)
+ {
+ int width = tr.fatLightmapCols * tr.lightmapSize;
+ int height = tr.fatLightmapRows * tr.lightmapSize;
+
+ for (i = 0; i < tr.numLightmaps; i++)
+ {
+ tr.lightmaps[i] = R_CreateImage(va("_fatlightmap%d", i), NULL, width, height, IMGTYPE_COLORALPHA, imgFlags, textureInternalFormat);
+
+ if (tr.worldDeluxeMapping)
+ tr.deluxemaps[i] = R_CreateImage(va("_fatdeluxemap%d", i), NULL, width, height, IMGTYPE_DELUXE, imgFlags, 0);
+ }
+ }
+
+ for(i = 0; i < numLightmaps; i++)
+ {
+ int xoff = 0, yoff = 0;
+ int lightmapnum = i;
+ // expand the 24 bit on-disk to 32 bit
+
+ if (r_mergeLightmaps->integer)
+ {
+ int lightmaponpage = i % numLightmapsPerPage;
+ xoff = (lightmaponpage % tr.fatLightmapCols) * tr.lightmapSize;
+ yoff = (lightmaponpage / tr.fatLightmapCols) * tr.lightmapSize;
+
+ lightmapnum /= numLightmapsPerPage;
+ }
+
+ // if (tr.worldLightmapping)
+ {
+ char filename[MAX_QPATH];
+ byte *hdrLightmap = NULL;
+ int size = 0;
+
+ // look for hdr lightmaps
+ if (textureInternalFormat == GL_RGBA16)
+ {
+ Com_sprintf( filename, sizeof( filename ), "maps/%s/lm_%04d.hdr", s_worldData.baseName, i * (tr.worldDeluxeMapping ? 2 : 1) );
+ //ri.Printf(PRINT_ALL, "looking for %s\n", filename);
+
+ size = ri.FS_ReadFile(filename, (void **)&hdrLightmap);
+ }
+
+ if (hdrLightmap)
+ {
+ byte *p = hdrLightmap, *end = hdrLightmap + size;
+ //ri.Printf(PRINT_ALL, "found!\n");
+
+ /* FIXME: don't just skip over this header and actually parse it */
+ while (p < end && !(*p == '\n' && *(p+1) == '\n'))
+ p++;
+
+ p += 2;
+
+ while (p < end && !(*p == '\n'))
+ p++;
+
+ p++;
+
+ if (p >= end)
+ ri.Error(ERR_DROP, "Bad header for %s!", filename);
+
+ buf_p = p;
+
+#if 0 // HDRFILE_RGBE
+ if ((int)(end - hdrLightmap) != tr.lightmapSize * tr.lightmapSize * 4)
+ ri.Error(ERR_DROP, "Bad size for %s (%i)!", filename, size);
+#else // HDRFILE_FLOAT
+ if ((int)(end - hdrLightmap) != tr.lightmapSize * tr.lightmapSize * 12)
+ ri.Error(ERR_DROP, "Bad size for %s (%i)!", filename, size);
+#endif
+ }
+ else
+ {
+ int imgOffset = tr.worldDeluxeMapping ? i * 2 : i;
+ buf_p = buf + imgOffset * tr.lightmapSize * tr.lightmapSize * 3;
+ }
+
+ for ( j = 0 ; j < tr.lightmapSize * tr.lightmapSize; j++ )
+ {
+ if (hdrLightmap)
+ {
+ vec4_t color;
+
+#if 0 // HDRFILE_RGBE
+ float exponent = exp2(buf_p[j*4+3] - 128);
+
+ color[0] = buf_p[j*4+0] * exponent;
+ color[1] = buf_p[j*4+1] * exponent;
+ color[2] = buf_p[j*4+2] * exponent;
+#else // HDRFILE_FLOAT
+ memcpy(color, &buf_p[j*12], 12);
+
+ color[0] = LittleFloat(color[0]);
+ color[1] = LittleFloat(color[1]);
+ color[2] = LittleFloat(color[2]);
+#endif
+ color[3] = 1.0f;
+
+ R_ColorShiftLightingFloats(color, color);
+
+ ColorToRGB16(color, (uint16_t *)(&image[j * 8]));
+ ((uint16_t *)(&image[j * 8]))[3] = 65535;
+ }
+ else if (textureInternalFormat == GL_RGBA16)
+ {
+ vec4_t color;
+
+ //hack: convert LDR lightmap to HDR one
+ color[0] = MAX(buf_p[j*3+0], 0.499f);
+ color[1] = MAX(buf_p[j*3+1], 0.499f);
+ color[2] = MAX(buf_p[j*3+2], 0.499f);
+
+ // if under an arbitrary value (say 12) grey it out
+ // this prevents weird splotches in dimly lit areas
+ if (color[0] + color[1] + color[2] < 12.0f)
+ {
+ float avg = (color[0] + color[1] + color[2]) * 0.3333f;
+ color[0] = avg;
+ color[1] = avg;
+ color[2] = avg;
+ }
+ color[3] = 1.0f;
+
+ R_ColorShiftLightingFloats(color, color);
+
+ ColorToRGB16(color, (uint16_t *)(&image[j * 8]));
+ ((uint16_t *)(&image[j * 8]))[3] = 65535;
+ }
+ else
+ {
+ if ( r_lightmap->integer == 2 )
+ { // color code by intensity as development tool (FIXME: check range)
+ float r = buf_p[j*3+0];
+ float g = buf_p[j*3+1];
+ float b = buf_p[j*3+2];
+ float intensity;
+ float out[3] = {0.0, 0.0, 0.0};
+
+ intensity = 0.33f * r + 0.685f * g + 0.063f * b;
+
+ if ( intensity > 255 )
+ intensity = 1.0f;
+ else
+ intensity /= 255.0f;
+
+ if ( intensity > maxIntensity )
+ maxIntensity = intensity;
+
+ HSVtoRGB( intensity, 1.00, 0.50, out );
+
+ image[j*4+0] = out[0] * 255;
+ image[j*4+1] = out[1] * 255;
+ image[j*4+2] = out[2] * 255;
+ image[j*4+3] = 255;
+
+ sumIntensity += intensity;
+ }
+ else
+ {
+ R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] );
+ image[j*4+3] = 255;
+ }
+ }
+ }
+
+ if (r_mergeLightmaps->integer)
+ R_UpdateSubImage(tr.lightmaps[lightmapnum], image, xoff, yoff, tr.lightmapSize, tr.lightmapSize, textureInternalFormat);
+ else
+ tr.lightmaps[i] = R_CreateImage(va("*lightmap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_COLORALPHA, imgFlags, textureInternalFormat );
+
+ if (hdrLightmap)
+ ri.FS_FreeFile(hdrLightmap);
+ }
+
+ if (tr.worldDeluxeMapping)
+ {
+ buf_p = buf + (i * 2 + 1) * tr.lightmapSize * tr.lightmapSize * 3;
+
+ for ( j = 0 ; j < tr.lightmapSize * tr.lightmapSize; j++ ) {
+ image[j*4+0] = buf_p[j*3+0];
+ image[j*4+1] = buf_p[j*3+1];
+ image[j*4+2] = buf_p[j*3+2];
+
+ // make 0,0,0 into 127,127,127
+ if ((image[j*4+0] == 0) && (image[j*4+1] == 0) && (image[j*4+2] == 0))
+ {
+ image[j*4+0] =
+ image[j*4+1] =
+ image[j*4+2] = 127;
+ }
+
+ image[j*4+3] = 255;
+ }
+
+ if (r_mergeLightmaps->integer)
+ R_UpdateSubImage(tr.deluxemaps[lightmapnum], image, xoff, yoff, tr.lightmapSize, tr.lightmapSize, GL_RGBA8 );
+ else
+ tr.deluxemaps[i] = R_CreateImage(va("*deluxemap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_DELUXE, imgFlags, 0 );
+ }
+ }
+
+ if ( r_lightmap->integer == 2 ) {
+ ri.Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) );
+ }
+
+ ri.Free(image);
+}
+
+
+static float FatPackU(float input, int lightmapnum)
+{
+ if (lightmapnum < 0)
+ return input;
+
+ if (tr.worldDeluxeMapping)
+ lightmapnum >>= 1;
+
+ if (tr.fatLightmapCols > 0)
+ {
+ lightmapnum %= (tr.fatLightmapCols * tr.fatLightmapRows);
+ return (input + (lightmapnum % tr.fatLightmapCols)) / (float)(tr.fatLightmapCols);
+ }
+
+ return input;
+}
+
+static float FatPackV(float input, int lightmapnum)
+{
+ if (lightmapnum < 0)
+ return input;
+
+ if (tr.worldDeluxeMapping)
+ lightmapnum >>= 1;
+
+ if (tr.fatLightmapCols > 0)
+ {
+ lightmapnum %= (tr.fatLightmapCols * tr.fatLightmapRows);
+ return (input + (lightmapnum / tr.fatLightmapCols)) / (float)(tr.fatLightmapRows);
+ }
+
+ return input;
+}
+
+
+static int FatLightmap(int lightmapnum)
+{
+ if (lightmapnum < 0)
+ return lightmapnum;
+
+ if (tr.worldDeluxeMapping)
+ lightmapnum >>= 1;
+
+ if (tr.fatLightmapCols > 0)
+ return lightmapnum / (tr.fatLightmapCols * tr.fatLightmapRows);
+
+ return lightmapnum;
+}
+
+/*
+=================
+RE_SetWorldVisData
+
+This is called by the clipmodel subsystem so we can share the 1.8 megs of
+space in big maps...
+=================
+*/
+void RE_SetWorldVisData( const byte *vis ) {
+ tr.externalVisData = vis;
+}
+
+
+/*
+=================
+R_LoadVisibility
+=================
+*/
+static void R_LoadVisibility( lump_t *l ) {
+ int len;
+ byte *buf;
+
+ len = l->filelen;
+ if ( !len ) {
+ return;
+ }
+ buf = fileBase + l->fileofs;
+
+ s_worldData.numClusters = LittleLong( ((int *)buf)[0] );
+ s_worldData.clusterBytes = LittleLong( ((int *)buf)[1] );
+
+ // CM_Load should have given us the vis data to share, so
+ // we don't need to allocate another copy
+ if ( tr.externalVisData ) {
+ s_worldData.vis = tr.externalVisData;
+ } else {
+ byte *dest = (byte*)ri.Hunk_Alloc( len - 8, h_low );
+ Com_Memcpy( dest, buf + 8, len - 8 );
+ s_worldData.vis = dest;
+ }
+}
+
+//===============================================================================
+
+
+/*
+===============
+ShaderForShaderNum
+===============
+*/
+static shader_t *ShaderForShaderNum( int shaderNum, int lightmapNum ) {
+ shader_t *shader;
+ dshader_t *dsh;
+
+ int _shaderNum = LittleLong( shaderNum );
+ if ( _shaderNum < 0 || _shaderNum >= s_worldData.numShaders ) {
+ ri.Error( ERR_DROP, "ShaderForShaderNum: bad num %i", _shaderNum );
+ }
+ dsh = &s_worldData.shaders[ _shaderNum ];
+
+ if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) {
+ lightmapNum = LIGHTMAP_BY_VERTEX;
+ }
+
+ if ( r_fullbright->integer ) {
+ lightmapNum = LIGHTMAP_WHITEIMAGE;
+ }
+
+ shader = R_FindShader( dsh->shader, lightmapNum, true );
+
+ // if the shader had errors, just use default shader
+ if ( shader->defaultShader ) {
+ return tr.defaultShader;
+ }
+
+ return shader;
+}
+
+void LoadDrawVertToSrfVert(srfVert_t *s, drawVert_t *d, int realLightmapNum, float hdrVertColors[3], vec3_t *bounds)
+{
+ vec4_t v;
+
+ s->xyz[0] = LittleFloat(d->xyz[0]);
+ s->xyz[1] = LittleFloat(d->xyz[1]);
+ s->xyz[2] = LittleFloat(d->xyz[2]);
+
+ if (bounds)
+ AddPointToBounds(s->xyz, bounds[0], bounds[1]);
+
+ s->st[0] = LittleFloat(d->st[0]);
+ s->st[1] = LittleFloat(d->st[1]);
+
+ if (realLightmapNum >= 0)
+ {
+ s->lightmap[0] = FatPackU(LittleFloat(d->lightmap[0]), realLightmapNum);
+ s->lightmap[1] = FatPackV(LittleFloat(d->lightmap[1]), realLightmapNum);
+ }
+ else
+ {
+ s->lightmap[0] = LittleFloat(d->lightmap[0]);
+ s->lightmap[1] = LittleFloat(d->lightmap[1]);
+ }
+
+ v[0] = LittleFloat(d->normal[0]);
+ v[1] = LittleFloat(d->normal[1]);
+ v[2] = LittleFloat(d->normal[2]);
+
+ R_VaoPackNormal(s->normal, v);
+
+ if (hdrVertColors)
+ {
+ v[0] = hdrVertColors[0];
+ v[1] = hdrVertColors[1];
+ v[2] = hdrVertColors[2];
+ }
+ else
+ {
+ //hack: convert LDR vertex colors to HDR
+ if (r_hdr->integer)
+ {
+ v[0] = MAX(d->color[0], 0.499f);
+ v[1] = MAX(d->color[1], 0.499f);
+ v[2] = MAX(d->color[2], 0.499f);
+ }
+ else
+ {
+ v[0] = d->color[0];
+ v[1] = d->color[1];
+ v[2] = d->color[2];
+ }
+
+ }
+ v[3] = d->color[3] / 255.0f;
+
+ R_ColorShiftLightingFloats(v, v);
+ R_VaoPackColor(s->color, v);
+}
+
+
+/*
+===============
+ParseFace
+===============
+*/
+static void ParseFace( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes ) {
+ int i, j;
+ srfBspSurface_t *cv;
+ glIndex_t *tri;
+ int numVerts, numIndexes, badTriangles;
+ int realLightmapNum;
+
+ realLightmapNum = LittleLong( ds->lightmapNum );
+
+ // get fog volume
+ surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+ // get shader value
+ surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) );
+ if ( r_singleShader->integer && !surf->shader->isSky ) {
+ surf->shader = tr.defaultShader;
+ }
+
+ numVerts = LittleLong(ds->numVerts);
+ if (numVerts > MAX_FACE_POINTS) {
+ ri.Printf( PRINT_WARNING, "WARNING: MAX_FACE_POINTS exceeded: %i\n", numVerts);
+ numVerts = MAX_FACE_POINTS;
+ surf->shader = tr.defaultShader;
+ }
+
+ numIndexes = LittleLong(ds->numIndexes);
+
+ //cv = ri.Hunk_Alloc(sizeof(*cv), h_low);
+ cv = (srfBspSurface_t*)surf->data;
+ cv->surfaceType = SF_FACE;
+
+ cv->numIndexes = numIndexes;
+ cv->indexes = (glIndex_t*)ri.Hunk_Alloc(numIndexes * sizeof(cv->indexes[0]), h_low);
+
+ cv->numVerts = numVerts;
+ cv->verts = (srfVert_t*)ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low);
+
+ // copy vertexes
+ surf->cullinfo.type = CULLINFO_PLANE | CULLINFO_BOX;
+ ClearBounds(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]);
+ verts += LittleLong(ds->firstVert);
+ for(i = 0; i < numVerts; i++)
+ LoadDrawVertToSrfVert(&cv->verts[i], &verts[i], realLightmapNum, hdrVertColors ? hdrVertColors + (ds->firstVert + i) * 3 : NULL, surf->cullinfo.bounds);
+
+ // copy triangles
+ badTriangles = 0;
+ indexes += LittleLong(ds->firstIndex);
+ for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3)
+ {
+ for(j = 0; j < 3; j++)
+ {
+ tri[j] = LittleLong(indexes[i + j]);
+
+ if(tri[j] >= numVerts)
+ {
+ ri.Error(ERR_DROP, "Bad index in face surface");
+ }
+ }
+
+ if ((tri[0] == tri[1]) || (tri[1] == tri[2]) || (tri[0] == tri[2]))
+ {
+ tri -= 3;
+ badTriangles++;
+ }
+ }
+
+ if (badTriangles)
+ {
+ ri.Printf(PRINT_WARNING, "Face has bad triangles, originally shader %s %d tris %d verts, now %d tris\n", surf->shader->name, numIndexes / 3, numVerts, numIndexes / 3 - badTriangles);
+ cv->numIndexes -= badTriangles * 3;
+ }
+
+ // take the plane information from the lightmap vector
+ for ( i = 0 ; i < 3 ; i++ ) {
+ cv->cullPlane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] );
+ }
+ cv->cullPlane.dist = DotProduct( cv->verts[0].xyz, cv->cullPlane.normal );
+ SetPlaneSignbits( &cv->cullPlane );
+ cv->cullPlane.type = PlaneTypeForNormal( cv->cullPlane.normal );
+ surf->cullinfo.plane = cv->cullPlane;
+
+ surf->data = (surfaceType_t *)cv;
+
+ // Calculate tangent spaces
+ {
+ srfVert_t *dv[3];
+
+ for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3)
+ {
+ dv[0] = &cv->verts[tri[0]];
+ dv[1] = &cv->verts[tri[1]];
+ dv[2] = &cv->verts[tri[2]];
+
+ R_CalcTangentVectors(dv);
+ }
+ }
+}
+
+
+/*
+===============
+ParseMesh
+===============
+*/
+static void ParseMesh ( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf ) {
+ srfBspSurface_t *grid = (srfBspSurface_t *)surf->data;
+ int i;
+ int width, height, numPoints;
+ srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE];
+ vec3_t bounds[2];
+ vec3_t tmpVec;
+ static surfaceType_t skipData = SF_SKIP;
+ int realLightmapNum;
+
+ realLightmapNum = LittleLong( ds->lightmapNum );
+
+ // get fog volume
+ surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+ // get shader value
+ surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) );
+ if ( r_singleShader->integer && !surf->shader->isSky ) {
+ surf->shader = tr.defaultShader;
+ }
+
+ // we may have a nodraw surface, because they might still need to
+ // be around for movement clipping
+ if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) {
+ surf->data = &skipData;
+ return;
+ }
+
+ width = LittleLong( ds->patchWidth );
+ height = LittleLong( ds->patchHeight );
+
+ if(width < 0 || width > MAX_PATCH_SIZE || height < 0 || height > MAX_PATCH_SIZE)
+ ri.Error(ERR_DROP, "ParseMesh: bad size");
+
+ verts += LittleLong( ds->firstVert );
+ numPoints = width * height;
+ for(i = 0; i < numPoints; i++)
+ LoadDrawVertToSrfVert(&points[i], &verts[i], realLightmapNum, hdrVertColors ? hdrVertColors + (ds->firstVert + i) * 3 : NULL, NULL);
+
+ // pre-tesseleate
+ R_SubdividePatchToGrid( grid, width, height, points );
+
+ // copy the level of detail origin, which is the center
+ // of the group of all curves that must subdivide the same
+ // to avoid cracking
+ for ( i = 0 ; i < 3 ; i++ ) {
+ bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] );
+ bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] );
+ }
+ VectorAdd( bounds[0], bounds[1], bounds[1] );
+ VectorScale( bounds[1], 0.5f, grid->lodOrigin );
+ VectorSubtract( bounds[0], grid->lodOrigin, tmpVec );
+ grid->lodRadius = VectorLength( tmpVec );
+
+ surf->cullinfo.type = CULLINFO_BOX | CULLINFO_SPHERE;
+ VectorCopy(grid->cullBounds[0], surf->cullinfo.bounds[0]);
+ VectorCopy(grid->cullBounds[1], surf->cullinfo.bounds[1]);
+ VectorCopy(grid->cullOrigin, surf->cullinfo.localOrigin);
+ surf->cullinfo.radius = grid->cullRadius;
+}
+
+/*
+===============
+ParseTriSurf
+===============
+*/
+static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes ) {
+ srfBspSurface_t *cv;
+ glIndex_t *tri;
+ int i, j;
+ int numVerts, numIndexes, badTriangles;
+
+ // get fog volume
+ surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+ // get shader
+ surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX );
+ if ( r_singleShader->integer && !surf->shader->isSky ) {
+ surf->shader = tr.defaultShader;
+ }
+
+ numVerts = LittleLong(ds->numVerts);
+ numIndexes = LittleLong(ds->numIndexes);
+
+ //cv = ri.Hunk_Alloc(sizeof(*cv), h_low);
+ cv = (srfBspSurface_t*)surf->data;
+ cv->surfaceType = SF_TRIANGLES;
+
+ cv->numIndexes = numIndexes;
+ cv->indexes = (glIndex_t*)ri.Hunk_Alloc(numIndexes * sizeof(cv->indexes[0]), h_low);
+
+ cv->numVerts = numVerts;
+ cv->verts = (srfVert_t*)ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low);
+
+ surf->data = (surfaceType_t *) cv;
+
+ // copy vertexes
+ surf->cullinfo.type = CULLINFO_BOX;
+ ClearBounds(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]);
+ verts += LittleLong(ds->firstVert);
+ for(i = 0; i < numVerts; i++)
+ LoadDrawVertToSrfVert(&cv->verts[i], &verts[i], -1, hdrVertColors ? hdrVertColors + (ds->firstVert + i) * 3 : NULL, surf->cullinfo.bounds);
+
+ // copy triangles
+ badTriangles = 0;
+ indexes += LittleLong(ds->firstIndex);
+ for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3)
+ {
+ for(j = 0; j < 3; j++)
+ {
+ tri[j] = LittleLong(indexes[i + j]);
+
+ if(tri[j] >= numVerts)
+ {
+ ri.Error(ERR_DROP, "Bad index in face surface");
+ }
+ }
+
+ if ((tri[0] == tri[1]) || (tri[1] == tri[2]) || (tri[0] == tri[2]))
+ {
+ tri -= 3;
+ badTriangles++;
+ }
+ }
+
+ if (badTriangles)
+ {
+ ri.Printf(PRINT_WARNING, "Trisurf has bad triangles, originally shader %s %d tris %d verts, now %d tris\n", surf->shader->name, numIndexes / 3, numVerts, numIndexes / 3 - badTriangles);
+ cv->numIndexes -= badTriangles * 3;
+ }
+
+ // Calculate tangent spaces
+ {
+ srfVert_t *dv[3];
+
+ for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3)
+ {
+ dv[0] = &cv->verts[tri[0]];
+ dv[1] = &cv->verts[tri[1]];
+ dv[2] = &cv->verts[tri[2]];
+
+ R_CalcTangentVectors(dv);
+ }
+ }
+}
+
+/*
+===============
+ParseFlare
+===============
+*/
+static void ParseFlare( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) {
+ srfFlare_t *flare;
+ int i;
+
+ // get fog volume
+ surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+ // get shader
+ surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX );
+ if ( r_singleShader->integer && !surf->shader->isSky ) {
+ surf->shader = tr.defaultShader;
+ }
+
+ //flare = ri.Hunk_Alloc( sizeof( *flare ), h_low );
+ flare = (srfFlare_t*)surf->data;
+ flare->surfaceType = SF_FLARE;
+
+ surf->data = (surfaceType_t *)flare;
+
+ for ( i = 0 ; i < 3 ; i++ ) {
+ flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] );
+ flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] );
+ flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] );
+ }
+
+ surf->cullinfo.type = CULLINFO_NONE;
+}
+
+
+/*
+=================
+R_MergedWidthPoints
+
+returns true if there are grid points merged on a width edge
+=================
+*/
+int R_MergedWidthPoints(srfBspSurface_t *grid, int offset) {
+ int i, j;
+
+ for (i = 1; i < grid->width-1; i++) {
+ for (j = i + 1; j < grid->width-1; j++) {
+ if ( fabs(grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0]) > .1) continue;
+ if ( fabs(grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1]) > .1) continue;
+ if ( fabs(grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2]) > .1) continue;
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+=================
+R_MergedHeightPoints
+
+returns true if there are grid points merged on a height edge
+=================
+*/
+int R_MergedHeightPoints(srfBspSurface_t *grid, int offset) {
+ int i, j;
+
+ for (i = 1; i < grid->height-1; i++) {
+ for (j = i + 1; j < grid->height-1; j++) {
+ if ( fabs(grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0]) > .1) continue;
+ if ( fabs(grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1]) > .1) continue;
+ if ( fabs(grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2]) > .1) continue;
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+=================
+R_FixSharedVertexLodError_r
+
+NOTE: never sync LoD through grid edges with merged points!
+
+FIXME: write generalized version that also avoids cracks between a patch and one that meets half way?
+=================
+*/
+void R_FixSharedVertexLodError_r( int start, srfBspSurface_t *grid1 ) {
+ int j, k, l, m, n, offset1, offset2, touch;
+ srfBspSurface_t *grid2;
+
+ for ( j = start; j < s_worldData.numsurfaces; j++ ) {
+ //
+ grid2 = (srfBspSurface_t *) s_worldData.surfaces[j].data;
+ // if this surface is not a grid
+ if ( grid2->surfaceType != SF_GRID ) continue;
+ // if the LOD errors are already fixed for this patch
+ if ( grid2->lodFixed == 2 ) continue;
+ // grids in the same LOD group should have the exact same lod radius
+ if ( grid1->lodRadius != grid2->lodRadius ) continue;
+ // grids in the same LOD group should have the exact same lod origin
+ if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue;
+ if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue;
+ if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue;
+ //
+ touch = false;
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = (grid1->height-1) * grid1->width;
+ else offset1 = 0;
+ if (R_MergedWidthPoints(grid1, offset1)) continue;
+ for (k = 1; k < grid1->width-1; k++) {
+ for (m = 0; m < 2; m++) {
+
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ if (R_MergedWidthPoints(grid2, offset2)) continue;
+ for ( l = 1; l < grid2->width-1; l++) {
+ //
+ if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue;
+ if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue;
+ if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue;
+ // ok the points are equal and should have the same lod error
+ grid2->widthLodError[l] = grid1->widthLodError[k];
+ touch = true;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ if (R_MergedHeightPoints(grid2, offset2)) continue;
+ for ( l = 1; l < grid2->height-1; l++) {
+ //
+ if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue;
+ if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue;
+ if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue;
+ // ok the points are equal and should have the same lod error
+ grid2->heightLodError[l] = grid1->widthLodError[k];
+ touch = true;
+ }
+ }
+ }
+ }
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = grid1->width-1;
+ else offset1 = 0;
+ if (R_MergedHeightPoints(grid1, offset1)) continue;
+ for (k = 1; k < grid1->height-1; k++) {
+ for (m = 0; m < 2; m++) {
+
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ if (R_MergedWidthPoints(grid2, offset2)) continue;
+ for ( l = 1; l < grid2->width-1; l++) {
+ //
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue;
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue;
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue;
+ // ok the points are equal and should have the same lod error
+ grid2->widthLodError[l] = grid1->heightLodError[k];
+ touch = true;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ if (R_MergedHeightPoints(grid2, offset2)) continue;
+ for ( l = 1; l < grid2->height-1; l++) {
+ //
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue;
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue;
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue;
+ // ok the points are equal and should have the same lod error
+ grid2->heightLodError[l] = grid1->heightLodError[k];
+ touch = true;
+ }
+ }
+ }
+ }
+ if (touch) {
+ grid2->lodFixed = 2;
+ R_FixSharedVertexLodError_r ( start, grid2 );
+ //NOTE: this would be correct but makes things really slow
+ //grid2->lodFixed = 1;
+ }
+ }
+}
+
+/*
+=================
+R_FixSharedVertexLodError
+
+This function assumes that all patches in one group are nicely stitched together for the highest LoD.
+If this is not the case this function will still do its job but won't fix the highest LoD cracks.
+=================
+*/
+void R_FixSharedVertexLodError( void ) {
+ int i;
+ srfBspSurface_t *grid1;
+
+ for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+ //
+ grid1 = (srfBspSurface_t *) s_worldData.surfaces[i].data;
+ // if this surface is not a grid
+ if ( grid1->surfaceType != SF_GRID )
+ continue;
+ //
+ if ( grid1->lodFixed )
+ continue;
+ //
+ grid1->lodFixed = 2;
+ // recursively fix other patches in the same LOD group
+ R_FixSharedVertexLodError_r( i + 1, grid1);
+ }
+}
+
+
+/*
+===============
+R_StitchPatches
+===============
+*/
+int R_StitchPatches( int grid1num, int grid2num ) {
+ float *v1, *v2;
+ srfBspSurface_t *grid1, *grid2;
+ int k, l, m, n, offset1, offset2, row, column;
+
+ grid1 = (srfBspSurface_t *) s_worldData.surfaces[grid1num].data;
+ grid2 = (srfBspSurface_t *) s_worldData.surfaces[grid2num].data;
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = (grid1->height-1) * grid1->width;
+ else offset1 = 0;
+ if (R_MergedWidthPoints(grid1, offset1))
+ continue;
+ for (k = 0; k < grid1->width-2; k += 2) {
+
+ for (m = 0; m < 2; m++) {
+
+ if ( grid2->width >= MAX_GRID_SIZE )
+ break;
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->width-1; l++) {
+ //
+ v1 = grid1->verts[k + offset1].xyz;
+ v2 = grid2->verts[l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[k + 2 + offset1].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[l + offset2].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert column into grid2 right after after column l
+ if (m) row = grid2->height-1;
+ else row = 0;
+ R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (grid2->height >= MAX_GRID_SIZE)
+ break;
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->height-1; l++) {
+ //
+ v1 = grid1->verts[k + offset1].xyz;
+ v2 = grid2->verts[grid2->width * l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[k + 2 + offset1].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[grid2->width * l + offset2].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert row into grid2 right after after row l
+ if (m) column = grid2->width-1;
+ else column = 0;
+ R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ }
+ }
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = grid1->width-1;
+ else offset1 = 0;
+ if (R_MergedHeightPoints(grid1, offset1))
+ continue;
+ for (k = 0; k < grid1->height-2; k += 2) {
+ for (m = 0; m < 2; m++) {
+
+ if ( grid2->width >= MAX_GRID_SIZE )
+ break;
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->width-1; l++) {
+ //
+ v1 = grid1->verts[grid1->width * k + offset1].xyz;
+ v2 = grid2->verts[l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[l + offset2].xyz;
+ v2 = grid2->verts[(l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert column into grid2 right after after column l
+ if (m) row = grid2->height-1;
+ else row = 0;
+ R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (grid2->height >= MAX_GRID_SIZE)
+ break;
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->height-1; l++) {
+ //
+ v1 = grid1->verts[grid1->width * k + offset1].xyz;
+ v2 = grid2->verts[grid2->width * l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[grid2->width * l + offset2].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert row into grid2 right after after row l
+ if (m) column = grid2->width-1;
+ else column = 0;
+ R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ }
+ }
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = (grid1->height-1) * grid1->width;
+ else offset1 = 0;
+ if (R_MergedWidthPoints(grid1, offset1))
+ continue;
+ for (k = grid1->width-1; k > 1; k -= 2) {
+
+ for (m = 0; m < 2; m++) {
+
+ if ( !grid2 || grid2->width >= MAX_GRID_SIZE )
+ break;
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->width-1; l++) {
+ //
+ v1 = grid1->verts[k + offset1].xyz;
+ v2 = grid2->verts[l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[k - 2 + offset1].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[l + offset2].xyz;
+ v2 = grid2->verts[(l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert column into grid2 right after after column l
+ if (m) row = grid2->height-1;
+ else row = 0;
+ R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (!grid2 || grid2->height >= MAX_GRID_SIZE)
+ break;
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->height-1; l++) {
+ //
+ v1 = grid1->verts[k + offset1].xyz;
+ v2 = grid2->verts[grid2->width * l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[k - 2 + offset1].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[grid2->width * l + offset2].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert row into grid2 right after after row l
+ if (m) column = grid2->width-1;
+ else column = 0;
+ R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ if (!grid2)
+ break;
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ }
+ }
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = grid1->width-1;
+ else offset1 = 0;
+ if (R_MergedHeightPoints(grid1, offset1))
+ continue;
+ for (k = grid1->height-1; k > 1; k -= 2) {
+ for (m = 0; m < 2; m++) {
+
+ if (!grid2 || grid2->width >= MAX_GRID_SIZE )
+ break;
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->width-1; l++) {
+ //
+ v1 = grid1->verts[grid1->width * k + offset1].xyz;
+ v2 = grid2->verts[l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[l + offset2].xyz;
+ v2 = grid2->verts[(l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert column into grid2 right after after column l
+ if (m) row = grid2->height-1;
+ else row = 0;
+ R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (!grid2 || grid2->height >= MAX_GRID_SIZE)
+ break;
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->height-1; l++) {
+ //
+ v1 = grid1->verts[grid1->width * k + offset1].xyz;
+ v2 = grid2->verts[grid2->width * l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[grid2->width * l + offset2].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert row into grid2 right after after row l
+ if (m) column = grid2->width-1;
+ else column = 0;
+ R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/*
+===============
+R_TryStitchPatch
+
+This function will try to stitch patches in the same LoD group together for the highest LoD.
+
+Only single missing vertice cracks will be fixed.
+
+Vertices will be joined at the patch side a crack is first found, at the other side
+of the patch (on the same row or column) the vertices will not be joined and cracks
+might still appear at that side.
+===============
+*/
+int R_TryStitchingPatch( int grid1num ) {
+ int j, numstitches;
+ srfBspSurface_t *grid1, *grid2;
+
+ numstitches = 0;
+ grid1 = (srfBspSurface_t *) s_worldData.surfaces[grid1num].data;
+ for ( j = 0; j < s_worldData.numsurfaces; j++ ) {
+ //
+ grid2 = (srfBspSurface_t *) s_worldData.surfaces[j].data;
+ // if this surface is not a grid
+ if ( grid2->surfaceType != SF_GRID ) continue;
+ // grids in the same LOD group should have the exact same lod radius
+ if ( grid1->lodRadius != grid2->lodRadius ) continue;
+ // grids in the same LOD group should have the exact same lod origin
+ if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue;
+ if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue;
+ if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue;
+ //
+ while (R_StitchPatches(grid1num, j))
+ {
+ numstitches++;
+ }
+ }
+ return numstitches;
+}
+
+/*
+===============
+R_StitchAllPatches
+===============
+*/
+void R_StitchAllPatches( void ) {
+ int i, stitched, numstitches;
+ srfBspSurface_t *grid1;
+
+ numstitches = 0;
+ do
+ {
+ stitched = false;
+ for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+ //
+ grid1 = (srfBspSurface_t *) s_worldData.surfaces[i].data;
+ // if this surface is not a grid
+ if ( grid1->surfaceType != SF_GRID )
+ continue;
+ //
+ if ( grid1->lodStitched )
+ continue;
+ //
+ grid1->lodStitched = true;
+ stitched = true;
+ //
+ numstitches += R_TryStitchingPatch( i );
+ }
+ }
+ while (stitched);
+ ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches );
+}
+
+/*
+===============
+R_MovePatchSurfacesToHunk
+===============
+*/
+void R_MovePatchSurfacesToHunk(void) {
+ int i;
+ srfBspSurface_t *grid;
+
+ for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+ void *copyFrom;
+ //
+ grid = (srfBspSurface_t *) s_worldData.surfaces[i].data;
+ // if this surface is not a grid
+ if ( grid->surfaceType != SF_GRID )
+ continue;
+ //
+
+ copyFrom = grid->widthLodError;
+ grid->widthLodError = (float*)ri.Hunk_Alloc( grid->width * 4, h_low );
+ Com_Memcpy(grid->widthLodError, copyFrom, grid->width * 4);
+ ri.Free(copyFrom);
+
+ copyFrom = grid->heightLodError;
+ grid->heightLodError = (float*)ri.Hunk_Alloc(grid->height * 4, h_low);
+ Com_Memcpy(grid->heightLodError, copyFrom, grid->height * 4);
+ ri.Free(copyFrom);
+
+ copyFrom = grid->indexes;
+ grid->indexes = (glIndex_t*)ri.Hunk_Alloc(grid->numIndexes * sizeof(glIndex_t), h_low);
+ Com_Memcpy(grid->indexes, copyFrom, grid->numIndexes * sizeof(glIndex_t));
+ ri.Free(copyFrom);
+
+ copyFrom = grid->verts;
+ grid->verts = (srfVert_t*)ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low);
+ Com_Memcpy(grid->verts, copyFrom, grid->numVerts * sizeof(srfVert_t));
+ ri.Free(copyFrom);
+ }
+}
+
+
+/*
+===============
+R_LoadSurfaces
+===============
+*/
+static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) {
+ dsurface_t *in;
+ msurface_t *out;
+ drawVert_t *dv;
+ int *indexes;
+ int count;
+ int numFaces, numMeshes, numTriSurfs, numFlares;
+ int i;
+ float *hdrVertColors = NULL;
+
+ numFaces = 0;
+ numMeshes = 0;
+ numTriSurfs = 0;
+ numFlares = 0;
+
+ if (surfs->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = surfs->filelen / sizeof(*in);
+
+ dv = (drawVert_t*)(fileBase + verts->fileofs);
+ if (verts->filelen % sizeof(*dv))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+
+ indexes = (int*)(fileBase + indexLump->fileofs);
+ if ( indexLump->filelen % sizeof(*indexes))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+
+ out = (msurface_t*)ri.Hunk_Alloc ( count * sizeof(*out), h_low );
+
+ s_worldData.surfaces = out;
+ s_worldData.numsurfaces = count;
+ s_worldData.surfacesViewCount = (int*)ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesViewCount), h_low );
+ s_worldData.surfacesDlightBits = (int*)ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesDlightBits), h_low );
+ s_worldData.surfacesPshadowBits = (int*)ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesPshadowBits), h_low );
+
+ // load hdr vertex colors
+ if (r_hdr->integer)
+ {
+ char filename[MAX_QPATH];
+ int size;
+
+ Com_sprintf( filename, sizeof( filename ), "maps/%s/vertlight.raw", s_worldData.baseName);
+ //ri.Printf(PRINT_ALL, "looking for %s\n", filename);
+
+ size = ri.FS_ReadFile(filename, (void **)&hdrVertColors);
+
+ if (hdrVertColors)
+ {
+ //ri.Printf(PRINT_ALL, "Found!\n");
+ if (size != sizeof(float) * 3 * (verts->filelen / sizeof(*dv)))
+ ri.Error(ERR_DROP, "Bad size for %s (%i, expected %i)!", filename, size, (int)((sizeof(float)) * 3 * (verts->filelen / sizeof(*dv))));
+ }
+ }
+
+
+ // Two passes, allocate surfaces first, then load them full of data
+ // This ensures surfaces are close together to reduce L2 cache misses when using VAOs,
+ // which don't actually use the verts and indexes
+ in = (dsurface_t*)(fileBase + surfs->fileofs);
+ out = s_worldData.surfaces;
+ for ( i = 0 ; i < count ; i++, in++, out++ ) {
+ switch ( LittleLong( in->surfaceType ) ) {
+ case MST_PATCH:
+ out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfBspSurface_t), h_low);
+ break;
+ case MST_TRIANGLE_SOUP:
+ out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfBspSurface_t), h_low);
+ break;
+ case MST_PLANAR:
+ out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfBspSurface_t), h_low);
+ break;
+ case MST_FLARE:
+ out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfFlare_t), h_low);
+ break;
+ default:
+ break;
+ }
+ }
+
+ in = (dsurface_t*)(fileBase + surfs->fileofs);
+ out = s_worldData.surfaces;
+ for ( i = 0 ; i < count ; i++, in++, out++ ) {
+ switch ( LittleLong( in->surfaceType ) ) {
+ case MST_PATCH:
+ ParseMesh ( in, dv, hdrVertColors, out );
+ numMeshes++;
+ break;
+ case MST_TRIANGLE_SOUP:
+ ParseTriSurf( in, dv, hdrVertColors, out, indexes );
+ numTriSurfs++;
+ break;
+ case MST_PLANAR:
+ ParseFace( in, dv, hdrVertColors, out, indexes );
+ numFaces++;
+ break;
+ case MST_FLARE:
+ ParseFlare( in, dv, out, indexes );
+ numFlares++;
+ break;
+ default:
+ ri.Error( ERR_DROP, "Bad surfaceType" );
+ }
+ }
+
+ if (hdrVertColors)
+ {
+ ri.FS_FreeFile(hdrVertColors);
+ }
+
+#ifdef PATCH_STITCHING
+ R_StitchAllPatches();
+#endif
+
+ R_FixSharedVertexLodError();
+
+#ifdef PATCH_STITCHING
+ R_MovePatchSurfacesToHunk();
+#endif
+
+ ri.Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n",
+ numFaces, numMeshes, numTriSurfs, numFlares );
+}
+
+
+
+/*
+=================
+R_LoadSubmodels
+=================
+*/
+static void R_LoadSubmodels( lump_t *l ) {
+ dmodel_t *in;
+ bmodel_t *out;
+ int i, j, count;
+
+ in = (dmodel_t*)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = l->filelen / sizeof(*in);
+
+ s_worldData.numBModels = count;
+ s_worldData.bmodels = out = (bmodel_t*)ri.Hunk_Alloc( count * sizeof(*out), h_low );
+
+ for ( i=0 ; i<count ; i++, in++, out++ ) {
+ model_t *model;
+
+ model = R_AllocModel();
+
+ assert( model != NULL ); // this should never happen
+ if ( model == NULL ) {
+ ri.Error(ERR_DROP, "R_LoadSubmodels: R_AllocModel() failed");
+ }
+
+ model->type = MOD_BRUSH;
+ model->bmodel = out;
+ Com_sprintf( model->name, sizeof( model->name ), "*%d", i );
+
+ for (j=0 ; j<3 ; j++) {
+ out->bounds[0][j] = LittleFloat (in->mins[j]);
+ out->bounds[1][j] = LittleFloat (in->maxs[j]);
+ }
+
+ out->firstSurface = LittleLong( in->firstSurface );
+ out->numSurfaces = LittleLong( in->numSurfaces );
+
+ if(i == 0)
+ {
+ // Add this for limiting VAO surface creation
+ s_worldData.numWorldSurfaces = out->numSurfaces;
+ }
+ }
+}
+
+
+
+//==================================================================
+
+/*
+=================
+R_SetParent
+=================
+*/
+static void R_SetParent (mnode_t *node, mnode_t *parent)
+{
+ node->parent = parent;
+ if (node->contents != -1)
+ return;
+ R_SetParent (node->children[0], node);
+ R_SetParent (node->children[1], node);
+}
+
+/*
+=================
+R_LoadNodesAndLeafs
+=================
+*/
+static void R_LoadNodesAndLeafs (lump_t *nodeLump, lump_t *leafLump) {
+ int i, j, p;
+ dnode_t *in;
+ dleaf_t *inLeaf;
+ mnode_t *out;
+ int numNodes, numLeafs;
+
+ in = (dnode_t*)(fileBase + nodeLump->fileofs);
+ if (nodeLump->filelen % sizeof(dnode_t) ||
+ leafLump->filelen % sizeof(dleaf_t) ) {
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ }
+ numNodes = nodeLump->filelen / sizeof(dnode_t);
+ numLeafs = leafLump->filelen / sizeof(dleaf_t);
+
+ out = (mnode_t*)ri.Hunk_Alloc ( (numNodes + numLeafs) * sizeof(*out), h_low);
+
+ s_worldData.nodes = out;
+ s_worldData.numnodes = numNodes + numLeafs;
+ s_worldData.numDecisionNodes = numNodes;
+
+ // load nodes
+ for ( i=0 ; i<numNodes; i++, in++, out++)
+ {
+ for (j=0 ; j<3 ; j++)
+ {
+ out->mins[j] = LittleLong (in->mins[j]);
+ out->maxs[j] = LittleLong (in->maxs[j]);
+ }
+
+ p = LittleLong(in->planeNum);
+ out->plane = s_worldData.planes + p;
+
+ out->contents = CONTENTS_NODE; // differentiate from leafs
+
+ for (j=0 ; j<2 ; j++)
+ {
+ p = LittleLong (in->children[j]);
+ if (p >= 0)
+ out->children[j] = s_worldData.nodes + p;
+ else
+ out->children[j] = s_worldData.nodes + numNodes + (-1 - p);
+ }
+ }
+
+ // load leafs
+ inLeaf = (dleaf_t*)(fileBase + leafLump->fileofs);
+ for ( i=0 ; i<numLeafs ; i++, inLeaf++, out++)
+ {
+ for (j=0 ; j<3 ; j++)
+ {
+ out->mins[j] = LittleLong (inLeaf->mins[j]);
+ out->maxs[j] = LittleLong (inLeaf->maxs[j]);
+ }
+
+ out->cluster = LittleLong(inLeaf->cluster);
+ out->area = LittleLong(inLeaf->area);
+
+ if ( out->cluster >= s_worldData.numClusters ) {
+ s_worldData.numClusters = out->cluster + 1;
+ }
+
+ out->firstmarksurface = LittleLong(inLeaf->firstLeafSurface);
+ out->nummarksurfaces = LittleLong(inLeaf->numLeafSurfaces);
+ }
+
+ // chain decendants
+ R_SetParent (s_worldData.nodes, NULL);
+}
+
+//=============================================================================
+
+/*
+=================
+R_LoadShaders
+=================
+*/
+static void R_LoadShaders( lump_t *l ) {
+ int i, count;
+ dshader_t *in, *out;
+
+ in = (dshader_t*)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = l->filelen / sizeof(*in);
+ out = (dshader_t*)ri.Hunk_Alloc ( count*sizeof(*out), h_low );
+
+ s_worldData.shaders = out;
+ s_worldData.numShaders = count;
+
+ Com_Memcpy( out, in, count*sizeof(*out) );
+
+ for ( i=0 ; i<count ; i++ ) {
+ out[i].surfaceFlags = LittleLong( out[i].surfaceFlags );
+ out[i].contentFlags = LittleLong( out[i].contentFlags );
+ }
+}
+
+
+/*
+=================
+R_LoadMarksurfaces
+=================
+*/
+static void R_LoadMarksurfaces (lump_t *l)
+{
+ int i, j, count;
+ int *in;
+ int *out;
+
+ in = (int*)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = l->filelen / sizeof(*in);
+ out = (int*)ri.Hunk_Alloc ( count*sizeof(*out), h_low);
+
+ s_worldData.marksurfaces = out;
+ s_worldData.nummarksurfaces = count;
+
+ for ( i=0 ; i<count ; i++)
+ {
+ j = LittleLong(in[i]);
+ out[i] = j;
+ }
+}
+
+
+/*
+=================
+R_LoadPlanes
+=================
+*/
+static void R_LoadPlanes( lump_t *l ) {
+ int i, j;
+ cplane_t *out;
+ dplane_t *in;
+ int count;
+ int bits;
+
+ in = (dplane_t*)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = l->filelen / sizeof(*in);
+ out = (cplane_t*)ri.Hunk_Alloc ( count*2*sizeof(*out), h_low);
+
+ s_worldData.planes = out;
+ s_worldData.numplanes = count;
+
+ for ( i=0 ; i<count ; i++, in++, out++) {
+ bits = 0;
+ for (j=0 ; j<3 ; j++) {
+ out->normal[j] = LittleFloat (in->normal[j]);
+ if (out->normal[j] < 0) {
+ bits |= 1<<j;
+ }
+ }
+
+ out->dist = LittleFloat (in->dist);
+ out->type = PlaneTypeForNormal( out->normal );
+ out->signbits = bits;
+ }
+}
+
+/*
+=================
+R_LoadFogs
+
+=================
+*/
+static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump ) {
+ int i;
+ fog_t *out;
+ dfog_t *fogs;
+ dbrush_t *brushes, *brush;
+ dbrushside_t *sides;
+ int count, brushesCount, sidesCount;
+ int sideNum;
+ int planeNum;
+ shader_t *shader;
+ float d;
+ int firstSide;
+
+ fogs = (dfog_t*)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*fogs)) {
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ }
+ count = l->filelen / sizeof(*fogs);
+
+ // create fog strucutres for them
+ s_worldData.numfogs = count + 1;
+ s_worldData.fogs = (fog_t*)ri.Hunk_Alloc ( s_worldData.numfogs*sizeof(*out), h_low);
+ out = s_worldData.fogs + 1;
+
+ if ( !count ) {
+ return;
+ }
+
+ brushes = (dbrush_t*)(fileBase + brushesLump->fileofs);
+ if (brushesLump->filelen % sizeof(*brushes)) {
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ }
+ brushesCount = brushesLump->filelen / sizeof(*brushes);
+
+ sides = (dbrushside_t*)(fileBase + sidesLump->fileofs);
+ if (sidesLump->filelen % sizeof(*sides)) {
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ }
+ sidesCount = sidesLump->filelen / sizeof(*sides);
+
+ for ( i=0 ; i<count ; i++, fogs++) {
+ out->originalBrushNumber = LittleLong( fogs->brushNum );
+
+ if ( (unsigned)out->originalBrushNumber >= brushesCount ) {
+ ri.Error( ERR_DROP, "fog brushNumber out of range" );
+ }
+ brush = brushes + out->originalBrushNumber;
+
+ firstSide = LittleLong( brush->firstSide );
+
+ if ( (unsigned)firstSide > sidesCount - 6 ) {
+ ri.Error( ERR_DROP, "fog brush sideNumber out of range" );
+ }
+
+ // brushes are always sorted with the axial sides first
+ sideNum = firstSide + 0;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 1;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[1][0] = s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 2;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 3;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[1][1] = s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 4;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 5;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[1][2] = s_worldData.planes[ planeNum ].dist;
+
+ // get information from the shader for fog parameters
+ shader = R_FindShader( fogs->shader, LIGHTMAP_NONE, true );
+
+ out->parms = shader->fogParms;
+
+ out->colorInt = ColorBytes4 ( shader->fogParms.color[0],
+ shader->fogParms.color[1],
+ shader->fogParms.color[2], 1.0 );
+
+ d = shader->fogParms.depthForOpaque < 1 ? 1 : shader->fogParms.depthForOpaque;
+ out->tcScale = 1.0f / ( d * 8 );
+
+ // set the gradient vector
+ sideNum = LittleLong( fogs->visibleSide );
+
+ if ( sideNum == -1 ) {
+ out->hasSurface = false;
+ } else {
+ out->hasSurface = true;
+ planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum );
+ VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface );
+ out->surface[3] = -s_worldData.planes[ planeNum ].dist;
+ }
+
+ out++;
+ }
+
+}
+
+
+/*
+================
+R_LoadLightGrid
+
+================
+*/
+void R_LoadLightGrid( lump_t *l ) {
+ int i;
+ vec3_t maxs;
+ int numGridPoints;
+ world_t *w;
+ float *wMins, *wMaxs;
+
+ w = &s_worldData;
+
+ w->lightGridInverseSize[0] = 1.0f / w->lightGridSize[0];
+ w->lightGridInverseSize[1] = 1.0f / w->lightGridSize[1];
+ w->lightGridInverseSize[2] = 1.0f / w->lightGridSize[2];
+
+ wMins = w->bmodels[0].bounds[0];
+ wMaxs = w->bmodels[0].bounds[1];
+
+ for ( i = 0 ; i < 3 ; i++ ) {
+ w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] );
+ maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] );
+ w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1;
+ }
+
+ numGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2];
+
+ if ( l->filelen != numGridPoints * 8 ) {
+ ri.Printf( PRINT_WARNING, "WARNING: light grid mismatch\n" );
+ w->lightGridData = NULL;
+ return;
+ }
+
+ w->lightGridData = (byte*)ri.Hunk_Alloc( l->filelen, h_low );
+ Com_Memcpy( w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen );
+
+ // deal with overbright bits
+ for ( i = 0 ; i < numGridPoints ; i++ ) {
+ R_ColorShiftLightingBytes( &w->lightGridData[i*8], &w->lightGridData[i*8] );
+ R_ColorShiftLightingBytes( &w->lightGridData[i*8+3], &w->lightGridData[i*8+3] );
+ }
+
+ // load hdr lightgrid
+ if (r_hdr->integer)
+ {
+ char filename[MAX_QPATH];
+ float *hdrLightGrid;
+ int size;
+
+ Com_sprintf( filename, sizeof( filename ), "maps/%s/lightgrid.raw", s_worldData.baseName);
+ //ri.Printf(PRINT_ALL, "looking for %s\n", filename);
+
+ size = ri.FS_ReadFile(filename, (void **)&hdrLightGrid);
+
+ if (hdrLightGrid)
+ {
+ //ri.Printf(PRINT_ALL, "found!\n");
+
+ if (size != sizeof(float) * 6 * numGridPoints)
+ ri.Error(ERR_DROP, "Bad size for %s (%i, expected %i)!", filename, size, (int)(sizeof(float)) * 6 * numGridPoints);
+
+ w->lightGrid16 = (uint16_t*)ri.Hunk_Alloc(sizeof(w->lightGrid16) * 6 * numGridPoints, h_low);
+
+ for (i = 0; i < numGridPoints ; i++)
+ {
+ vec4_t c;
+
+ c[0] = hdrLightGrid[i * 6];
+ c[1] = hdrLightGrid[i * 6 + 1];
+ c[2] = hdrLightGrid[i * 6 + 2];
+ c[3] = 1.0f;
+
+ R_ColorShiftLightingFloats(c, c);
+ ColorToRGB16(c, &w->lightGrid16[i * 6]);
+
+ c[0] = hdrLightGrid[i * 6 + 3];
+ c[1] = hdrLightGrid[i * 6 + 4];
+ c[2] = hdrLightGrid[i * 6 + 5];
+ c[3] = 1.0f;
+
+ R_ColorShiftLightingFloats(c, c);
+ ColorToRGB16(c, &w->lightGrid16[i * 6 + 3]);
+ }
+ }
+ else if (0)
+ {
+ // promote 8-bit lightgrid to 16-bit
+ w->lightGrid16 = (uint16_t*)ri.Hunk_Alloc(sizeof(w->lightGrid16) * 6 * numGridPoints, h_low);
+
+ for (i = 0; i < numGridPoints; i++)
+ {
+ w->lightGrid16[i * 6] = w->lightGridData[i * 8] * 257;
+ w->lightGrid16[i * 6 + 1] = w->lightGridData[i * 8 + 1] * 257;
+ w->lightGrid16[i * 6 + 2] = w->lightGridData[i * 8 + 2] * 257;
+ w->lightGrid16[i * 6 + 3] = w->lightGridData[i * 8 + 3] * 257;
+ w->lightGrid16[i * 6 + 4] = w->lightGridData[i * 8 + 4] * 257;
+ w->lightGrid16[i * 6 + 5] = w->lightGridData[i * 8 + 5] * 257;
+ }
+ }
+
+ if (hdrLightGrid)
+ ri.FS_FreeFile(hdrLightGrid);
+ }
+}
+
+/*
+================
+R_LoadEntities
+================
+*/
+void R_LoadEntities( lump_t *l ) {
+ char *p, *token;
+ char *s;
+ char keyname[MAX_TOKEN_CHARS];
+ char value[MAX_TOKEN_CHARS];
+ world_t *w;
+
+ w = &s_worldData;
+ w->lightGridSize[0] = 64;
+ w->lightGridSize[1] = 64;
+ w->lightGridSize[2] = 128;
+
+ p = (char *)(fileBase + l->fileofs);
+
+ // store for reference by the cgame
+ w->entityString = (char*)ri.Hunk_Alloc( l->filelen + 1, h_low );
+ strcpy( w->entityString, p );
+ w->entityParsePoint = w->entityString;
+
+ token = COM_ParseExt( &p, qtrue );
+ if (!*token || *token != '{') {
+ return;
+ }
+
+ // only parse the world spawn
+ while ( 1 ) {
+ // parse key
+ token = COM_ParseExt( &p, qtrue );
+
+ if ( !*token || *token == '}' ) {
+ break;
+ }
+ Q_strncpyz(keyname, token, sizeof(keyname));
+
+ // parse value
+ token = COM_ParseExt( &p, qtrue );
+
+ if ( !*token || *token == '}' ) {
+ break;
+ }
+ Q_strncpyz(value, token, sizeof(value));
+
+ // check for remapping of shaders for vertex lighting
+ if (!Q_strncmp(keyname, "vertexremapshader", strlen("vertexremapshader")) ) {
+ s = strchr(value, ';');
+ if (!s) {
+ ri.Printf( PRINT_WARNING, "WARNING: no semi colon in vertexshaderremap '%s'\n", value );
+ break;
+ }
+ *s++ = 0;
+ if (r_vertexLight->integer) {
+ R_RemapShader(value, s, "0");
+ }
+ continue;
+ }
+ // check for remapping of shaders
+ if (!Q_strncmp(keyname, "remapshader", strlen("remapshader")) ) {
+ s = strchr(value, ';');
+ if (!s) {
+ ri.Printf( PRINT_WARNING, "WARNING: no semi colon in shaderremap '%s'\n", value );
+ break;
+ }
+ *s++ = 0;
+ R_RemapShader(value, s, "0");
+ continue;
+ }
+ // check for a different grid size
+ if (!Q_stricmp(keyname, "gridsize")) {
+ sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] );
+ continue;
+ }
+
+ // check for auto exposure
+ if (!Q_stricmp(keyname, "autoExposureMinMax")) {
+ sscanf(value, "%f %f", &tr.autoExposureMinMax[0], &tr.autoExposureMinMax[1]);
+ continue;
+ }
+ }
+}
+
+/*
+=================
+R_GetEntityToken
+=================
+*/
+bool R_GetEntityToken( char *buffer, int size ) {
+ const char *s;
+
+ s = COM_Parse( &s_worldData.entityParsePoint );
+ Q_strncpyz( buffer, s, size );
+ if ( !s_worldData.entityParsePoint && !s[0] ) {
+ s_worldData.entityParsePoint = s_worldData.entityString;
+ return false;
+ } else {
+ return true;
+ }
+}
+
+#ifndef MAX_SPAWN_VARS
+#define MAX_SPAWN_VARS 64
+#endif
+
+// derived from G_ParseSpawnVars() in g_spawn.c
+bool R_ParseSpawnVars( char *spawnVarChars, int maxSpawnVarChars, int *numSpawnVars, char *spawnVars[MAX_SPAWN_VARS][2] )
+{
+ char keyname[MAX_TOKEN_CHARS];
+ char com_token[MAX_TOKEN_CHARS];
+ int numSpawnVarChars = 0;
+
+ *numSpawnVars = 0;
+
+ // parse the opening brace
+ if ( !R_GetEntityToken( com_token, sizeof( com_token ) ) ) {
+ // end of spawn string
+ return false;
+ }
+ if ( com_token[0] != '{' ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: found %s when expecting {\n",com_token );
+ return false;
+ }
+
+ // go through all the key / value pairs
+ while ( 1 ) {
+ int keyLength, tokenLength;
+
+ // parse key
+ if ( !R_GetEntityToken( keyname, sizeof( keyname ) ) ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: EOF without closing brace\n" );
+ return false;
+ }
+
+ if ( keyname[0] == '}' ) {
+ break;
+ }
+
+ // parse value
+ if ( !R_GetEntityToken( com_token, sizeof( com_token ) ) ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: EOF without closing brace\n" );
+ return false;
+ }
+
+ if ( com_token[0] == '}' ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: closing brace without data\n" );
+ return false;
+ }
+
+ if ( *numSpawnVars == MAX_SPAWN_VARS ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: MAX_SPAWN_VARS\n" );
+ return false;
+ }
+
+ keyLength = strlen(keyname) + 1;
+ tokenLength = strlen(com_token) + 1;
+
+ if (numSpawnVarChars + keyLength + tokenLength > maxSpawnVarChars)
+ {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: MAX_SPAWN_VAR_CHARS\n" );
+ return false;
+ }
+
+ strcpy(spawnVarChars + numSpawnVarChars, keyname);
+ spawnVars[ *numSpawnVars ][0] = spawnVarChars + numSpawnVarChars;
+ numSpawnVarChars += keyLength;
+
+ strcpy(spawnVarChars + numSpawnVarChars, com_token);
+ spawnVars[ *numSpawnVars ][1] = spawnVarChars + numSpawnVarChars;
+ numSpawnVarChars += tokenLength;
+
+ (*numSpawnVars)++;
+ }
+
+ return true;
+}
+
+void R_LoadEnvironmentJson(const char *baseName)
+{
+ char filename[MAX_QPATH];
+
+ union {
+ char *c;
+ void *v;
+ } buffer;
+ char *bufferEnd;
+
+ const char *cubemapArrayJson;
+ int filelen, i;
+
+ Com_sprintf(filename, MAX_QPATH, "cubemaps/%s/env.json", baseName);
+
+ filelen = ri.FS_ReadFile(filename, &buffer.v);
+ if (!buffer.c)
+ return;
+ bufferEnd = buffer.c + filelen;
+
+ if (JSON_ValueGetType(buffer.c, bufferEnd) != JSONTYPE_OBJECT)
+ {
+ ri.Printf(PRINT_ALL, "Bad %s: does not start with a object\n", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+
+ cubemapArrayJson = JSON_ObjectGetNamedValue(buffer.c, bufferEnd, "Cubemaps");
+ if (!cubemapArrayJson)
+ {
+ ri.Printf(PRINT_ALL, "Bad %s: no Cubemaps\n", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+
+ if (JSON_ValueGetType(cubemapArrayJson, bufferEnd) != JSONTYPE_ARRAY)
+ {
+ ri.Printf(PRINT_ALL, "Bad %s: Cubemaps not an array\n", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+
+ tr.numCubemaps = JSON_ArrayGetIndex(cubemapArrayJson, bufferEnd, NULL, 0);
+ tr.cubemaps = (cubemap_t*)ri.Hunk_Alloc(tr.numCubemaps * sizeof(*tr.cubemaps), h_low);
+ memset(tr.cubemaps, 0, tr.numCubemaps * sizeof(*tr.cubemaps));
+
+ for (i = 0; i < tr.numCubemaps; i++)
+ {
+ cubemap_t *cubemap = &tr.cubemaps[i];
+ const char *cubemapJson, *keyValueJson, *indexes[3];
+ int j;
+
+ cubemapJson = JSON_ArrayGetValue(cubemapArrayJson, bufferEnd, i);
+
+ keyValueJson = JSON_ObjectGetNamedValue(cubemapJson, bufferEnd, "Name");
+ if (!JSON_ValueGetString(keyValueJson, bufferEnd, cubemap->name, MAX_QPATH))
+ cubemap->name[0] = '\0';
+
+ keyValueJson = JSON_ObjectGetNamedValue(cubemapJson, bufferEnd, "Position");
+ JSON_ArrayGetIndex(keyValueJson, bufferEnd, indexes, 3);
+ for (j = 0; j < 3; j++)
+ cubemap->origin[j] = JSON_ValueGetFloat(indexes[j], bufferEnd);
+
+ cubemap->parallaxRadius = 1000.0f;
+ keyValueJson = JSON_ObjectGetNamedValue(cubemapJson, bufferEnd, "Radius");
+ if (keyValueJson)
+ cubemap->parallaxRadius = JSON_ValueGetFloat(keyValueJson, bufferEnd);
+ }
+
+ ri.FS_FreeFile(buffer.v);
+}
+
+void R_LoadCubemapEntities(const char *cubemapEntityName)
+{
+ char spawnVarChars[2048];
+ int numSpawnVars;
+ char *spawnVars[MAX_SPAWN_VARS][2];
+ int numCubemaps = 0;
+
+ // count cubemaps
+ numCubemaps = 0;
+ while(R_ParseSpawnVars(spawnVarChars, sizeof(spawnVarChars), &numSpawnVars, spawnVars))
+ {
+ int i;
+
+ for (i = 0; i < numSpawnVars; i++)
+ {
+ if (!Q_stricmp(spawnVars[i][0], "classname") && !Q_stricmp(spawnVars[i][1], cubemapEntityName))
+ numCubemaps++;
+ }
+ }
+
+ if (!numCubemaps)
+ return;
+
+ tr.numCubemaps = numCubemaps;
+ tr.cubemaps = (cubemap_t*)ri.Hunk_Alloc(tr.numCubemaps * sizeof(*tr.cubemaps), h_low);
+ memset(tr.cubemaps, 0, tr.numCubemaps * sizeof(*tr.cubemaps));
+
+ numCubemaps = 0;
+ while(R_ParseSpawnVars(spawnVarChars, sizeof(spawnVarChars), &numSpawnVars, spawnVars))
+ {
+ int i;
+ char name[MAX_QPATH];
+ bool isCubemap = false;
+ bool originSet = false;
+ vec3_t origin;
+ float parallaxRadius = 1000.0f;
+
+ name[0] = '\0';
+ for (i = 0; i < numSpawnVars; i++)
+ {
+ if (!Q_stricmp(spawnVars[i][0], "classname") && !Q_stricmp(spawnVars[i][1], cubemapEntityName))
+ isCubemap = true;
+
+ if (!Q_stricmp(spawnVars[i][0], "name"))
+ Q_strncpyz(name, spawnVars[i][1], MAX_QPATH);
+
+ if (!Q_stricmp(spawnVars[i][0], "origin"))
+ {
+ sscanf(spawnVars[i][1], "%f %f %f", &origin[0], &origin[1], &origin[2]);
+ originSet = true;
+ }
+ else if (!Q_stricmp(spawnVars[i][0], "radius"))
+ {
+ sscanf(spawnVars[i][1], "%f", &parallaxRadius);
+ }
+ }
+
+ if (isCubemap && originSet)
+ {
+ cubemap_t *cubemap = &tr.cubemaps[numCubemaps];
+ Q_strncpyz(cubemap->name, name, MAX_QPATH);
+ VectorCopy(origin, cubemap->origin);
+ cubemap->parallaxRadius = parallaxRadius;
+ numCubemaps++;
+ }
+ }
+}
+
+void R_AssignCubemapsToWorldSurfaces(void)
+{
+ world_t *w;
+ int i;
+
+ w = &s_worldData;
+
+ for (i = 0; i < w->numsurfaces; i++)
+ {
+ msurface_t *surf = &w->surfaces[i];
+ vec3_t surfOrigin;
+
+ if (surf->cullinfo.type & CULLINFO_SPHERE)
+ {
+ VectorCopy(surf->cullinfo.localOrigin, surfOrigin);
+ }
+ else if (surf->cullinfo.type & CULLINFO_BOX)
+ {
+ surfOrigin[0] = (surf->cullinfo.bounds[0][0] + surf->cullinfo.bounds[1][0]) * 0.5f;
+ surfOrigin[1] = (surf->cullinfo.bounds[0][1] + surf->cullinfo.bounds[1][1]) * 0.5f;
+ surfOrigin[2] = (surf->cullinfo.bounds[0][2] + surf->cullinfo.bounds[1][2]) * 0.5f;
+ }
+ else
+ {
+ //ri.Printf(PRINT_ALL, "surface %d has no cubemap\n", i);
+ continue;
+ }
+
+ surf->cubemapIndex = R_CubemapForPoint(surfOrigin);
+ //ri.Printf(PRINT_ALL, "surface %d has cubemap %d\n", i, surf->cubemapIndex);
+ }
+}
+
+
+void R_LoadCubemaps(void)
+{
+ int i;
+ int/*imgFlags_t*/ flags = IMGFLAG_CLAMPTOEDGE | IMGFLAG_MIPMAP | IMGFLAG_NOLIGHTSCALE | IMGFLAG_CUBEMAP;
+
+ for (i = 0; i < tr.numCubemaps; i++)
+ {
+ char filename[MAX_QPATH];
+ cubemap_t *cubemap = &tr.cubemaps[i];
+
+ Com_sprintf(filename, MAX_QPATH, "cubemaps/%s/%03d.dds", tr.world->baseName, i);
+
+ cubemap->image = R_FindImageFile(filename, IMGTYPE_COLORALPHA, flags);
+ }
+}
+
+
+void R_RenderMissingCubemaps(void)
+{
+ int i, j;
+ int/*imgFlags_t*/ flags = IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE | IMGFLAG_MIPMAP | IMGFLAG_NOLIGHTSCALE | IMGFLAG_CUBEMAP;
+
+ for (i = 0; i < tr.numCubemaps; i++)
+ {
+ if (!tr.cubemaps[i].image)
+ {
+ tr.cubemaps[i].image = R_CreateImage(va("*cubeMap%d", i), NULL,
+ r_cubemapSize->integer, r_cubemapSize->integer,
+ IMGTYPE_COLORALPHA, flags, GL_RGBA8);
+
+ for (j = 0; j < 6; j++)
+ {
+ RE_ClearScene();
+ R_RenderCubemapSide(i, j, false);
+ R_IssuePendingRenderCommands();
+ R_InitNextFrame();
+ }
+ }
+ }
+}
+
+
+void R_CalcVertexLightDirs( void )
+{
+ int i, k;
+ msurface_t *surface;
+
+ for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numsurfaces /* s_worldData.numWorldSurfaces */; k++, surface++)
+ {
+ srfBspSurface_t *bspSurf = (srfBspSurface_t *) surface->data;
+
+ switch(bspSurf->surfaceType)
+ {
+ case SF_FACE:
+ case SF_GRID:
+ case SF_TRIANGLES:
+ for(i = 0; i < bspSurf->numVerts; i++)
+ {
+ vec3_t lightDir;
+ vec3_t normal;
+
+ R_VaoUnpackNormal(normal, bspSurf->verts[i].normal);
+ R_LightDirForPoint( bspSurf->verts[i].xyz, lightDir, normal, &s_worldData );
+ R_VaoPackNormal(bspSurf->verts[i].lightdir, lightDir);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+/*
+=================
+RE_LoadWorldMap
+
+Called directly from cgame
+=================
+*/
+void RE_LoadWorldMap( const char *name ) {
+ int i;
+ dheader_t *header;
+ union {
+ byte *b;
+ void *v;
+ } buffer;
+ byte *startMarker;
+
+ if ( tr.worldMapLoaded ) {
+ ri.Error( ERR_DROP, "ERROR: attempted to redundantly load world map" );
+ }
+
+ // set default map light scale
+ tr.sunShadowScale = 0.5f;
+
+ // set default sun direction to be used if it isn't
+ // overridden by a shader
+ tr.sunDirection[0] = 0.45f;
+ tr.sunDirection[1] = 0.3f;
+ tr.sunDirection[2] = 0.9f;
+
+ VectorNormalize( tr.sunDirection );
+
+ // set default autoexposure settings
+ tr.autoExposureMinMax[0] = -2.0f;
+ tr.autoExposureMinMax[1] = 2.0f;
+
+ // set default tone mapping settings
+ tr.toneMinAvgMaxLevel[0] = -8.0f;
+ tr.toneMinAvgMaxLevel[1] = -2.0f;
+ tr.toneMinAvgMaxLevel[2] = 0.0f;
+
+ // reset last cascade sun direction so last shadow cascade is rerendered
+ VectorClear(tr.lastCascadeSunDirection);
+
+ tr.worldMapLoaded = true;
+
+ // load it
+ ri.FS_ReadFile( name, &buffer.v );
+ if ( !buffer.b ) {
+ ri.Error (ERR_DROP, "RE_LoadWorldMap: %s not found", name);
+ }
+
+ // clear tr.world so if the level fails to load, the next
+ // try will not look at the partially loaded version
+ tr.world = NULL;
+
+ Com_Memset( &s_worldData, 0, sizeof( s_worldData ) );
+ Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) );
+
+ Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) );
+ COM_StripExtension(s_worldData.baseName, s_worldData.baseName, sizeof(s_worldData.baseName));
+
+ startMarker = (byte*)ri.Hunk_Alloc(0, h_low);
+ c_gridVerts = 0;
+
+ header = (dheader_t *)buffer.b;
+ fileBase = (byte *)header;
+
+ i = LittleLong (header->version);
+ if ( i != BSP_VERSION ) {
+ ri.Error (ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)",
+ name, i, BSP_VERSION);
+ }
+
+ // swap all the lumps
+ for (i=0 ; i<sizeof(dheader_t)/4 ; i++) {
+ ((int *)header)[i] = LittleLong ( ((int *)header)[i]);
+ }
+
+ // load into heap
+ R_LoadEntities( &header->lumps[LUMP_ENTITIES] );
+ R_LoadShaders( &header->lumps[LUMP_SHADERS] );
+ R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS], &header->lumps[LUMP_SURFACES] );
+ R_LoadPlanes (&header->lumps[LUMP_PLANES]);
+ R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES] );
+ R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES] );
+ R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES]);
+ R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]);
+ R_LoadSubmodels (&header->lumps[LUMP_MODELS]);
+ R_LoadVisibility( &header->lumps[LUMP_VISIBILITY] );
+ R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID] );
+
+ // determine vertex light directions
+ R_CalcVertexLightDirs();
+
+ // determine which parts of the map are in sunlight
+ if (0)
+ {
+ world_t *w;
+ uint8_t *primaryLightGrid, *data;
+ int lightGridSize;
+ int i;
+
+ w = &s_worldData;
+
+ lightGridSize = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2];
+ primaryLightGrid = (uint8_t*)ri.Malloc(lightGridSize * sizeof(*primaryLightGrid));
+
+ memset(primaryLightGrid, 0, lightGridSize * sizeof(*primaryLightGrid));
+
+ data = w->lightGridData;
+ for (i = 0; i < lightGridSize; i++, data += 8)
+ {
+ int lat, lng;
+ vec3_t gridLightDir, gridLightCol;
+
+ // skip samples in wall
+ if (!(data[0]+data[1]+data[2]+data[3]+data[4]+data[5]) )
+ continue;
+
+ gridLightCol[0] = ByteToFloat(data[3]);
+ gridLightCol[1] = ByteToFloat(data[4]);
+ gridLightCol[2] = ByteToFloat(data[5]);
+ (void)gridLightCol; // Suppress unused-but-set-variable warning
+
+ lat = data[7];
+ lng = data[6];
+ lat *= (FUNCTABLE_SIZE/256);
+ lng *= (FUNCTABLE_SIZE/256);
+
+ // decode X as cos( lat ) * sin( long )
+ // decode Y as sin( lat ) * sin( long )
+ // decode Z as cos( long )
+
+ gridLightDir[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ gridLightDir[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ gridLightDir[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+ // FIXME: magic number for determining if light direction is close enough to sunlight
+ if (DotProduct(gridLightDir, tr.sunDirection) > 0.75f)
+ {
+ primaryLightGrid[i] = 1;
+ }
+ else
+ {
+ primaryLightGrid[i] = 255;
+ }
+ }
+
+ if (0)
+ {
+ int i;
+ byte *buffer = (byte*)ri.Malloc(w->lightGridBounds[0] * w->lightGridBounds[1] * 3 + 18);
+ byte *out;
+ uint8_t *in;
+ char fileName[MAX_QPATH];
+
+ Com_Memset (buffer, 0, 18);
+ buffer[2] = 2; // uncompressed type
+ buffer[12] = w->lightGridBounds[0] & 255;
+ buffer[13] = w->lightGridBounds[0] >> 8;
+ buffer[14] = w->lightGridBounds[1] & 255;
+ buffer[15] = w->lightGridBounds[1] >> 8;
+ buffer[16] = 24; // pixel size
+
+ in = primaryLightGrid;
+ for (i = 0; i < w->lightGridBounds[2]; i++)
+ {
+ int j;
+
+ sprintf(fileName, "primarylg%d.tga", i);
+
+ out = buffer + 18;
+ for (j = 0; j < w->lightGridBounds[0] * w->lightGridBounds[1]; j++)
+ {
+ if (*in == 1)
+ {
+ *out++ = 255;
+ *out++ = 255;
+ *out++ = 255;
+ }
+ else if (*in == 255)
+ {
+ *out++ = 64;
+ *out++ = 64;
+ *out++ = 64;
+ }
+ else
+ {
+ *out++ = 0;
+ *out++ = 0;
+ *out++ = 0;
+ }
+ in++;
+ }
+
+ ri.FS_WriteFile(fileName, buffer, w->lightGridBounds[0] * w->lightGridBounds[1] * 3 + 18);
+ }
+
+ ri.Free(buffer);
+ }
+
+ for (i = 0; i < w->numWorldSurfaces; i++)
+ {
+ msurface_t *surf = w->surfaces + i;
+ cullinfo_t *ci = &surf->cullinfo;
+
+ if(ci->type & CULLINFO_PLANE)
+ {
+ if (DotProduct(ci->plane.normal, tr.sunDirection) <= 0.0f)
+ {
+ //ri.Printf(PRINT_ALL, "surface %d is not oriented towards sunlight\n", i);
+ continue;
+ }
+ }
+
+ if(ci->type & CULLINFO_BOX)
+ {
+ int ibounds[2][3], x, y, z, goodSamples, numSamples;
+ vec3_t lightOrigin;
+
+ VectorSubtract( ci->bounds[0], w->lightGridOrigin, lightOrigin );
+
+ ibounds[0][0] = floor(lightOrigin[0] * w->lightGridInverseSize[0]);
+ ibounds[0][1] = floor(lightOrigin[1] * w->lightGridInverseSize[1]);
+ ibounds[0][2] = floor(lightOrigin[2] * w->lightGridInverseSize[2]);
+
+ VectorSubtract( ci->bounds[1], w->lightGridOrigin, lightOrigin );
+
+ ibounds[1][0] = ceil(lightOrigin[0] * w->lightGridInverseSize[0]);
+ ibounds[1][1] = ceil(lightOrigin[1] * w->lightGridInverseSize[1]);
+ ibounds[1][2] = ceil(lightOrigin[2] * w->lightGridInverseSize[2]);
+
+ ibounds[0][0] = CLAMP(ibounds[0][0], 0, w->lightGridSize[0]);
+ ibounds[0][1] = CLAMP(ibounds[0][1], 0, w->lightGridSize[1]);
+ ibounds[0][2] = CLAMP(ibounds[0][2], 0, w->lightGridSize[2]);
+
+ ibounds[1][0] = CLAMP(ibounds[1][0], 0, w->lightGridSize[0]);
+ ibounds[1][1] = CLAMP(ibounds[1][1], 0, w->lightGridSize[1]);
+ ibounds[1][2] = CLAMP(ibounds[1][2], 0, w->lightGridSize[2]);
+
+ /*
+ ri.Printf(PRINT_ALL, "surf %d bounds (%f %f %f)-(%f %f %f) ibounds (%d %d %d)-(%d %d %d)\n", i,
+ ci->bounds[0][0], ci->bounds[0][1], ci->bounds[0][2],
+ ci->bounds[1][0], ci->bounds[1][1], ci->bounds[1][2],
+ ibounds[0][0], ibounds[0][1], ibounds[0][2],
+ ibounds[1][0], ibounds[1][1], ibounds[1][2]);
+ */
+
+ goodSamples = 0;
+ numSamples = 0;
+ for (x = ibounds[0][0]; x <= ibounds[1][0]; x++)
+ {
+ for (y = ibounds[0][1]; y <= ibounds[1][1]; y++)
+ {
+ for (z = ibounds[0][2]; z <= ibounds[1][2]; z++)
+ {
+ uint8_t primaryLight = primaryLightGrid[x * 8 + y * 8 * w->lightGridBounds[0] + z * 8 * w->lightGridBounds[0] * w->lightGridBounds[2]];
+
+ if (primaryLight == 0)
+ continue;
+
+ numSamples++;
+
+ if (primaryLight == 1)
+ goodSamples++;
+ }
+ }
+ }
+
+ // FIXME: magic number for determining whether object is mostly in sunlight
+ if (goodSamples > numSamples * 0.75f)
+ {
+ //ri.Printf(PRINT_ALL, "surface %d is in sunlight\n", i);
+ //surf->primaryLight = 1;
+ }
+ }
+ }
+
+ ri.Free(primaryLightGrid);
+ }
+
+ // load cubemaps
+ if (r_cubeMapping->integer)
+ {
+ // Try loading an env.json file first
+ R_LoadEnvironmentJson(s_worldData.baseName);
+
+ if (!tr.numCubemaps)
+ {
+ R_LoadCubemapEntities("misc_cubemap");
+ }
+
+ if (!tr.numCubemaps)
+ {
+ // location names are an assured way to get an even distribution
+ R_LoadCubemapEntities("target_location");
+ }
+
+ if (!tr.numCubemaps)
+ {
+ // try misc_models
+ R_LoadCubemapEntities("misc_model");
+ }
+
+ if (tr.numCubemaps)
+ {
+ R_AssignCubemapsToWorldSurfaces();
+ }
+ }
+
+ s_worldData.dataSize = (byte *)ri.Hunk_Alloc(0, h_low) - startMarker;
+
+ // only set tr.world now that we know the entire level has loaded properly
+ tr.world = &s_worldData;
+
+ // make sure the VAO glState entry is safe
+ R_BindNullVao();
+
+ // Render or load all cubemaps
+ if (r_cubeMapping->integer && tr.numCubemaps && glRefConfig.framebufferObject)
+ {
+ R_LoadCubemaps();
+ R_RenderMissingCubemaps();
+ }
+
+ ri.FS_FreeFile( buffer.v );
+}