mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-05-13 22:12:20 +03:00
9296 lines
225 KiB
C
9296 lines
225 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
Copyright (C) 2006-2011 Robert Beckebans <trebor_7@users.sourceforge.net>
|
|
Copyright (C) 2009 Peter McNeill <n27@bigpond.net.au>
|
|
|
|
This file is part of XreaL source code.
|
|
|
|
XreaL 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.
|
|
|
|
XreaL 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 XreaL source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
// tr_bsp.c
|
|
#include "tr_local.h"
|
|
|
|
/*
|
|
========================================================
|
|
Loads and prepares a map file for scene rendering.
|
|
|
|
A single entry point:
|
|
RE_LoadWorldMap(const char *name);
|
|
========================================================
|
|
*/
|
|
|
|
static world_t s_worldData;
|
|
static int s_lightCount;
|
|
static growList_t s_interactions;
|
|
static byte *fileBase;
|
|
|
|
static int c_redundantInteractions;
|
|
static int c_redundantVertexes;
|
|
static int c_vboWorldSurfaces;
|
|
static int c_vboLightSurfaces;
|
|
static int c_vboShadowSurfaces;
|
|
|
|
//===============================================================================
|
|
|
|
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
|
|
===============
|
|
*/
|
|
#if defined(COMPAT_Q3A) || defined(COMPAT_ET)
|
|
static void R_ColorShiftLightingBytes(byte in[4], byte out[4])
|
|
{
|
|
int shift, r, g, b;
|
|
|
|
// shift the color data based on overbright range
|
|
shift = tr.mapOverBrightBits - tr.overbrightBits;
|
|
|
|
// shift the data based on overbright range
|
|
r = in[0] << shift;
|
|
g = in[1] << shift;
|
|
b = in[2] << shift;
|
|
|
|
// 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];
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
===============
|
|
R_ColorShiftLightingFloats
|
|
===============
|
|
*/
|
|
#if 1 //defined(COMPAT_Q3A)
|
|
static void R_ColorShiftLightingFloats(const vec4_t in, vec4_t out)
|
|
{
|
|
int shift, r, g, b;
|
|
|
|
// shift the color data based on overbright range
|
|
shift = tr.mapOverBrightBits - tr.overbrightBits;
|
|
|
|
// shift the data based on overbright range
|
|
r = ((byte)(in[0] * 255)) << shift;
|
|
g = ((byte)(in[1] * 255)) << shift;
|
|
b = ((byte)(in[2] * 255)) << shift;
|
|
|
|
// 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 * (1.0f / 255.0f);
|
|
out[1] = g * (1.0f / 255.0f);
|
|
out[2] = b * (1.0f / 255.0f);
|
|
out[3] = in[3];
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
===============
|
|
R_HDRTonemapLightingColors
|
|
===============
|
|
*/
|
|
static void R_HDRTonemapLightingColors(const vec4_t in, vec4_t out, qboolean applyGamma)
|
|
{
|
|
#if 0 //!defined(USE_HDR_LIGHTMAPS)
|
|
R_ColorShiftLightingFloats(in, out);
|
|
#else
|
|
int i;
|
|
//float scaledLuminance;
|
|
//float finalLuminance;
|
|
//const vec3_t LUMINANCE_VECTOR = { 0.2125f, 0.7154f, 0.0721f };
|
|
vec4_t sample;
|
|
|
|
if(!tr.worldHDR_RGBE)
|
|
{
|
|
R_ColorShiftLightingFloats(in, out);
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
scaledLuminance = r_hdrLightmapExposure->value * DotProduct(in, LUMINANCE_VECTOR);
|
|
|
|
#if 0
|
|
finalLuminance = scaledLuminance / (scaledLuminance + 1.0);
|
|
#else
|
|
// exponential tone mapping
|
|
finalLuminance = 1.0 - exp(-scaledLuminance);
|
|
#endif
|
|
|
|
VectorScale(sample, finalLuminance, sample);
|
|
sample[3] = Q_min(1.0f, sample[3]);
|
|
|
|
if(!r_hdrRendering->integer || !r_hdrLightmap->integer || !glConfig2.framebufferObjectAvailable ||
|
|
!glConfig2.textureFloatAvailable || !glConfig2.framebufferBlitAvailable)
|
|
{
|
|
float max;
|
|
|
|
// clamp with color normalization
|
|
NormalizeColor(sample, out);
|
|
out[3] = Q_min(1.0f, sample[3]);
|
|
}
|
|
else
|
|
{
|
|
Vector4Copy(sample, out);
|
|
}
|
|
#else
|
|
if(!r_hdrRendering->integer || !r_hdrLightmap->integer || !glConfig2.framebufferObjectAvailable ||
|
|
!glConfig2.textureFloatAvailable || !glConfig2.framebufferBlitAvailable)
|
|
{
|
|
float max;
|
|
|
|
Vector4Copy(in, sample);
|
|
|
|
// clamp with color normalization
|
|
max = sample[0];
|
|
if(sample[1] > max)
|
|
max = sample[1];
|
|
if(sample[2] > max)
|
|
max = sample[2];
|
|
if(max > 255.0f)
|
|
VectorScale(sample, (255.0f / max), out);
|
|
|
|
VectorScale(out, (1.0f / 255.0f), out);
|
|
|
|
out[3] = Q_min(1.0f, sample[3]);
|
|
}
|
|
else
|
|
{
|
|
Vector4Scale(in, 1.0f / 255.0f, sample);
|
|
|
|
if(applyGamma)
|
|
{
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
sample[i] = pow(sample[i], 1.0f / r_hdrLightmapGamma->value);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
scaledLuminance = r_hdrLightmapExposure->value * DotProduct(in, LUMINANCE_VECTOR);
|
|
|
|
#if 0
|
|
finalLuminance = scaledLuminance / (scaledLuminance + 1.0);
|
|
#else
|
|
// exponential tone mapping
|
|
finalLuminance = 1.0 - exp(-scaledLuminance);
|
|
#endif
|
|
|
|
VectorScale(sample, finalLuminance, out);
|
|
#else
|
|
VectorCopy(sample, out);
|
|
#endif
|
|
|
|
out[3] = Q_min(1.0f, sample[3]);
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_ProcessLightmap
|
|
|
|
returns maxIntensity
|
|
===============
|
|
*/
|
|
#if defined(COMPAT_Q3A) || defined(COMPAT_ET)
|
|
float R_ProcessLightmap(byte ** pic, int in_padding, int width, int height, byte ** pic_out)
|
|
{
|
|
int j;
|
|
float maxIntensity = 0;
|
|
double sumIntensity = 0;
|
|
|
|
/*
|
|
if(r_lightmap->integer > 1)
|
|
{
|
|
// color code by intensity as development tool (FIXME: check range)
|
|
for(j = 0; j < width * height; j++)
|
|
{
|
|
float r = (*pic)[j * in_padding + 0];
|
|
float g = (*pic)[j * in_padding + 1];
|
|
float b = (*pic)[j * in_padding + 2];
|
|
float intensity;
|
|
float out[3];
|
|
|
|
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);
|
|
|
|
if(r_lightmap->integer == 3)
|
|
{
|
|
// Arnout: artists wanted the colours to be inversed
|
|
(*pic_out)[j * 4 + 0] = out[2] * 255;
|
|
(*pic_out)[j * 4 + 1] = out[1] * 255;
|
|
(*pic_out)[j * 4 + 2] = out[0] * 255;
|
|
}
|
|
else
|
|
{
|
|
(*pic_out)[j * 4 + 0] = out[0] * 255;
|
|
(*pic_out)[j * 4 + 1] = out[1] * 255;
|
|
(*pic_out)[j * 4 + 2] = out[2] * 255;
|
|
}
|
|
(*pic_out)[j * 4 + 3] = 255;
|
|
|
|
sumIntensity += intensity;
|
|
}
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
for(j = 0; j < width * height; j++)
|
|
{
|
|
R_ColorShiftLightingBytes(&(*pic)[j * in_padding], &(*pic_out)[j * 4]);
|
|
(*pic_out)[j * 4 + 3] = 255;
|
|
}
|
|
}
|
|
|
|
return maxIntensity;
|
|
}
|
|
#endif
|
|
|
|
static int QDECL LightmapNameCompare(const void *a, const void *b)
|
|
{
|
|
char *s1, *s2;
|
|
int c1, c2;
|
|
|
|
s1 = *(char **)a;
|
|
s2 = *(char **)b;
|
|
|
|
do
|
|
{
|
|
c1 = *s1++;
|
|
c2 = *s2++;
|
|
|
|
if(c1 >= 'a' && c1 <= 'z')
|
|
{
|
|
c1 -= ('a' - 'A');
|
|
}
|
|
if(c2 >= 'a' && c2 <= 'z')
|
|
{
|
|
c2 -= ('a' - 'A');
|
|
}
|
|
|
|
if(c1 == '\\' || c1 == ':')
|
|
{
|
|
c1 = '/';
|
|
}
|
|
if(c2 == '\\' || c2 == ':')
|
|
{
|
|
c2 = '/';
|
|
}
|
|
|
|
if(c1 < c2)
|
|
{
|
|
// strings not equal
|
|
return -1;
|
|
}
|
|
if(c1 > c2)
|
|
{
|
|
return 1;
|
|
}
|
|
} while(c1);
|
|
|
|
// strings are equal
|
|
return 0;
|
|
}
|
|
|
|
/* standard conversion from rgbe to float pixels */
|
|
/* note: Ward uses ldexp(col+0.5,exp-(128+8)). However we wanted pixels */
|
|
/* in the range [0,1] to map back into the range [0,1]. */
|
|
static ID_INLINE void rgbe2float(float *red, float *green, float *blue, unsigned char rgbe[4])
|
|
{
|
|
float e;
|
|
float f;
|
|
|
|
if(rgbe[3])
|
|
{ /*nonzero pixel */
|
|
f = ldexp(1.0, rgbe[3] - (int)(128 + 8));
|
|
//f = ldexp(1.0, rgbe[3] - 128) / 10.0;
|
|
e = (rgbe[3] - 128) / 4.0f;
|
|
|
|
// RB: exp2 not defined by MSVC
|
|
//f = exp2(e);
|
|
f = pow(2, e);
|
|
|
|
//decoded = rgbe.rgb * exp2(fExp);
|
|
*red = (rgbe[0] / 255.0f) * f;
|
|
*green = (rgbe[1] / 255.0f) * f;
|
|
*blue = (rgbe[2] / 255.0f) * f;
|
|
}
|
|
else
|
|
*red = *green = *blue = 0.0;
|
|
}
|
|
|
|
void LoadRGBEToFloats(const char *name, float **pic, int *width, int *height, qboolean doGamma, qboolean toneMap,
|
|
qboolean compensate)
|
|
{
|
|
int i, j;
|
|
byte *buf_p;
|
|
byte *buffer;
|
|
float *floatbuf;
|
|
int len;
|
|
char *token;
|
|
int w, h, c;
|
|
qboolean formatFound;
|
|
//unsigned char rgbe[4];
|
|
//float red;
|
|
//float green;
|
|
//float blue;
|
|
//float max;
|
|
//float inv, dif;
|
|
float exposure = 1.6;
|
|
//float exposureGain = 1.0;
|
|
const vec3_t LUMINANCE_VECTOR = { 0.2125f, 0.7154f, 0.0721f };
|
|
float luminance;
|
|
float avgLuminance;
|
|
float maxLuminance;
|
|
float scaledLuminance;
|
|
float finalLuminance;
|
|
double sum;
|
|
float gamma;
|
|
|
|
union
|
|
{
|
|
byte b[4];
|
|
float f;
|
|
} sample;
|
|
vec4_t sampleVector;
|
|
|
|
*pic = NULL;
|
|
|
|
// load the file
|
|
len = ri.FS_ReadFile((char *)name, (void **)&buffer);
|
|
if(!buffer)
|
|
{
|
|
ri.Error(ERR_DROP, "LoadRGBE: '%s' not found\n", name);
|
|
return;
|
|
}
|
|
|
|
buf_p = buffer;
|
|
|
|
formatFound = qfalse;
|
|
w = h = 0;
|
|
while(qtrue)
|
|
{
|
|
token = COM_ParseExt((char **)&buf_p, qtrue);
|
|
if(!token[0])
|
|
break;
|
|
|
|
if(!Q_stricmp(token, "FORMAT"))
|
|
{
|
|
//ri.Printf(PRINT_ALL, "LoadRGBE: FORMAT found\n");
|
|
|
|
token = COM_ParseExt((char **)&buf_p, qfalse);
|
|
if(!Q_stricmp(token, "="))
|
|
{
|
|
token = COM_ParseExt((char **)&buf_p, qfalse);
|
|
if(!Q_stricmp(token, "32"))
|
|
{
|
|
token = COM_ParseExt((char **)&buf_p, qfalse);
|
|
if(!Q_stricmp(token, "-"))
|
|
{
|
|
token = COM_ParseExt((char **)&buf_p, qfalse);
|
|
if(!Q_stricmp(token, "bit_rle_rgbe"))
|
|
{
|
|
formatFound = qtrue;
|
|
}
|
|
else
|
|
{
|
|
ri.Printf(PRINT_ALL, "LoadRGBE: Expected 'bit_rle_rgbe' found instead '%s'\n", token);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ri.Printf(PRINT_ALL, "LoadRGBE: Expected '-' found instead '%s'\n", token);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ri.Printf(PRINT_ALL, "LoadRGBE: Expected '32' found instead '%s'\n", token);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ri.Printf(PRINT_ALL, "LoadRGBE: Expected '=' found instead '%s'\n", token);
|
|
}
|
|
}
|
|
|
|
if(!Q_stricmp(token, "-"))
|
|
{
|
|
token = COM_ParseExt((char **)&buf_p, qfalse);
|
|
if(!Q_stricmp(token, "Y"))
|
|
{
|
|
token = COM_ParseExt((char **)&buf_p, qfalse);
|
|
w = atoi(token);
|
|
|
|
token = COM_ParseExt((char **)&buf_p, qfalse);
|
|
if(!Q_stricmp(token, "+"))
|
|
{
|
|
token = COM_ParseExt((char **)&buf_p, qfalse);
|
|
if(!Q_stricmp(token, "X"))
|
|
{
|
|
token = COM_ParseExt((char **)&buf_p, qfalse);
|
|
h = atoi(token);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
ri.Printf(PRINT_ALL, "LoadRGBE: Expected 'X' found instead '%s'\n", token);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ri.Printf(PRINT_ALL, "LoadRGBE: Expected '+' found instead '%s'\n", token);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ri.Printf(PRINT_ALL, "LoadRGBE: Expected 'Y' found instead '%s'\n", token);
|
|
}
|
|
}
|
|
}
|
|
|
|
// go to the first byte
|
|
while((c = *buf_p++) != 0)
|
|
{
|
|
if(c == '\n')
|
|
{
|
|
//buf_p++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(width)
|
|
*width = w;
|
|
if(height)
|
|
*height = h;
|
|
|
|
if(!formatFound)
|
|
{
|
|
ri.Error(ERR_DROP, "LoadRGBE: %s has no format\n", name);
|
|
}
|
|
|
|
if(!w || !h)
|
|
{
|
|
ri.Error(ERR_DROP, "LoadRGBE: %s has an invalid image size\n", name);
|
|
}
|
|
|
|
*pic = Com_Allocate(w * h * 3 * sizeof(float));
|
|
floatbuf = *pic;
|
|
for(i = 0; i < (w * h); i++)
|
|
{
|
|
#if 0
|
|
rgbe[0] = *buf_p++;
|
|
rgbe[1] = *buf_p++;
|
|
rgbe[2] = *buf_p++;
|
|
rgbe[3] = *buf_p++;
|
|
|
|
rgbe2float(&red, &green, &blue, rgbe);
|
|
|
|
*floatbuf++ = red;
|
|
*floatbuf++ = green;
|
|
*floatbuf++ = blue;
|
|
#else
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
sample.b[0] = *buf_p++;
|
|
sample.b[1] = *buf_p++;
|
|
sample.b[2] = *buf_p++;
|
|
sample.b[3] = *buf_p++;
|
|
|
|
*floatbuf++ = sample.f / 255.0f; // FIXME XMap2's output is 255 times too high
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// LOADING DONE
|
|
if(doGamma)
|
|
{
|
|
floatbuf = *pic;
|
|
gamma = 1.0f / r_hdrLightmapGamma->value;
|
|
for(i = 0; i < (w * h); i++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
//*floatbuf = pow(*floatbuf / 255.0f, gamma) * 255.0f;
|
|
*floatbuf = pow(*floatbuf, gamma);
|
|
floatbuf++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(toneMap)
|
|
{
|
|
// calculate the average and maximum luminance
|
|
sum = 0.0f;
|
|
maxLuminance = 0.0f;
|
|
floatbuf = *pic;
|
|
for(i = 0; i < (w * h); i++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
sampleVector[j] = *floatbuf++;
|
|
}
|
|
|
|
luminance = DotProduct(sampleVector, LUMINANCE_VECTOR) + 0.0001f;
|
|
if(luminance > maxLuminance)
|
|
maxLuminance = luminance;
|
|
|
|
sum += log(luminance);
|
|
}
|
|
sum /= (float)(w * h);
|
|
avgLuminance = exp(sum);
|
|
|
|
// post process buffer with tone mapping
|
|
floatbuf = *pic;
|
|
for(i = 0; i < (w * h); i++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
sampleVector[j] = *floatbuf++;
|
|
}
|
|
|
|
if(r_hdrLightmapExposure->value <= 0)
|
|
{
|
|
exposure = (r_hdrKey->value / avgLuminance);
|
|
}
|
|
else
|
|
{
|
|
exposure = r_hdrLightmapExposure->value;
|
|
}
|
|
//
|
|
|
|
scaledLuminance = exposure * DotProduct(sampleVector, LUMINANCE_VECTOR);
|
|
#if 0
|
|
finalLuminance = scaledLuminance / (scaledLuminance + 1.0);
|
|
#elif 0
|
|
finalLuminance = (scaledLuminance * (scaledLuminance / maxLuminance + 1.0)) / (scaledLuminance + 1.0);
|
|
#elif 0
|
|
finalLuminance =
|
|
(scaledLuminance * ((scaledLuminance / (maxLuminance * maxLuminance)) + 1.0)) / (scaledLuminance + 1.0);
|
|
#else
|
|
// exponential tone mapping
|
|
finalLuminance = 1.0 - exp(-scaledLuminance);
|
|
#endif
|
|
|
|
//VectorScale(sampleVector, scaledLuminance * (scaledLuminance / maxLuminance + 1.0) / (scaledLuminance + 1.0), sampleVector);
|
|
//VectorScale(sampleVector, scaledLuminance / (scaledLuminance + 1.0), sampleVector);
|
|
|
|
VectorScale(sampleVector, finalLuminance, sampleVector);
|
|
|
|
floatbuf -= 3;
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
*floatbuf++ = sampleVector[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
if(compensate)
|
|
{
|
|
floatbuf = *pic;
|
|
for(i = 0; i < (w * h); i++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
*floatbuf = *floatbuf / r_hdrLightmapCompensate->value;
|
|
floatbuf++;
|
|
}
|
|
}
|
|
}
|
|
|
|
ri.FS_FreeFile(buffer);
|
|
}
|
|
|
|
|
|
static void LoadRGBEToBytes(const char *name, byte ** ldrImage, int *width, int *height)
|
|
{
|
|
int i, j;
|
|
int w, h;
|
|
float *hdrImage;
|
|
float *floatbuf;
|
|
byte *pixbuf;
|
|
vec3_t sample;
|
|
float max;
|
|
|
|
#if 0
|
|
w = h = 0;
|
|
LoadRGBEToFloats(name, &hdrImage, &w, &h, qtrue, qtrue, qtrue);
|
|
|
|
*width = w;
|
|
*height = h;
|
|
|
|
*ldrImage = ri.Malloc(w * h * 4);
|
|
pixbuf = *ldrImage;
|
|
|
|
floatbuf = hdrImage;
|
|
for(i = 0; i < (w * h); i++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
sample[j] = *floatbuf++;
|
|
}
|
|
|
|
NormalizeColor(sample, sample);
|
|
|
|
*pixbuf++ = (byte) (sample[0] * 255);
|
|
*pixbuf++ = (byte) (sample[1] * 255);
|
|
*pixbuf++ = (byte) (sample[2] * 255);
|
|
*pixbuf++ = (byte) 255;
|
|
}
|
|
#else
|
|
w = h = 0;
|
|
LoadRGBEToFloats(name, &hdrImage, &w, &h, qfalse, qfalse, qfalse);
|
|
|
|
*width = w;
|
|
*height = h;
|
|
|
|
*ldrImage = ri.Malloc(w * h * 4);
|
|
pixbuf = *ldrImage;
|
|
|
|
floatbuf = hdrImage;
|
|
for(i = 0; i < (w * h); i++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
sample[j] = *floatbuf++ * 255.0f;
|
|
}
|
|
|
|
// clamp with color normalization
|
|
max = sample[0];
|
|
if(sample[1] > max)
|
|
max = sample[1];
|
|
if(sample[2] > max)
|
|
max = sample[2];
|
|
if(max > 255.0f)
|
|
VectorScale(sample, (255.0f / max), sample);
|
|
|
|
*pixbuf++ = (byte) sample[0];
|
|
*pixbuf++ = (byte) sample[1];
|
|
*pixbuf++ = (byte) sample[2];
|
|
*pixbuf++ = (byte) 255;
|
|
}
|
|
#endif
|
|
|
|
Com_Dealloc(hdrImage);
|
|
}
|
|
|
|
void LoadRGBEToHalfs(const char *name, unsigned short ** halfImage, int *width, int *height);
|
|
|
|
/*
|
|
===============
|
|
R_LoadLightmaps
|
|
===============
|
|
*/
|
|
#define LIGHTMAP_SIZE 128
|
|
static void R_LoadLightmaps(lump_t * l, const char *bspName)
|
|
{
|
|
int len;
|
|
image_t *image;
|
|
int i;
|
|
int numLightmaps;
|
|
|
|
len = l->filelen;
|
|
if(!len)
|
|
{
|
|
char mapName[MAX_QPATH];
|
|
char **lightmapFiles;
|
|
|
|
Q_strncpyz(mapName, bspName, sizeof(mapName));
|
|
COM_StripExtension(mapName, mapName, sizeof(mapName));
|
|
|
|
if(tr.worldHDR_RGBE)
|
|
{
|
|
// we are about to upload textures
|
|
R_SyncRenderThread();
|
|
|
|
// load HDR lightmaps
|
|
lightmapFiles = ri.FS_ListFiles(mapName, ".hdr", qfalse, &numLightmaps);
|
|
|
|
qsort(lightmapFiles, numLightmaps, sizeof(char *), LightmapNameCompare);
|
|
|
|
if(!lightmapFiles || !numLightmaps)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: no lightmap files found\n");
|
|
return;
|
|
}
|
|
|
|
ri.Printf(PRINT_ALL, "...loading %i HDR lightmaps\n", numLightmaps);
|
|
|
|
if(r_hdrRendering->integer && r_hdrLightmap->integer && glConfig2.framebufferObjectAvailable &&
|
|
glConfig2.framebufferBlitAvailable && glConfig2.textureFloatAvailable && glConfig2.textureHalfFloatAvailable)
|
|
{
|
|
int width, height;
|
|
unsigned short *hdrImage;
|
|
|
|
for(i = 0; i < numLightmaps; i++)
|
|
{
|
|
ri.Printf(PRINT_ALL, "...loading external lightmap as RGB 16 bit half HDR '%s/%s'\n", mapName, lightmapFiles[i]);
|
|
|
|
width = height = 0;
|
|
LoadRGBEToFloats(va("%s/%s", mapName, lightmapFiles[i]), &hdrImage, &width, &height, qtrue, qfalse, qtrue);
|
|
//LoadRGBEToHalfs(va("%s/%s", mapName, lightmapFiles[i]), &hdrImage, &width, &height);
|
|
|
|
//ri.Printf(PRINT_ALL, "...converted '%s/%s' to HALF format\n", mapName, lightmapFiles[i]);
|
|
|
|
image = R_AllocImage(va("%s/%s", mapName, lightmapFiles[i]), qtrue);
|
|
|
|
if(!image) {
|
|
Com_Dealloc(hdrImage);
|
|
break;
|
|
}
|
|
|
|
//Q_strncpyz(image->name, );
|
|
image->type = GL_TEXTURE_2D;
|
|
|
|
image->width = width;
|
|
image->height = height;
|
|
|
|
image->bits = IF_NOPICMIP | IF_RGBA16F;
|
|
image->filterType = FT_NEAREST;
|
|
image->wrapType = WT_CLAMP;
|
|
|
|
GL_Bind(image);
|
|
|
|
image->internalFormat = GL_RGBA16F_ARB;
|
|
image->uploadWidth = width;
|
|
image->uploadHeight = height;
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F_ARB, width, height, 0, GL_RGB, GL_HALF_FLOAT_ARB, hdrImage);
|
|
|
|
if(glConfig2.generateMipmapAvailable)
|
|
{
|
|
//glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST); // make sure its nice
|
|
glTexParameteri(image->type, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
|
|
glTexParameteri(image->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // default to trilinear
|
|
}
|
|
|
|
#if 0
|
|
if(glConfig.hardwareType == GLHW_NV_DX10 || glConfig.hardwareType == GLHW_ATI_DX10)
|
|
{
|
|
glTexParameterf(image->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameterf(image->type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
glTexParameterf(image->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameterf(image->type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
}
|
|
glTexParameterf(image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameterf(image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glBindTexture(image->type, 0);
|
|
|
|
GL_CheckErrors();
|
|
|
|
Com_Dealloc(hdrImage);
|
|
|
|
Com_AddToGrowList(&tr.lightmaps, image);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int width, height;
|
|
byte *ldrImage;
|
|
|
|
for(i = 0; i < numLightmaps; i++)
|
|
{
|
|
ri.Printf(PRINT_ALL, "...loading external lightmap as RGB8 LDR '%s/%s'\n", mapName, lightmapFiles[i]);
|
|
|
|
width = height = 0;
|
|
LoadRGBEToBytes(va("%s/%s", mapName, lightmapFiles[i]), &ldrImage, &width, &height);
|
|
|
|
image = R_CreateImage(va("%s/%s", mapName, lightmapFiles[i]), (byte *) ldrImage, width, height,
|
|
IF_NOPICMIP | IF_LIGHTMAP | IF_NOCOMPRESSION, FT_DEFAULT, WT_CLAMP);
|
|
|
|
Com_AddToGrowList(&tr.lightmaps, image);
|
|
|
|
ri.Free(ldrImage);
|
|
}
|
|
}
|
|
|
|
if(tr.worldDeluxeMapping)
|
|
{
|
|
// load deluxemaps
|
|
lightmapFiles = ri.FS_ListFiles( mapName, ".png", qfalse, &numLightmaps );
|
|
|
|
if(!lightmapFiles || !numLightmaps)
|
|
{
|
|
lightmapFiles = ri.FS_ListFiles( mapName, ".tga", qfalse, &numLightmaps );
|
|
|
|
if(!lightmapFiles || !numLightmaps)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: no lightmap files found\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
qsort(lightmapFiles, numLightmaps, sizeof(char *), LightmapNameCompare);
|
|
|
|
ri.Printf(PRINT_ALL, "...loading %i deluxemaps\n", numLightmaps);
|
|
|
|
for(i = 0; i < numLightmaps; i++)
|
|
{
|
|
ri.Printf(PRINT_ALL, "...loading external lightmap '%s/%s'\n", mapName, lightmapFiles[i]);
|
|
|
|
image = R_FindImageFile(va("%s/%s", mapName, lightmapFiles[i]), IF_NORMALMAP | IF_NOCOMPRESSION, FT_DEFAULT, WT_CLAMP, NULL);
|
|
Com_AddToGrowList(&tr.deluxemaps, image);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lightmapFiles = ri.FS_ListFiles( mapName, ".png", qfalse, &numLightmaps );
|
|
|
|
if(!lightmapFiles || !numLightmaps)
|
|
{
|
|
lightmapFiles = ri.FS_ListFiles( mapName, ".tga", qfalse, &numLightmaps );
|
|
|
|
if(!lightmapFiles || !numLightmaps)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: no lightmap files found\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
qsort(lightmapFiles, numLightmaps, sizeof(char *), LightmapNameCompare);
|
|
|
|
ri.Printf(PRINT_ALL, "...loading %i lightmaps\n", numLightmaps);
|
|
|
|
// we are about to upload textures
|
|
R_SyncRenderThread();
|
|
|
|
for(i = 0; i < numLightmaps; i++)
|
|
{
|
|
ri.Printf(PRINT_ALL, "...loading external lightmap '%s/%s'\n", mapName, lightmapFiles[i]);
|
|
|
|
if(tr.worldDeluxeMapping)
|
|
{
|
|
if(i % 2 == 0)
|
|
{
|
|
image = R_FindImageFile(va("%s/%s", mapName, lightmapFiles[i]), IF_LIGHTMAP | IF_NOCOMPRESSION, FT_LINEAR, WT_CLAMP, NULL);
|
|
Com_AddToGrowList(&tr.lightmaps, image);
|
|
}
|
|
else
|
|
{
|
|
image = R_FindImageFile(va("%s/%s", mapName, lightmapFiles[i]), IF_NORMALMAP | IF_NOCOMPRESSION, FT_LINEAR, WT_CLAMP, NULL);
|
|
Com_AddToGrowList(&tr.deluxemaps, image);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
image = R_FindImageFile(va("%s/%s", mapName, lightmapFiles[i]), IF_LIGHTMAP | IF_NOCOMPRESSION, FT_LINEAR, WT_CLAMP, NULL);
|
|
Com_AddToGrowList(&tr.lightmaps, image);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if 0 //defined(COMPAT_Q3A)
|
|
else
|
|
{
|
|
static byte data[LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4];
|
|
|
|
buf = fileBase + l->fileofs;
|
|
|
|
// we are about to upload textures
|
|
R_SyncRenderThread();
|
|
|
|
// create all the lightmaps
|
|
tr.numLightmaps = len / (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3);
|
|
|
|
ri.Printf(PRINT_ALL, "...loading %i lightmaps\n", tr.numLightmaps);
|
|
|
|
for(i = 0; i < tr.numLightmaps; i++)
|
|
{
|
|
// expand the 24 bit on-disk to 32 bit
|
|
buf_p = buf + i * LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3;
|
|
|
|
if(tr.worldDeluxeMapping)
|
|
{
|
|
if(i % 2 == 0)
|
|
{
|
|
for(j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++)
|
|
{
|
|
R_ColorShiftLightingBytes(&buf_p[j * 3], &data[j * 4]);
|
|
data[j * 4 + 3] = 255;
|
|
}
|
|
image = R_CreateImage(va("_lightmap%d", i), data, LIGHTMAP_SIZE, LIGHTMAP_SIZE, IF_LIGHTMAP | IF_NOCOMPRESSION, FT_DEFAULT, WT_CLAMP);
|
|
Com_AddToGrowList(&tr.lightmaps, image);
|
|
}
|
|
else
|
|
{
|
|
for(j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++)
|
|
{
|
|
data[j * 4 + 0] = buf_p[j * 3 + 0];
|
|
data[j * 4 + 1] = buf_p[j * 3 + 1];
|
|
data[j * 4 + 2] = buf_p[j * 3 + 2];
|
|
data[j * 4 + 3] = 255;
|
|
}
|
|
image = R_CreateImage(va("_lightmap%d", i), data, LIGHTMAP_SIZE, LIGHTMAP_SIZE, IF_NORMALMAP, FT_DEFAULT, WT_CLAMP);
|
|
Com_AddToGrowList(&tr.deluxemaps, image);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++)
|
|
{
|
|
R_ColorShiftLightingBytes(&buf_p[j * 3], &data[j * 4]);
|
|
data[j * 4 + 3] = 255;
|
|
}
|
|
image = R_CreateImage(va("_lightmap%d", i), data, LIGHTMAP_SIZE, LIGHTMAP_SIZE, IF_LIGHTMAP | IF_NOCOMPRESSION, FT_DEFAULT, WT_CLAMP);
|
|
Com_AddToGrowList(&tr.lightmaps, image);
|
|
}
|
|
}
|
|
}
|
|
#elif defined(COMPAT_Q3A)
|
|
else
|
|
{
|
|
int i;
|
|
byte *buf, *buf_p;
|
|
|
|
//int BIGSIZE=2048;
|
|
//int BIGNUM=16;
|
|
|
|
byte *fatbuffer;
|
|
int xoff, yoff, x, y;
|
|
//float scale = 0.9f;
|
|
|
|
tr.fatLightmapSize = 2048;
|
|
tr.fatLightmapStep = 16;
|
|
|
|
len = l->filelen;
|
|
if(!len)
|
|
{
|
|
return;
|
|
}
|
|
buf = fileBase + l->fileofs;
|
|
|
|
// we are about to upload textures
|
|
R_SyncRenderThread();
|
|
|
|
// create all the lightmaps
|
|
numLightmaps = len / (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3);
|
|
if(numLightmaps == 1)
|
|
{
|
|
//FIXME: HACK: maps with only one lightmap turn up fullbright for some reason.
|
|
//this avoids this, but isn't the correct solution.
|
|
numLightmaps++;
|
|
}
|
|
else if(numLightmaps >= MAX_LIGHTMAPS)
|
|
{
|
|
// 20051020 misantropia
|
|
ri.Printf(PRINT_WARNING, "WARNING: number of lightmaps > MAX_LIGHTMAPS\n");
|
|
numLightmaps = MAX_LIGHTMAPS;
|
|
}
|
|
|
|
if(numLightmaps < 65)
|
|
{
|
|
// optimize: use a 1024 if we can get away with it
|
|
tr.fatLightmapSize = 1024;
|
|
tr.fatLightmapStep = 8;
|
|
|
|
}
|
|
fatbuffer = ri.Malloc(sizeof(byte) * tr.fatLightmapSize * tr.fatLightmapSize * 4);
|
|
|
|
Com_Memset(fatbuffer, 128, tr.fatLightmapSize * tr.fatLightmapSize * 4);
|
|
for(i = 0; i < numLightmaps; i++)
|
|
{
|
|
// expand the 24 bit on-disk to 32 bit
|
|
buf_p = buf + i * LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3;
|
|
|
|
xoff = i % tr.fatLightmapStep;
|
|
yoff = i / tr.fatLightmapStep;
|
|
|
|
//if (tr.radbumping==qfalse)
|
|
if(1)
|
|
{
|
|
for(y = 0; y < LIGHTMAP_SIZE; y++)
|
|
{
|
|
for(x = 0; x < LIGHTMAP_SIZE; x++)
|
|
{
|
|
int index =
|
|
(x + (y * tr.fatLightmapSize)) + ((xoff * LIGHTMAP_SIZE) + (yoff * tr.fatLightmapSize * LIGHTMAP_SIZE));
|
|
fatbuffer[(index * 4) + 0] = buf_p[((x + (y * LIGHTMAP_SIZE)) * 3) + 0];
|
|
fatbuffer[(index * 4) + 1] = buf_p[((x + (y * LIGHTMAP_SIZE)) * 3) + 1];
|
|
fatbuffer[(index * 4) + 2] = buf_p[((x + (y * LIGHTMAP_SIZE)) * 3) + 2];
|
|
fatbuffer[(index * 4) + 3] = 255;
|
|
|
|
R_ColorShiftLightingBytes(&fatbuffer[(index * 4) + 0], &fatbuffer[(index * 4) + 0]);
|
|
}
|
|
}
|
|
}
|
|
/*else
|
|
{
|
|
//We need to darken the lightmaps a little bit when mixing radbump and fallback path rendering
|
|
//because radbump will be darker due to the error introduced by using 3 basis vector probes for lighting instead of surf normal.
|
|
for ( y = 0 ; y < LIGHTMAP_SIZE ; y++ )
|
|
{
|
|
for ( x = 0 ; x < LIGHTMAP_SIZE ; x++ )
|
|
{
|
|
int index = (x+(y*tr.fatLightmapSize))+((xoff*LIGHTMAP_SIZE)+(yoff*tr.fatLightmapSize*LIGHTMAP_SIZE));
|
|
fatbuffer[(index*4)+0 ]=(byte)(((float)buf_p[((x+(y*LIGHTMAP_SIZE))*3)+0])*scale);
|
|
fatbuffer[(index*4)+1 ]=(byte)(((float)buf_p[((x+(y*LIGHTMAP_SIZE))*3)+1])*scale);
|
|
fatbuffer[(index*4)+2 ]=(byte)(((float)buf_p[((x+(y*LIGHTMAP_SIZE))*3)+2])*scale);
|
|
fatbuffer[(index*4)+3 ]=255;
|
|
}
|
|
}
|
|
|
|
} */
|
|
|
|
|
|
}
|
|
//memset(fatbuffer,128,tr.fatLightmapSize*tr.fatLightmapSize*4);
|
|
|
|
tr.fatLightmap = R_CreateImage(va("_fatlightmap%d", 0), fatbuffer, tr.fatLightmapSize, tr.fatLightmapSize, IF_LIGHTMAP | IF_NOCOMPRESSION, FT_DEFAULT, WT_CLAMP);
|
|
Com_AddToGrowList(&tr.lightmaps, tr.fatLightmap);
|
|
|
|
ri.Free(fatbuffer);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(COMPAT_Q3A)
|
|
static float FatPackU(float input, int lightmapnum)
|
|
{
|
|
if(tr.fatLightmapSize > 0)
|
|
{
|
|
int x = lightmapnum % tr.fatLightmapStep;
|
|
|
|
return (input / ((float)tr.fatLightmapStep)) + ((1.0 / ((float)tr.fatLightmapStep)) * (float)x);
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
static float FatPackV(float input, int lightmapnum)
|
|
{
|
|
if(tr.fatLightmapSize > 0)
|
|
{
|
|
int y = lightmapnum / ((float)tr.fatLightmapStep);
|
|
|
|
return (input / ((float)tr.fatLightmapStep)) + ((1.0 / ((float)tr.fatLightmapStep)) * (float)y);
|
|
}
|
|
|
|
return input;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
=================
|
|
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;
|
|
|
|
ri.Printf(PRINT_ALL, "...loading visibility\n");
|
|
|
|
len = (s_worldData.numClusters + 63) & ~63;
|
|
s_worldData.novis = ri.Hunk_Alloc(len, h_low);
|
|
Com_Memset(s_worldData.novis, 0xff, len);
|
|
|
|
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;
|
|
|
|
dest = 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)
|
|
{
|
|
shader_t *shader;
|
|
dshader_t *dsh;
|
|
|
|
shaderNum = LittleLong(shaderNum);
|
|
if(shaderNum < 0 || shaderNum >= s_worldData.numShaders)
|
|
{
|
|
ri.Error(ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum);
|
|
}
|
|
dsh = &s_worldData.shaders[shaderNum];
|
|
|
|
// ri.Printf(PRINT_ALL, "ShaderForShaderNum: '%s'\n", dsh->shader);
|
|
|
|
shader = R_FindShader(dsh->shader, SHADER_3D_STATIC, qtrue);
|
|
|
|
// if the shader had errors, just use default shader
|
|
if(shader->defaultShader)
|
|
{
|
|
// ri.Printf(PRINT_ALL, "failed\n");
|
|
return tr.defaultShader;
|
|
}
|
|
|
|
// ri.Printf(PRINT_ALL, "success\n");
|
|
return shader;
|
|
}
|
|
|
|
|
|
/*
|
|
SphereFromBounds() - ydnar
|
|
creates a bounding sphere from a bounding box
|
|
*/
|
|
|
|
static void SphereFromBounds(vec3_t mins, vec3_t maxs, vec3_t origin, float *radius)
|
|
{
|
|
vec3_t temp;
|
|
|
|
VectorAdd(mins, maxs, origin);
|
|
VectorScale(origin, 0.5, origin);
|
|
VectorSubtract(maxs, origin, temp);
|
|
*radius = VectorLength(temp);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
FinishGenericSurface() - ydnar
|
|
handles final surface classification
|
|
*/
|
|
|
|
static void FinishGenericSurface(dsurface_t * ds, srfGeneric_t * gen, vec3_t pt)
|
|
{
|
|
// set bounding sphere
|
|
SphereFromBounds(gen->bounds[0], gen->bounds[1], gen->origin, &gen->radius);
|
|
|
|
// take the plane normal from the lightmap vector and classify it
|
|
gen->plane.normal[0] = LittleFloat(ds->lightmapVecs[2][0]);
|
|
gen->plane.normal[1] = LittleFloat(ds->lightmapVecs[2][1]);
|
|
gen->plane.normal[2] = LittleFloat(ds->lightmapVecs[2][2]);
|
|
gen->plane.dist = DotProduct(pt, gen->plane.normal);
|
|
SetPlaneSignbits(&gen->plane);
|
|
gen->plane.type = PlaneTypeForNormal(gen->plane.normal);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
ParseFace
|
|
===============
|
|
*/
|
|
static void ParseFace(dsurface_t * ds, drawVert_t * verts, bspSurface_t * surf, int *indexes)
|
|
{
|
|
int i, j;
|
|
srfSurfaceFace_t *cv;
|
|
srfTriangle_t *tri;
|
|
int numVerts, numTriangles;
|
|
int realLightmapNum;
|
|
|
|
// get lightmap
|
|
realLightmapNum = LittleLong(ds->lightmapNum);
|
|
if(r_vertexLighting->integer || !r_precomputedLighting->integer)
|
|
{
|
|
surf->lightmapNum = -1;
|
|
}
|
|
else
|
|
{
|
|
#if defined(COMPAT_Q3A)
|
|
surf->lightmapNum = 0;
|
|
#else
|
|
surf->lightmapNum = realLightmapNum;
|
|
#endif
|
|
}
|
|
|
|
if(tr.worldDeluxeMapping && surf->lightmapNum >= 2)
|
|
{
|
|
surf->lightmapNum /= 2;
|
|
}
|
|
|
|
/*
|
|
if(surf->lightmapNum >= tr.lightmaps.currentElements)
|
|
{
|
|
ri.Error(ERR_DROP, "Bad lightmap number %i in face surface", surf->lightmapNum);
|
|
}
|
|
*/
|
|
|
|
// get fog volume
|
|
surf->fogIndex = LittleLong(ds->fogNum) + 1;
|
|
|
|
// get shader value
|
|
surf->shader = ShaderForShaderNum(ds->shaderNum);
|
|
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;
|
|
}
|
|
*/
|
|
numTriangles = LittleLong(ds->numIndexes) / 3;
|
|
|
|
cv = ri.Hunk_Alloc(sizeof(*cv), h_low);
|
|
cv->surfaceType = SF_FACE;
|
|
|
|
cv->numTriangles = numTriangles;
|
|
cv->triangles = ri.Hunk_Alloc(numTriangles * sizeof(cv->triangles[0]), h_low);
|
|
|
|
cv->numVerts = numVerts;
|
|
cv->verts = ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low);
|
|
|
|
surf->data = (surfaceType_t *) cv;
|
|
|
|
// copy vertexes
|
|
ClearBounds(cv->bounds[0], cv->bounds[1]);
|
|
verts += LittleLong(ds->firstVert);
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
cv->verts[i].xyz[j] = LittleFloat(verts[i].xyz[j]);
|
|
cv->verts[i].normal[j] = LittleFloat(verts[i].normal[j]);
|
|
}
|
|
AddPointToBounds(cv->verts[i].xyz, cv->bounds[0], cv->bounds[1]);
|
|
for(j = 0; j < 2; j++)
|
|
{
|
|
cv->verts[i].st[j] = LittleFloat(verts[i].st[j]);
|
|
cv->verts[i].lightmap[j] = LittleFloat(verts[i].lightmap[j]);
|
|
}
|
|
|
|
#if defined(COMPAT_Q3A)
|
|
cv->verts[i].lightmap[0] = FatPackU(LittleFloat(verts[i].lightmap[0]), realLightmapNum);
|
|
cv->verts[i].lightmap[1] = FatPackV(LittleFloat(verts[i].lightmap[1]), realLightmapNum);
|
|
|
|
for(j = 0; j < 4; j++)
|
|
{
|
|
cv->verts[i].lightColor[j] = verts[i].color[j] * (1.0f / 255.0f);
|
|
}
|
|
R_ColorShiftLightingFloats(cv->verts[i].lightColor, cv->verts[i].lightColor);
|
|
|
|
#elif defined(COMPAT_Q3A) || defined(COMPAT_ET)
|
|
for(j = 0; j < 4; j++)
|
|
{
|
|
cv->verts[i].lightColor[j] = verts[i].color[j] * (1.0f / 255.0f);
|
|
}
|
|
R_ColorShiftLightingFloats(cv->verts[i].lightColor, cv->verts[i].lightColor);
|
|
#else
|
|
for(j = 0; j < 4; j++)
|
|
{
|
|
cv->verts[i].paintColor[j] = Q_bound(0, LittleFloat(verts[i].paintColor[j]), 1);
|
|
cv->verts[i].lightColor[j] = LittleFloat(verts[i].lightColor[j]);
|
|
}
|
|
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
cv->verts[i].lightDirection[j] = LittleFloat(verts[i].lightDirection[j]);
|
|
}
|
|
//VectorNormalize(cv->verts[i].lightDirection);
|
|
|
|
R_HDRTonemapLightingColors(cv->verts[i].lightColor, cv->verts[i].lightColor, qtrue);
|
|
#endif
|
|
}
|
|
|
|
// copy triangles
|
|
indexes += LittleLong(ds->firstIndex);
|
|
for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
tri->indexes[j] = LittleLong(indexes[i * 3 + j]);
|
|
|
|
if(tri->indexes[j] < 0 || tri->indexes[j] >= numVerts)
|
|
{
|
|
ri.Error(ERR_DROP, "Bad index in face surface");
|
|
}
|
|
}
|
|
}
|
|
|
|
R_CalcSurfaceTriangleNeighbors(numTriangles, cv->triangles);
|
|
R_CalcSurfaceTrianglePlanes(numTriangles, cv->triangles, cv->verts);
|
|
|
|
// take the plane information from the lightmap vector
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
cv->plane.normal[i] = LittleFloat(ds->lightmapVecs[2][i]);
|
|
}
|
|
cv->plane.dist = DotProduct(cv->verts[0].xyz, cv->plane.normal);
|
|
SetPlaneSignbits(&cv->plane);
|
|
cv->plane.type = PlaneTypeForNormal(cv->plane.normal);
|
|
|
|
surf->data = (surfaceType_t *) cv;
|
|
|
|
// Tr3B - calc tangent spaces
|
|
#if 0
|
|
{
|
|
float *v;
|
|
const float *v0, *v1, *v2;
|
|
const float *t0, *t1, *t2;
|
|
vec3_t tangent;
|
|
vec3_t binormal;
|
|
vec3_t normal;
|
|
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
VectorClear(cv->verts[i].tangent);
|
|
VectorClear(cv->verts[i].binormal);
|
|
VectorClear(cv->verts[i].normal);
|
|
}
|
|
|
|
for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
v0 = cv->verts[tri->indexes[0]].xyz;
|
|
v1 = cv->verts[tri->indexes[1]].xyz;
|
|
v2 = cv->verts[tri->indexes[2]].xyz;
|
|
|
|
t0 = cv->verts[tri->indexes[0]].st;
|
|
t1 = cv->verts[tri->indexes[1]].st;
|
|
t2 = cv->verts[tri->indexes[2]].st;
|
|
|
|
R_CalcTangentSpace(tangent, binormal, normal, v0, v1, v2, t0, t1, t2);
|
|
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
v = cv->verts[tri->indexes[j]].tangent;
|
|
VectorAdd(v, tangent, v);
|
|
v = cv->verts[tri->indexes[j]].binormal;
|
|
VectorAdd(v, binormal, v);
|
|
v = cv->verts[tri->indexes[j]].normal;
|
|
VectorAdd(v, normal, v);
|
|
}
|
|
}
|
|
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
VectorNormalize(cv->verts[i].tangent);
|
|
VectorNormalize(cv->verts[i].binormal);
|
|
VectorNormalize(cv->verts[i].normal);
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
srfVert_t *dv[3];
|
|
|
|
for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
dv[0] = &cv->verts[tri->indexes[0]];
|
|
dv[1] = &cv->verts[tri->indexes[1]];
|
|
dv[2] = &cv->verts[tri->indexes[2]];
|
|
|
|
R_CalcTangentVectors(dv);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// finish surface
|
|
FinishGenericSurface(ds, (srfGeneric_t *) cv, cv->verts[0].xyz);
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
ParseMesh
|
|
===============
|
|
*/
|
|
static void ParseMesh(dsurface_t * ds, drawVert_t * verts, bspSurface_t * surf)
|
|
{
|
|
srfGridMesh_t *grid;
|
|
int i, j;
|
|
int width, height, numPoints;
|
|
static srfVert_t points[MAX_PATCH_SIZE * MAX_PATCH_SIZE];
|
|
vec3_t bounds[2];
|
|
vec3_t tmpVec;
|
|
static surfaceType_t skipData = SF_SKIP;
|
|
int realLightmapNum;
|
|
|
|
// get lightmap
|
|
realLightmapNum = LittleLong(ds->lightmapNum);
|
|
if(r_vertexLighting->integer || !r_precomputedLighting->integer)
|
|
{
|
|
surf->lightmapNum = -1;
|
|
}
|
|
else
|
|
{
|
|
//#if defined(COMPAT_Q3A)
|
|
// surf->lightmapNum = 0;
|
|
//#else
|
|
surf->lightmapNum = realLightmapNum;
|
|
//#endif
|
|
}
|
|
|
|
if(tr.worldDeluxeMapping && surf->lightmapNum >= 2)
|
|
{
|
|
surf->lightmapNum /= 2;
|
|
}
|
|
|
|
// get fog volume
|
|
surf->fogIndex = LittleLong(ds->fogNum) + 1;
|
|
|
|
// get shader value
|
|
surf->shader = ShaderForShaderNum(ds->shaderNum);
|
|
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 defined(COMPAT_ET)
|
|
if(s_worldData.shaders[LittleLong(ds->shaderNum)].surfaceFlags & SURF_NODRAW)
|
|
//#else
|
|
// if(s_worldData.shaders[LittleLong(ds->shaderNum)].surfaceFlags & (SURF_NODRAW | SURF_COLLISION))
|
|
//#endif
|
|
{
|
|
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++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
points[i].xyz[j] = LittleFloat(verts[i].xyz[j]);
|
|
points[i].normal[j] = LittleFloat(verts[i].normal[j]);
|
|
}
|
|
|
|
for(j = 0; j < 2; j++)
|
|
{
|
|
points[i].st[j] = LittleFloat(verts[i].st[j]);
|
|
points[i].lightmap[j] = LittleFloat(verts[i].lightmap[j]);
|
|
}
|
|
|
|
#if defined(COMPAT_Q3A)
|
|
points[i].lightmap[0] = FatPackU(LittleFloat(verts[i].lightmap[0]), realLightmapNum);
|
|
points[i].lightmap[1] = FatPackV(LittleFloat(verts[i].lightmap[1]), realLightmapNum);
|
|
|
|
for(j = 0; j < 4; j++)
|
|
{
|
|
points[i].lightColor[j] = verts[i].color[j] * (1.0f / 255.0f);
|
|
}
|
|
R_ColorShiftLightingFloats(points[i].lightColor, points[i].lightColor);
|
|
|
|
#elif defined(COMPAT_Q3A) || defined(COMPAT_ET)
|
|
for(j = 0; j < 4; j++)
|
|
{
|
|
points[i].lightColor[j] = verts[i].color[j] * (1.0f / 255.0f);
|
|
}
|
|
R_ColorShiftLightingFloats(points[i].lightColor, points[i].lightColor);
|
|
#else
|
|
for(j = 0; j < 4; j++)
|
|
{
|
|
points[i].paintColor[j] = Q_bound(0, LittleFloat(verts[i].paintColor[j]), 1);
|
|
points[i].lightColor[j] = LittleFloat(verts[i].lightColor[j]);
|
|
}
|
|
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
points[i].lightDirection[j] = LittleFloat(verts[i].lightDirection[j]);
|
|
}
|
|
//VectorNormalize(points[i].lightDirection);
|
|
|
|
R_HDRTonemapLightingColors(points[i].lightColor, points[i].lightColor, qtrue);
|
|
#endif
|
|
}
|
|
|
|
// pre-tesseleate
|
|
grid = R_SubdividePatchToGrid(width, height, points);
|
|
surf->data = (surfaceType_t *) grid;
|
|
|
|
// 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);
|
|
|
|
// finish surface
|
|
FinishGenericSurface(ds, (srfGeneric_t *) grid, grid->verts[0].xyz);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
===============
|
|
ParseTriSurf
|
|
===============
|
|
*/
|
|
static void ParseTriSurf(dsurface_t * ds, drawVert_t * verts, bspSurface_t * surf, int *indexes)
|
|
{
|
|
srfTriangles_t *cv;
|
|
srfTriangle_t *tri;
|
|
int i, j;
|
|
int numVerts, numTriangles;
|
|
int realLightmapNum;
|
|
static surfaceType_t skipData = SF_SKIP;
|
|
|
|
// get lightmap
|
|
//#if defined(COMPAT_Q3A)
|
|
// surf->lightmapNum = -1; // FIXME LittleLong(ds->lightmapNum);
|
|
//#else
|
|
realLightmapNum = LittleLong(ds->lightmapNum);
|
|
if(r_vertexLighting->integer || !r_precomputedLighting->integer)
|
|
{
|
|
surf->lightmapNum = -1;
|
|
}
|
|
else
|
|
{
|
|
surf->lightmapNum = realLightmapNum;
|
|
}
|
|
//#endif
|
|
|
|
if(tr.worldDeluxeMapping && surf->lightmapNum >= 2)
|
|
{
|
|
surf->lightmapNum /= 2;
|
|
}
|
|
|
|
// get fog volume
|
|
surf->fogIndex = LittleLong(ds->fogNum) + 1;
|
|
|
|
// get shader
|
|
surf->shader = ShaderForShaderNum(ds->shaderNum);
|
|
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 defined(COMPAT_ET)
|
|
if(s_worldData.shaders[LittleLong(ds->shaderNum)].surfaceFlags & SURF_NODRAW)
|
|
//#else
|
|
// if(s_worldData.shaders[LittleLong(ds->shaderNum)].surfaceFlags & (SURF_NODRAW | SURF_COLLISION))
|
|
//#endif
|
|
{
|
|
surf->data = &skipData;
|
|
return;
|
|
}
|
|
|
|
numVerts = LittleLong(ds->numVerts);
|
|
numTriangles = LittleLong(ds->numIndexes) / 3;
|
|
|
|
cv = ri.Hunk_Alloc(sizeof(*cv), h_low);
|
|
cv->surfaceType = SF_TRIANGLES;
|
|
|
|
cv->numTriangles = numTriangles;
|
|
cv->triangles = ri.Hunk_Alloc(numTriangles * sizeof(cv->triangles[0]), h_low);
|
|
|
|
cv->numVerts = numVerts;
|
|
cv->verts = ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low);
|
|
|
|
surf->data = (surfaceType_t *) cv;
|
|
|
|
// copy vertexes
|
|
verts += LittleLong(ds->firstVert);
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
cv->verts[i].xyz[j] = LittleFloat(verts[i].xyz[j]);
|
|
cv->verts[i].normal[j] = LittleFloat(verts[i].normal[j]);
|
|
}
|
|
|
|
for(j = 0; j < 2; j++)
|
|
{
|
|
cv->verts[i].st[j] = LittleFloat(verts[i].st[j]);
|
|
cv->verts[i].lightmap[j] = LittleFloat(verts[i].lightmap[j]);
|
|
}
|
|
|
|
#if defined(COMPAT_Q3A) || defined(COMPAT_ET)
|
|
for(j = 0; j < 4; j++)
|
|
{
|
|
cv->verts[i].lightColor[j] = verts[i].color[j] * (1.0f / 255.0f);
|
|
}
|
|
R_ColorShiftLightingFloats(cv->verts[i].lightColor, cv->verts[i].lightColor);
|
|
#else
|
|
for(j = 0; j < 4; j++)
|
|
{
|
|
cv->verts[i].paintColor[j] = Q_bound(0, LittleFloat(verts[i].paintColor[j]), 1);
|
|
cv->verts[i].lightColor[j] = LittleFloat(verts[i].lightColor[j]);
|
|
}
|
|
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
cv->verts[i].lightDirection[j] = LittleFloat(verts[i].lightDirection[j]);
|
|
}
|
|
//VectorNormalize(cv->verts[i].lightDirection);
|
|
|
|
R_HDRTonemapLightingColors(cv->verts[i].lightColor, cv->verts[i].lightColor, qtrue);
|
|
#endif
|
|
}
|
|
|
|
// copy triangles
|
|
indexes += LittleLong(ds->firstIndex);
|
|
for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
tri->indexes[j] = LittleLong(indexes[i * 3 + j]);
|
|
|
|
if(tri->indexes[j] < 0 || tri->indexes[j] >= numVerts)
|
|
{
|
|
ri.Error(ERR_DROP, "Bad index in face surface");
|
|
}
|
|
}
|
|
}
|
|
|
|
// calc bounding box
|
|
// HACK: don't loop only through the vertices because they can contain bad data with .lwo models ...
|
|
ClearBounds(cv->bounds[0], cv->bounds[1]);
|
|
for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
AddPointToBounds(cv->verts[tri->indexes[0]].xyz, cv->bounds[0], cv->bounds[1]);
|
|
AddPointToBounds(cv->verts[tri->indexes[1]].xyz, cv->bounds[0], cv->bounds[1]);
|
|
AddPointToBounds(cv->verts[tri->indexes[2]].xyz, cv->bounds[0], cv->bounds[1]);
|
|
}
|
|
|
|
R_CalcSurfaceTriangleNeighbors(numTriangles, cv->triangles);
|
|
R_CalcSurfaceTrianglePlanes(numTriangles, cv->triangles, cv->verts);
|
|
|
|
// Tr3B - calc tangent spaces
|
|
#if 0
|
|
{
|
|
float *v;
|
|
const float *v0, *v1, *v2;
|
|
const float *t0, *t1, *t2;
|
|
vec3_t tangent;
|
|
vec3_t binormal;
|
|
vec3_t normal;
|
|
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
VectorClear(cv->verts[i].tangent);
|
|
VectorClear(cv->verts[i].binormal);
|
|
VectorClear(cv->verts[i].normal);
|
|
}
|
|
|
|
for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
v0 = cv->verts[tri->indexes[0]].xyz;
|
|
v1 = cv->verts[tri->indexes[1]].xyz;
|
|
v2 = cv->verts[tri->indexes[2]].xyz;
|
|
|
|
t0 = cv->verts[tri->indexes[0]].st;
|
|
t1 = cv->verts[tri->indexes[1]].st;
|
|
t2 = cv->verts[tri->indexes[2]].st;
|
|
|
|
#if 1
|
|
R_CalcTangentSpace(tangent, binormal, normal, v0, v1, v2, t0, t1, t2);
|
|
#else
|
|
R_CalcNormalForTriangle(normal, v0, v1, v2);
|
|
R_CalcTangentsForTriangle2(tangent, binormal, v0, v1, v2, t0, t1, t2);
|
|
#endif
|
|
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
v = cv->verts[tri->indexes[j]].tangent;
|
|
VectorAdd(v, tangent, v);
|
|
v = cv->verts[tri->indexes[j]].binormal;
|
|
VectorAdd(v, binormal, v);
|
|
v = cv->verts[tri->indexes[j]].normal;
|
|
VectorAdd(v, normal, v);
|
|
}
|
|
}
|
|
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
float dot;
|
|
|
|
//VectorNormalize(cv->verts[i].tangent);
|
|
VectorNormalize(cv->verts[i].binormal);
|
|
VectorNormalize(cv->verts[i].normal);
|
|
|
|
// Gram-Schmidt orthogonalize
|
|
dot = DotProduct(cv->verts[i].normal, cv->verts[i].tangent);
|
|
VectorMA(cv->verts[i].tangent, -dot, cv->verts[i].normal, cv->verts[i].tangent);
|
|
VectorNormalize(cv->verts[i].tangent);
|
|
|
|
//dot = DotProduct(cv->verts[i].normal, cv->verts[i].tangent);
|
|
//VectorMA(cv->verts[i].tangent, -dot, cv->verts[i].normal, cv->verts[i].tangent);
|
|
//VectorNormalize(cv->verts[i].tangent);
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
srfVert_t *dv[3];
|
|
|
|
for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
dv[0] = &cv->verts[tri->indexes[0]];
|
|
dv[1] = &cv->verts[tri->indexes[1]];
|
|
dv[2] = &cv->verts[tri->indexes[2]];
|
|
|
|
R_CalcTangentVectors(dv);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
// do another extra smoothing for normals to avoid flat shading
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
for(j = 0; j < numVerts; j++)
|
|
{
|
|
if(i == j)
|
|
continue;
|
|
|
|
if(R_CompareVert(&cv->verts[i], &cv->verts[j], qfalse))
|
|
{
|
|
VectorAdd(cv->verts[i].normal, cv->verts[j].normal, cv->verts[i].normal);
|
|
}
|
|
}
|
|
|
|
VectorNormalize(cv->verts[i].normal);
|
|
}
|
|
#endif
|
|
|
|
// finish surface
|
|
FinishGenericSurface(ds, (srfGeneric_t *) cv, cv->verts[0].xyz);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
ParseFlare
|
|
===============
|
|
*/
|
|
static void ParseFlare(dsurface_t * ds, drawVert_t * verts, bspSurface_t * surf, int *indexes)
|
|
{
|
|
srfFlare_t *flare;
|
|
int i;
|
|
|
|
// set lightmap
|
|
surf->lightmapNum = -1;
|
|
|
|
// get fog volume
|
|
surf->fogIndex = LittleLong(ds->fogNum) + 1;
|
|
|
|
// get shader
|
|
surf->shader = ShaderForShaderNum(ds->shaderNum);
|
|
if(r_singleShader->integer && !surf->shader->isSky)
|
|
{
|
|
surf->shader = tr.defaultShader;
|
|
}
|
|
|
|
flare = ri.Hunk_Alloc(sizeof(*flare), h_low);
|
|
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]);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
R_MergedWidthPoints
|
|
|
|
returns true if there are grid points merged on a width edge
|
|
=================
|
|
*/
|
|
int R_MergedWidthPoints(srfGridMesh_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 qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_MergedHeightPoints
|
|
|
|
returns true if there are grid points merged on a height edge
|
|
=================
|
|
*/
|
|
int R_MergedHeightPoints(srfGridMesh_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 qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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, srfGridMesh_t * grid1)
|
|
{
|
|
int j, k, l, m, n, offset1, offset2, touch;
|
|
srfGridMesh_t *grid2;
|
|
|
|
for(j = start; j < s_worldData.numSurfaces; j++)
|
|
{
|
|
//
|
|
grid2 = (srfGridMesh_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 = qfalse;
|
|
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 = qtrue;
|
|
}
|
|
}
|
|
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 = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 = qtrue;
|
|
}
|
|
}
|
|
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 = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
srfGridMesh_t *grid1;
|
|
|
|
for(i = 0; i < s_worldData.numSurfaces; i++)
|
|
{
|
|
//
|
|
grid1 = (srfGridMesh_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;
|
|
srfGridMesh_t *grid1, *grid2;
|
|
int k, l, m, n, offset1, offset2, row, column;
|
|
|
|
grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data;
|
|
grid2 = (srfGridMesh_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;
|
|
grid2 = R_GridInsertColumn(grid2, l + 1, row, grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k + 1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (void *)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
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;
|
|
grid2 = R_GridInsertRow(grid2, l + 1, column, grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k + 1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (void *)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
grid2 = R_GridInsertColumn(grid2, l + 1, row,
|
|
grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k + 1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (void *)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
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;
|
|
grid2 = R_GridInsertRow(grid2, l + 1, column,
|
|
grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k + 1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (void *)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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->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;
|
|
grid2 = R_GridInsertColumn(grid2, l + 1, row, grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k + 1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (void *)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
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;
|
|
grid2 = R_GridInsertRow(grid2, l + 1, column, grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k + 1]);
|
|
if(!grid2)
|
|
break;
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (void *)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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->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;
|
|
grid2 = R_GridInsertColumn(grid2, l + 1, row,
|
|
grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k + 1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (void *)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
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;
|
|
grid2 = R_GridInsertRow(grid2, l + 1, column,
|
|
grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k + 1]);
|
|
grid2->lodStitched = qfalse;
|
|
s_worldData.surfaces[grid2num].data = (void *)grid2;
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
srfGridMesh_t *grid1, *grid2;
|
|
|
|
numstitches = 0;
|
|
grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data;
|
|
for(j = 0; j < s_worldData.numSurfaces; j++)
|
|
{
|
|
//
|
|
grid2 = (srfGridMesh_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;
|
|
srfGridMesh_t *grid1;
|
|
|
|
ri.Printf(PRINT_ALL, "...stitching LoD cracks\n");
|
|
|
|
numstitches = 0;
|
|
do
|
|
{
|
|
stitched = qfalse;
|
|
for(i = 0; i < s_worldData.numSurfaces; i++)
|
|
{
|
|
//
|
|
grid1 = (srfGridMesh_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 = qtrue;
|
|
stitched = qtrue;
|
|
//
|
|
numstitches += R_TryStitchingPatch(i);
|
|
}
|
|
} while(stitched);
|
|
ri.Printf(PRINT_ALL, "stitched %d LoD cracks\n", numstitches);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_MovePatchSurfacesToHunk
|
|
===============
|
|
*/
|
|
void R_MovePatchSurfacesToHunk(void)
|
|
{
|
|
int i, size;
|
|
srfGridMesh_t *grid, *hunkgrid;
|
|
|
|
for(i = 0; i < s_worldData.numSurfaces; i++)
|
|
{
|
|
//
|
|
grid = (srfGridMesh_t *) s_worldData.surfaces[i].data;
|
|
|
|
// if this surface is not a grid
|
|
if(grid->surfaceType != SF_GRID)
|
|
continue;
|
|
//
|
|
size = sizeof(*grid);
|
|
hunkgrid = ri.Hunk_Alloc(size, h_low);
|
|
Com_Memcpy(hunkgrid, grid, size);
|
|
|
|
hunkgrid->widthLodError = ri.Hunk_Alloc(grid->width * 4, h_low);
|
|
Com_Memcpy(hunkgrid->widthLodError, grid->widthLodError, grid->width * 4);
|
|
|
|
hunkgrid->heightLodError = ri.Hunk_Alloc(grid->height * 4, h_low);
|
|
Com_Memcpy(hunkgrid->heightLodError, grid->heightLodError, grid->height * 4);
|
|
|
|
hunkgrid->numTriangles = grid->numTriangles;
|
|
hunkgrid->triangles = ri.Hunk_Alloc(grid->numTriangles * sizeof(srfTriangle_t), h_low);
|
|
Com_Memcpy(hunkgrid->triangles, grid->triangles, grid->numTriangles * sizeof(srfTriangle_t));
|
|
|
|
hunkgrid->numVerts = grid->numVerts;
|
|
hunkgrid->verts = ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low);
|
|
Com_Memcpy(hunkgrid->verts, grid->verts, grid->numVerts * sizeof(srfVert_t));
|
|
|
|
R_FreeSurfaceGridMesh(grid);
|
|
|
|
s_worldData.surfaces[i].data = (void *)hunkgrid;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
BSPSurfaceCompare
|
|
compare function for qsort()
|
|
=================
|
|
*/
|
|
static int BSPSurfaceCompare(const void *a, const void *b)
|
|
{
|
|
bspSurface_t *aa, *bb;
|
|
|
|
aa = *(bspSurface_t **) a;
|
|
bb = *(bspSurface_t **) b;
|
|
|
|
// shader first
|
|
if(aa->shader < bb->shader)
|
|
return -1;
|
|
|
|
else if(aa->shader > bb->shader)
|
|
return 1;
|
|
|
|
// by lightmap
|
|
if(aa->lightmapNum < bb->lightmapNum)
|
|
return -1;
|
|
|
|
else if(aa->lightmapNum > bb->lightmapNum)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void CopyVert(const srfVert_t * in, srfVert_t * out)
|
|
{
|
|
int j;
|
|
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
out->xyz[j] = in->xyz[j];
|
|
out->tangent[j] = in->tangent[j];
|
|
out->binormal[j] = in->binormal[j];
|
|
out->normal[j] = in->normal[j];
|
|
out->lightDirection[j] = in->lightDirection[j];
|
|
}
|
|
|
|
for(j = 0; j < 2; j++)
|
|
{
|
|
out->st[j] = in->st[j];
|
|
out->lightmap[j] = in->lightmap[j];
|
|
}
|
|
|
|
for(j = 0; j < 4; j++)
|
|
{
|
|
out->paintColor[j] = in->paintColor[j];
|
|
out->lightColor[j] = in->lightColor[j];
|
|
}
|
|
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
out->id = in->id;
|
|
#endif
|
|
}
|
|
|
|
|
|
#define EQUAL_EPSILON 0.001
|
|
/*static qboolean CompareWorldVert(const srfVert_t * v1, const srfVert_t * v2)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
//if(fabs(v1->xyz[i] - v2->xyz[i]) > EQUAL_EPSILON)
|
|
if(v1->xyz[i] != v2->xyz[i])
|
|
return qfalse;
|
|
}
|
|
|
|
for(i = 0; i < 2; i++)
|
|
{
|
|
if(v1->st[i] != v2->st[i])
|
|
return qfalse;
|
|
}
|
|
|
|
if(r_precomputedLighting->integer)
|
|
{
|
|
for(i = 0; i < 2; i++)
|
|
{
|
|
if(v1->lightmap[i] != v2->lightmap[i])
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
for(i = 0; i < 4; i++)
|
|
{
|
|
if(v1->lightColor[i] != v2->lightColor[i])
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
static qboolean CompareLightVert(const srfVert_t * v1, const srfVert_t * v2)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
//if(fabs(v1->xyz[i] - v2->xyz[i]) > EQUAL_EPSILON)
|
|
if(v1->xyz[i] != v2->xyz[i])
|
|
return qfalse;
|
|
}
|
|
|
|
for(i = 0; i < 2; i++)
|
|
{
|
|
if(v1->st[i] != v2->st[i])
|
|
return qfalse;
|
|
}
|
|
|
|
for(i = 0; i < 4; i++)
|
|
{
|
|
if(v1->paintColor[i] != v2->paintColor[i])
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
static qboolean CompareShadowVertAlphaTest(const srfVert_t * v1, const srfVert_t * v2)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
//if(fabs(v1->xyz[i] - v2->xyz[i]) > EQUAL_EPSILON)
|
|
if(v1->xyz[i] != v2->xyz[i])
|
|
return qfalse;
|
|
}
|
|
|
|
for(i = 0; i < 2; i++)
|
|
{
|
|
if(v1->st[i] != v2->st[i])
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
static qboolean CompareShadowVert(const srfVert_t * v1, const srfVert_t * v2)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
//if(fabs(v1->xyz[i] - v2->xyz[i]) > EQUAL_EPSILON)
|
|
if(v1->xyz[i] != v2->xyz[i])
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}*/
|
|
|
|
static qboolean CompareShadowVolumeVert(const srfVert_t * v1, const srfVert_t * v2)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
// don't consider epsilon to avoid shadow volume z-fighting
|
|
if(v1->xyz[i] != v2->xyz[i])
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*static qboolean CompareWorldVertSmoothNormal(const srfVert_t * v1, const srfVert_t * v2)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
if(fabs(v1->xyz[i] - v2->xyz[i]) > EQUAL_EPSILON)
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}*/
|
|
|
|
/*
|
|
remove duplicated / redundant vertices from a batch of vertices
|
|
return the new number of vertices
|
|
*/
|
|
static int OptimizeVertices(int numVerts, srfVert_t * verts, int numTriangles, srfTriangle_t * triangles, srfVert_t * outVerts,
|
|
qboolean(*CompareVert) (const srfVert_t * v1, const srfVert_t * v2))
|
|
{
|
|
srfTriangle_t *tri;
|
|
int i, j, k, l;
|
|
|
|
static int redundantIndex[MAX_MAP_DRAW_VERTS];
|
|
int numOutVerts;
|
|
|
|
if(r_vboOptimizeVertices->integer)
|
|
{
|
|
if(numVerts >= MAX_MAP_DRAW_VERTS)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "OptimizeVertices: MAX_MAP_DRAW_VERTS reached\n");
|
|
for(j = 0; j < numVerts; j++)
|
|
{
|
|
CopyVert(&verts[j], &outVerts[j]);
|
|
}
|
|
return numVerts;
|
|
}
|
|
|
|
memset(redundantIndex, -1, sizeof(redundantIndex));
|
|
|
|
c_redundantVertexes = 0;
|
|
numOutVerts = 0;
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
verts[i].id = i;
|
|
#endif
|
|
if(redundantIndex[i] == -1)
|
|
{
|
|
for(j = i + 1; j < numVerts; j++)
|
|
{
|
|
if(redundantIndex[i] != -1)
|
|
continue;
|
|
|
|
if(CompareVert(&verts[i], &verts[j]))
|
|
{
|
|
// mark vertex as redundant
|
|
redundantIndex[j] = i; //numOutVerts;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "input triangles: ");
|
|
for(k = 0, tri = triangles; k < numTriangles; k++, tri++)
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i,%i,%i),", verts[tri->indexes[0]].id, verts[tri->indexes[1]].id, verts[tri->indexes[2]].id);
|
|
}
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
#endif
|
|
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "input vertices: ");
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
if(redundantIndex[i] != -1)
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i,%i),", i, redundantIndex[i]);
|
|
}
|
|
else
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i,-),", i);
|
|
}
|
|
}
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
#endif
|
|
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
if(redundantIndex[i] != -1)
|
|
{
|
|
c_redundantVertexes++;
|
|
}
|
|
else
|
|
{
|
|
CopyVert(&verts[i], &outVerts[numOutVerts]);
|
|
numOutVerts++;
|
|
}
|
|
}
|
|
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "output vertices: ");
|
|
for(i = 0; i < numOutVerts; i++)
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i),", outVerts[i].id);
|
|
}
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
#endif
|
|
|
|
for(i = 0; i < numVerts;)
|
|
{
|
|
qboolean noIncrement = qfalse;
|
|
|
|
if(redundantIndex[i] != -1)
|
|
{
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "-------------------------------------------------\n");
|
|
ri.Printf(PRINT_ALL, "changing triangles for redundant vertex (%i->%i):\n", i, redundantIndex[i]);
|
|
#endif
|
|
|
|
// kill redundant vert
|
|
for(k = 0, tri = triangles; k < numTriangles; k++, tri++)
|
|
{
|
|
for(l = 0; l < 3; l++)
|
|
{
|
|
if(tri->indexes[l] == i) //redundantIndex[i])
|
|
{
|
|
// replace duplicated index j with the original vertex index i
|
|
tri->indexes[l] = redundantIndex[i]; //numOutVerts;
|
|
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "mapTriangleIndex<%i,%i>(%i->%i)\n", k, l, i, redundantIndex[i]);
|
|
#endif
|
|
}
|
|
#if 1
|
|
else if(tri->indexes[l] > i) // && redundantIndex[tri->indexes[l]] == -1)
|
|
{
|
|
tri->indexes[l]--;
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "decTriangleIndex<%i,%i>(%i->%i)\n", k, l, tri->indexes[l] + 1, tri->indexes[l]);
|
|
#endif
|
|
|
|
if(tri->indexes[l] < 0)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "OptimizeVertices: triangle index < 0\n");
|
|
for(j = 0; j < numVerts; j++)
|
|
{
|
|
CopyVert(&verts[j], &outVerts[j]);
|
|
}
|
|
return numVerts;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "pending redundant vertices: ");
|
|
for(j = i + 1; j < numVerts; j++)
|
|
{
|
|
if(redundantIndex[j] != -1)
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i,%i),", j, redundantIndex[j]);
|
|
}
|
|
else
|
|
{
|
|
//ri.Printf(PRINT_ALL, "(%i,-),", j);
|
|
}
|
|
}
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
#endif
|
|
|
|
|
|
for(j = i + 1; j < numVerts; j++)
|
|
{
|
|
if(redundantIndex[j] != -1) //> i)//== tri->indexes[l])
|
|
{
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "updateRedundantIndex(%i->%i) to (%i->%i)\n", j, redundantIndex[j], j - 1,
|
|
redundantIndex[j]);
|
|
#endif
|
|
|
|
if(redundantIndex[j] <= i)
|
|
{
|
|
redundantIndex[j - 1] = redundantIndex[j];
|
|
redundantIndex[j] = -1;
|
|
}
|
|
else
|
|
{
|
|
redundantIndex[j - 1] = redundantIndex[j] - 1;
|
|
redundantIndex[j] = -1;
|
|
}
|
|
|
|
if((j - 1) == i)
|
|
{
|
|
noIncrement = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "current triangles: ");
|
|
for(k = 0, tri = triangles; k < numTriangles; k++, tri++)
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i,%i,%i),", verts[tri->indexes[0]].id, verts[tri->indexes[1]].id,
|
|
verts[tri->indexes[2]].id);
|
|
}
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
#endif
|
|
}
|
|
|
|
if(!noIncrement)
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
#if DEBUG_OPTIMIZEVERTICES
|
|
ri.Printf(PRINT_ALL, "output triangles: ");
|
|
for(k = 0, tri = triangles; k < numTriangles; k++, tri++)
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i,%i,%i),", verts[tri->indexes[0]].id, verts[tri->indexes[1]].id, verts[tri->indexes[2]].id);
|
|
}
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
#endif
|
|
|
|
if(c_redundantVertexes)
|
|
{
|
|
//*numVerts -= c_redundantVertexes;
|
|
|
|
//ri.Printf(PRINT_ALL, "removed %i redundant vertices\n", c_redundantVertexes);
|
|
}
|
|
|
|
return numOutVerts;
|
|
}
|
|
else
|
|
{
|
|
for(j = 0; j < numVerts; j++)
|
|
{
|
|
CopyVert(&verts[j], &outVerts[j]);
|
|
}
|
|
return numVerts;
|
|
}
|
|
}
|
|
|
|
/*static void OptimizeTriangles(int numVerts, srfVert_t * verts, int numTriangles, srfTriangle_t * triangles,
|
|
qboolean(*compareVert) (const srfVert_t * v1, const srfVert_t * v2))
|
|
{
|
|
#if 1
|
|
srfTriangle_t *tri;
|
|
int i, j, k, l;
|
|
static int redundantIndex[MAX_MAP_DRAW_VERTS];
|
|
static qboolean(*compareFunction) (const srfVert_t * v1, const srfVert_t * v2) = NULL;
|
|
//static int minVertOld = 9999999, maxVertOld = 0;
|
|
int minVert, maxVert;
|
|
|
|
if(numVerts >= MAX_MAP_DRAW_VERTS)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "OptimizeTriangles: MAX_MAP_DRAW_VERTS reached\n");
|
|
return;
|
|
}
|
|
|
|
// find vertex bounds
|
|
maxVert = 0;
|
|
minVert = numVerts - 1;
|
|
for(k = 0, tri = triangles; k < numTriangles; k++, tri++)
|
|
{
|
|
for(l = 0; l < 3; l++)
|
|
{
|
|
if(tri->indexes[l] > maxVert)
|
|
maxVert = tri->indexes[l];
|
|
|
|
if(tri->indexes[l] < minVert)
|
|
minVert = tri->indexes[l];
|
|
}
|
|
}
|
|
|
|
ri.Printf(PRINT_ALL, "OptimizeTriangles: minVert %i maxVert %i\n", minVert, maxVert);
|
|
|
|
// if(compareFunction != compareVert || minVert != minVertOld || maxVert != maxVertOld)
|
|
{
|
|
compareFunction = compareVert;
|
|
|
|
memset(redundantIndex, -1, sizeof(redundantIndex));
|
|
|
|
for(i = minVert; i <= maxVert; i++)
|
|
{
|
|
if(redundantIndex[i] == -1)
|
|
{
|
|
for(j = i + 1; j <= maxVert; j++)
|
|
{
|
|
if(redundantIndex[i] != -1)
|
|
continue;
|
|
|
|
if(compareVert(&verts[i], &verts[j]))
|
|
{
|
|
// mark vertex as redundant
|
|
redundantIndex[j] = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
c_redundantVertexes = 0;
|
|
for(i = minVert; i <= maxVert; i++)
|
|
{
|
|
if(redundantIndex[i] != -1)
|
|
{
|
|
//ri.Printf(PRINT_ALL, "changing triangles for redundant vertex (%i->%i):\n", i, redundantIndex[i]);
|
|
|
|
// kill redundant vert
|
|
for(k = 0, tri = triangles; k < numTriangles; k++, tri++)
|
|
{
|
|
for(l = 0; l < 3; l++)
|
|
{
|
|
if(tri->indexes[l] == i) //redundantIndex[i])
|
|
{
|
|
// replace duplicated index j with the original vertex index i
|
|
tri->indexes[l] = redundantIndex[i];
|
|
|
|
//ri.Printf(PRINT_ALL, "mapTriangleIndex<%i,%i>(%i->%i)\n", k, l, i, redundantIndex[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
c_redundantVertexes++;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
ri.Printf(PRINT_ALL, "vertices: ");
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
if(redundantIndex[i] != -1)
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i,%i),", i, redundantIndex[i]);
|
|
}
|
|
else
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i,-),", i);
|
|
}
|
|
}
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
|
|
ri.Printf(PRINT_ALL, "triangles: ");
|
|
for(k = 0, tri = triangles; k < numTriangles; k++, tri++)
|
|
{
|
|
ri.Printf(PRINT_ALL, "(%i,%i,%i),", tri->indexes[0], tri->indexes[1], tri->indexes[2]);
|
|
}
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
#endif
|
|
|
|
if(c_redundantVertexes)
|
|
{
|
|
// *numVerts -= c_redundantVertexes;
|
|
|
|
//ri.Printf(PRINT_ALL, "removed %i redundant vertices\n", c_redundantVertexes);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void OptimizeTrianglesLite(const int *redundantIndex, int numTriangles, srfTriangle_t * triangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
int k, l;
|
|
|
|
c_redundantVertexes = 0;
|
|
for(k = 0, tri = triangles; k < numTriangles; k++, tri++)
|
|
{
|
|
for(l = 0; l < 3; l++)
|
|
{
|
|
if(redundantIndex[tri->indexes[l]] != -1)
|
|
{
|
|
// replace duplicated index j with the original vertex index i
|
|
tri->indexes[l] = redundantIndex[tri->indexes[l]];
|
|
|
|
//ri.Printf(PRINT_ALL, "mapTriangleIndex<%i,%i>(%i->%i)\n", k, l, tri->indexes[l], redundantIndex[tri->indexes[l]]);
|
|
|
|
c_redundantVertexes++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void BuildRedundantIndices(int numVerts, const srfVert_t * verts, int *redundantIndex,
|
|
qboolean(*CompareVert) (const srfVert_t * v1, const srfVert_t * v2))
|
|
{
|
|
int i, j;
|
|
|
|
memset(redundantIndex, -1, numVerts * sizeof(int));
|
|
|
|
for(i = 0; i < numVerts; i++)
|
|
{
|
|
if(redundantIndex[i] == -1)
|
|
{
|
|
for(j = i + 1; j < numVerts; j++)
|
|
{
|
|
if(redundantIndex[j] != -1)
|
|
continue;
|
|
|
|
if(CompareVert(&verts[i], &verts[j]))
|
|
{
|
|
// mark vertex as redundant
|
|
redundantIndex[j] = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
|
|
|
|
/*
|
|
static void R_LoadAreaPortals(const char *bspName)
|
|
{
|
|
int i, j, k;
|
|
char fileName[MAX_QPATH];
|
|
char *token;
|
|
char *buf_p;
|
|
byte *buffer;
|
|
int bufferLen;
|
|
const char *version = "AREAPRT1";
|
|
int numAreas;
|
|
int numAreaPortals;
|
|
int numPoints;
|
|
|
|
bspAreaPortal_t *ap;
|
|
|
|
Q_strncpyz(fileName, bspName, sizeof(fileName));
|
|
COM_StripExtension(fileName, fileName, sizeof(fileName));
|
|
Q_strcat(fileName, sizeof(fileName), ".areaprt");
|
|
|
|
bufferLen = ri.FS_ReadFile(fileName, (void **)&buffer);
|
|
if(!buffer)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: could not load area portals file '%s'\n", fileName);
|
|
return;
|
|
}
|
|
|
|
buf_p = (char *)buffer;
|
|
|
|
// check version
|
|
token = COM_ParseExt(&buf_p, qfalse);
|
|
if(strcmp(token, version))
|
|
{
|
|
ri.Printf(PRINT_WARNING, "R_LoadAreaPortals: %s has wrong version (%i should be %i)\n", fileName, token, version);
|
|
return;
|
|
}
|
|
|
|
// load areas num
|
|
token = COM_ParseExt(&buf_p, qtrue);
|
|
numAreas = atoi(token);
|
|
if(numAreas != s_worldData.numAreas)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "R_LoadAreaPortals: %s has wrong number of areas (%i should be %i)\n", fileName, numAreas,
|
|
s_worldData.numAreas);
|
|
return;
|
|
}
|
|
|
|
// load areas portals
|
|
token = COM_ParseExt(&buf_p, qtrue);
|
|
numAreaPortals = atoi(token);
|
|
|
|
ri.Printf(PRINT_ALL, "...loading %i area portals\n", numAreaPortals);
|
|
|
|
s_worldData.numAreaPortals = numAreaPortals;
|
|
s_worldData.areaPortals = ri.Hunk_Alloc(numAreaPortals * sizeof(*s_worldData.areaPortals), h_low);
|
|
|
|
for(i = 0, ap = s_worldData.areaPortals; i < numAreaPortals; i++, ap++)
|
|
{
|
|
token = COM_ParseExt(&buf_p, qtrue);
|
|
numPoints = atoi(token);
|
|
|
|
if(numPoints != 4)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "R_LoadAreaPortals: expected 4 found '%s' in file '%s'\n", token, fileName);
|
|
return;
|
|
}
|
|
|
|
COM_ParseExt(&buf_p, qfalse);
|
|
ap->areas[0] = atoi(token);
|
|
|
|
COM_ParseExt(&buf_p, qfalse);
|
|
ap->areas[1] = atoi(token);
|
|
|
|
for(j = 0; j < numPoints; j++)
|
|
{
|
|
// skip (
|
|
token = COM_ParseExt(&buf_p, qfalse);
|
|
if(Q_stricmp(token, "("))
|
|
{
|
|
ri.Printf(PRINT_WARNING, "R_LoadAreaPortals: expected '(' found '%s' in file '%s'\n", token, fileName);
|
|
return;
|
|
}
|
|
|
|
for(k = 0; k < 3; k++)
|
|
{
|
|
token = COM_ParseExt(&buf_p, qfalse);
|
|
ap->points[j][k] = atof(token);
|
|
}
|
|
|
|
// skip )
|
|
token = COM_ParseExt(&buf_p, qfalse);
|
|
if(Q_stricmp(token, ")"))
|
|
{
|
|
ri.Printf(PRINT_WARNING, "R_LoadAreaPortals: expected ')' found '%s' in file '%s'\n", token, fileName);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
=================
|
|
R_CreateAreas
|
|
=================
|
|
*/
|
|
/*
|
|
static void R_CreateAreas()
|
|
{
|
|
int i, j;
|
|
int numAreas, maxArea;
|
|
bspNode_t *node;
|
|
bspArea_t *area;
|
|
growList_t areaSurfaces;
|
|
int c;
|
|
bspSurface_t *surface, **mark;
|
|
int surfaceNum;
|
|
|
|
|
|
ri.Printf(PRINT_ALL, "...creating BSP areas\n");
|
|
|
|
// go through the leaves and count areas
|
|
maxArea = 0;
|
|
|
|
for(i = 0, node = s_worldData.nodes; i < s_worldData.numnodes; i++, node++)
|
|
{
|
|
if(node->contents == CONTENTS_NODE)
|
|
continue;
|
|
|
|
if(node->area == -1)
|
|
continue;
|
|
|
|
if(node->area > maxArea)
|
|
maxArea = node->area;
|
|
}
|
|
|
|
numAreas = maxArea + 1;
|
|
|
|
s_worldData.numAreas = numAreas;
|
|
s_worldData.areas = ri.Hunk_Alloc(numAreas * sizeof(*s_worldData.areas), h_low);
|
|
|
|
// reset surfaces' viewCount
|
|
for(i = 0, surface = s_worldData.surfaces; i < s_worldData.numSurfaces; i++, surface++)
|
|
{
|
|
surface->viewCount = -1;
|
|
}
|
|
|
|
// add area surfaces
|
|
for(i = 0; i < numAreas; i++)
|
|
{
|
|
area = &s_worldData.areas[i];
|
|
|
|
Com_InitGrowList(&areaSurfaces, 100);
|
|
|
|
for(j = 0, node = s_worldData.nodes; j < s_worldData.numnodes; j++, node++)
|
|
{
|
|
if(node->contents == CONTENTS_NODE)
|
|
continue;
|
|
|
|
if(node->area != i)
|
|
continue;
|
|
|
|
if(node->cluster < 0)
|
|
continue;
|
|
|
|
// add the individual surfaces
|
|
mark = node->markSurfaces;
|
|
c = node->numMarkSurfaces;
|
|
while(c--)
|
|
{
|
|
// the surface may have already been added if it
|
|
// spans multiple leafs
|
|
surface = *mark;
|
|
|
|
surfaceNum = surface - s_worldData.surfaces;
|
|
|
|
if((surface->viewCount != (i + 1)) && (surfaceNum < s_worldData.numWorldSurfaces))
|
|
{
|
|
surface->viewCount = i + 1;
|
|
Com_AddToGrowList(&areaSurfaces, surface);
|
|
}
|
|
|
|
mark++;
|
|
}
|
|
}
|
|
|
|
// move area surfaces list to hunk
|
|
area->numMarkSurfaces = areaSurfaces.currentElements;
|
|
area->markSurfaces = ri.Hunk_Alloc(area->numMarkSurfaces * sizeof(*area->markSurfaces), h_low);
|
|
|
|
for(j = 0; j < area->numMarkSurfaces; j++)
|
|
{
|
|
area->markSurfaces[j] = (bspSurface_t *) Com_GrowListElement(&areaSurfaces, j);
|
|
}
|
|
|
|
Com_DestroyGrowList(&areaSurfaces);
|
|
|
|
ri.Printf(PRINT_ALL, "area %i contains %i bsp surfaces\n", i, area->numMarkSurfaces);
|
|
}
|
|
|
|
ri.Printf(PRINT_ALL, "%i world areas created\n", numAreas);
|
|
}
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
R_CreateVBOWorldSurfaces
|
|
===============
|
|
*/
|
|
/*
|
|
static void R_CreateVBOWorldSurfaces()
|
|
{
|
|
int i, j, k, l, a;
|
|
|
|
int numVerts;
|
|
srfVert_t *verts;
|
|
|
|
srfVert_t *optimizedVerts;
|
|
|
|
int numTriangles;
|
|
srfTriangle_t *triangles;
|
|
|
|
shader_t *shader, *oldShader;
|
|
int lightmapNum, oldLightmapNum;
|
|
|
|
int numSurfaces;
|
|
bspSurface_t *surface, *surface2;
|
|
bspSurface_t **surfacesSorted;
|
|
|
|
bspArea_t *area;
|
|
|
|
growList_t vboSurfaces;
|
|
srfVBOMesh_t *vboSurf;
|
|
|
|
if(!glConfig.vertexBufferObjectAvailable)
|
|
return;
|
|
|
|
for(a = 0, area = s_worldData.areas; a < s_worldData.numAreas; a++, area++)
|
|
{
|
|
// count number of static area surfaces
|
|
numSurfaces = 0;
|
|
for(k = 0; k < area->numMarkSurfaces; k++)
|
|
{
|
|
surface = area->markSurfaces[k];
|
|
shader = surface->shader;
|
|
|
|
if(shader->isSky)
|
|
continue;
|
|
|
|
if(shader->isPortal || shader->isMirror)
|
|
continue;
|
|
|
|
if(shader->numDeforms)
|
|
continue;
|
|
|
|
numSurfaces++;
|
|
}
|
|
|
|
if(!numSurfaces)
|
|
continue;
|
|
|
|
// build interaction caches list
|
|
surfacesSorted = ri.Malloc(numSurfaces * sizeof(surfacesSorted[0]));
|
|
|
|
numSurfaces = 0;
|
|
for(k = 0; k < area->numMarkSurfaces; k++)
|
|
{
|
|
surface = area->markSurfaces[k];
|
|
shader = surface->shader;
|
|
|
|
if(shader->isSky)
|
|
continue;
|
|
|
|
if(shader->isPortal || shader->isMirror)
|
|
continue;
|
|
|
|
if(shader->numDeforms)
|
|
continue;
|
|
|
|
surfacesSorted[numSurfaces] = surface;
|
|
numSurfaces++;
|
|
}
|
|
|
|
Com_InitGrowList(&vboSurfaces, 100);
|
|
|
|
// sort surfaces by shader
|
|
qsort(surfacesSorted, numSurfaces, sizeof(surfacesSorted), BSPSurfaceCompare);
|
|
|
|
// create a VBO for each shader
|
|
shader = oldShader = NULL;
|
|
lightmapNum = oldLightmapNum = -1;
|
|
|
|
for(k = 0; k < numSurfaces; k++)
|
|
{
|
|
surface = surfacesSorted[k];
|
|
shader = surface->shader;
|
|
lightmapNum = surface->lightmapNum;
|
|
|
|
if(shader != oldShader || (r_precomputedLighting->integer ? lightmapNum != oldLightmapNum : 0))
|
|
{
|
|
oldShader = shader;
|
|
oldLightmapNum = lightmapNum;
|
|
|
|
// count vertices and indices
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
|
|
for(l = k; l < numSurfaces; l++)
|
|
{
|
|
surface2 = surfacesSorted[l];
|
|
|
|
if(surface2->shader != shader)
|
|
continue;
|
|
|
|
if(*surface2->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *face = (srfSurfaceFace_t *) surface2->data;
|
|
|
|
if(face->numVerts)
|
|
numVerts += face->numVerts;
|
|
|
|
if(face->numTriangles)
|
|
numTriangles += face->numTriangles;
|
|
}
|
|
else if(*surface2->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *grid = (srfGridMesh_t *) surface2->data;
|
|
|
|
if(grid->numVerts)
|
|
numVerts += grid->numVerts;
|
|
|
|
if(grid->numTriangles)
|
|
numTriangles += grid->numTriangles;
|
|
}
|
|
else if(*surface2->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *tri = (srfTriangles_t *) surface2->data;
|
|
|
|
if(tri->numVerts)
|
|
numVerts += tri->numVerts;
|
|
|
|
if(tri->numTriangles)
|
|
numTriangles += tri->numTriangles;
|
|
}
|
|
}
|
|
|
|
if(!numVerts || !numTriangles)
|
|
continue;
|
|
|
|
ri.Printf(PRINT_DEVELOPER, "...calculating world mesh VBOs ( %s, %i verts %i tris )\n", shader->name, numVerts,
|
|
numTriangles);
|
|
|
|
// create surface
|
|
vboSurf = ri.Hunk_Alloc(sizeof(*vboSurf), h_low);
|
|
Com_AddToGrowList(&vboSurfaces, vboSurf);
|
|
|
|
vboSurf->surfaceType = SF_VBO_MESH;
|
|
vboSurf->numIndexes = numTriangles * 3;
|
|
vboSurf->numVerts = numVerts;
|
|
|
|
vboSurf->shader = shader;
|
|
vboSurf->lightmapNum = lightmapNum;
|
|
|
|
// create arrays
|
|
verts = ri.Malloc(numVerts * sizeof(srfVert_t));
|
|
optimizedVerts = ri.Malloc(numVerts * sizeof(srfVert_t));
|
|
numVerts = 0;
|
|
|
|
triangles = ri.Malloc(numTriangles * sizeof(srfTriangle_t));
|
|
numTriangles = 0;
|
|
|
|
ClearBounds(vboSurf->bounds[0], vboSurf->bounds[1]);
|
|
|
|
// build triangle indices
|
|
for(l = k; l < numSurfaces; l++)
|
|
{
|
|
surface2 = surfacesSorted[l];
|
|
|
|
if(surface2->shader != shader)
|
|
continue;
|
|
|
|
// set up triangle indices
|
|
if(*surface2->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface2->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
|
|
for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
|
|
}
|
|
}
|
|
|
|
numTriangles += srf->numTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface2->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface2->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
|
|
for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
|
|
}
|
|
}
|
|
|
|
numTriangles += srf->numTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface2->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface2->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
|
|
for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
|
|
}
|
|
}
|
|
|
|
numTriangles += srf->numTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
}
|
|
|
|
// build vertices
|
|
numVerts = 0;
|
|
for(l = k; l < numSurfaces; l++)
|
|
{
|
|
surface2 = surfacesSorted[l];
|
|
|
|
if(surface2->shader != shader)
|
|
continue;
|
|
|
|
if(*surface2->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *cv = (srfSurfaceFace_t *) surface2->data;
|
|
|
|
if(cv->numVerts)
|
|
{
|
|
for(i = 0; i < cv->numVerts; i++)
|
|
{
|
|
CopyVert(&cv->verts[i], &verts[numVerts + i]);
|
|
|
|
AddPointToBounds(cv->verts[i].xyz, vboSurf->bounds[0], vboSurf->bounds[1]);
|
|
}
|
|
|
|
numVerts += cv->numVerts;
|
|
}
|
|
}
|
|
else if(*surface2->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *cv = (srfGridMesh_t *) surface2->data;
|
|
|
|
if(cv->numVerts)
|
|
{
|
|
for(i = 0; i < cv->numVerts; i++)
|
|
{
|
|
CopyVert(&cv->verts[i], &verts[numVerts + i]);
|
|
|
|
AddPointToBounds(cv->verts[i].xyz, vboSurf->bounds[0], vboSurf->bounds[1]);
|
|
}
|
|
|
|
numVerts += cv->numVerts;
|
|
}
|
|
}
|
|
else if(*surface2->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *cv = (srfTriangles_t *) surface2->data;
|
|
|
|
if(cv->numVerts)
|
|
{
|
|
for(i = 0; i < cv->numVerts; i++)
|
|
{
|
|
CopyVert(&cv->verts[i], &verts[numVerts + i]);
|
|
|
|
AddPointToBounds(cv->verts[i].xyz, vboSurf->bounds[0], vboSurf->bounds[1]);
|
|
}
|
|
|
|
numVerts += cv->numVerts;
|
|
}
|
|
}
|
|
}
|
|
|
|
numVerts = OptimizeVertices(numVerts, verts, numTriangles, triangles, optimizedVerts, CompareWorldVert);
|
|
if(c_redundantVertexes)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER,
|
|
"...removed %i redundant vertices from staticWorldMesh %i ( %s, %i verts %i tris )\n",
|
|
c_redundantVertexes, vboSurfaces.currentElements, shader->name, numVerts, numTriangles);
|
|
}
|
|
|
|
vboSurf->vbo = R_CreateVBO2(va("staticWorldMesh_vertices %i", vboSurfaces.currentElements), numVerts, optimizedVerts,
|
|
ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_TANGENT | ATTR_BINORMAL | ATTR_NORMAL
|
|
| ATTR_COLOR);
|
|
|
|
vboSurf->ibo = R_CreateIBO2(va("staticWorldMesh_indices %i", vboSurfaces.currentElements), numTriangles, triangles);
|
|
|
|
ri.Free(triangles);
|
|
ri.Free(optimizedVerts);
|
|
ri.Free(verts);
|
|
}
|
|
}
|
|
|
|
ri.Free(surfacesSorted);
|
|
|
|
// move VBO surfaces list to hunk
|
|
area->numVBOSurfaces = vboSurfaces.currentElements;
|
|
area->vboSurfaces = ri.Hunk_Alloc(area->numVBOSurfaces * sizeof(*area->vboSurfaces), h_low);
|
|
|
|
for(i = 0; i < area->numVBOSurfaces; i++)
|
|
{
|
|
area->vboSurfaces[i] = (srfVBOMesh_t *) Com_GrowListElement(&vboSurfaces, i);
|
|
}
|
|
|
|
Com_DestroyGrowList(&vboSurfaces);
|
|
|
|
ri.Printf(PRINT_ALL, "%i VBO surfaces created for area %i\n", area->numVBOSurfaces, a);
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
=================
|
|
R_CreateClusters
|
|
=================
|
|
*/
|
|
static void R_CreateClusters()
|
|
{
|
|
int i, j;
|
|
bspSurface_t *surface, **mark;
|
|
bspNode_t *node, *parent;
|
|
#if defined(USE_BSP_CLUSTERSURFACE_MERGING)
|
|
int numClusters;
|
|
bspCluster_t *cluster;
|
|
growList_t clusterSurfaces;
|
|
const byte *vis;
|
|
int c;
|
|
int surfaceNum;
|
|
vec3_t mins, maxs;
|
|
|
|
ri.Printf(PRINT_ALL, "...creating BSP clusters\n");
|
|
|
|
if(s_worldData.vis)
|
|
{
|
|
// go through the leaves and count clusters
|
|
numClusters = 0;
|
|
for(i = 0, node = s_worldData.nodes; i < s_worldData.numnodes; i++, node++)
|
|
{
|
|
if(node->cluster >= numClusters)
|
|
{
|
|
numClusters = node->cluster;
|
|
}
|
|
}
|
|
numClusters++;
|
|
|
|
s_worldData.numClusters = numClusters;
|
|
s_worldData.clusters = ri.Hunk_Alloc((numClusters + 1) * sizeof(*s_worldData.clusters), h_low); // + supercluster
|
|
|
|
// reset surfaces' viewCount
|
|
for(i = 0, surface = s_worldData.surfaces; i < s_worldData.numSurfaces; i++, surface++)
|
|
{
|
|
surface->viewCount = -1;
|
|
}
|
|
|
|
for(j = 0, node = s_worldData.nodes; j < s_worldData.numnodes; j++, node++)
|
|
{
|
|
node->visCounts[0] = -1;
|
|
}
|
|
|
|
for(i = 0; i < numClusters; i++)
|
|
{
|
|
cluster = &s_worldData.clusters[i];
|
|
|
|
// mark leaves in cluster
|
|
vis = s_worldData.vis + i * s_worldData.clusterBytes;
|
|
|
|
for(j = 0, node = s_worldData.nodes; j < s_worldData.numnodes; j++, node++)
|
|
{
|
|
if(node->cluster < 0 || node->cluster >= numClusters)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// check general pvs
|
|
if(!(vis[node->cluster >> 3] & (1 << (node->cluster & 7))))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
parent = node;
|
|
do
|
|
{
|
|
if(parent->visCounts[0] == i)
|
|
break;
|
|
parent->visCounts[0] = i;
|
|
parent = parent->parent;
|
|
} while(parent);
|
|
}
|
|
|
|
|
|
// add cluster surfaces
|
|
Com_InitGrowList(&clusterSurfaces, 10000);
|
|
|
|
ClearBounds(mins, maxs);
|
|
for(j = 0, node = s_worldData.nodes; j < s_worldData.numnodes; j++, node++)
|
|
{
|
|
if(node->contents == CONTENTS_NODE)
|
|
continue;
|
|
|
|
if(node->visCounts[0] != i)
|
|
continue;
|
|
|
|
BoundsAdd(mins, maxs, node->mins, node->maxs);
|
|
|
|
mark = node->markSurfaces;
|
|
c = node->numMarkSurfaces;
|
|
while(c--)
|
|
{
|
|
// the surface may have already been added if it
|
|
// spans multiple leafs
|
|
surface = *mark;
|
|
|
|
surfaceNum = surface - s_worldData.surfaces;
|
|
|
|
if((surface->viewCount != i) && (surfaceNum < s_worldData.numWorldSurfaces))
|
|
{
|
|
surface->viewCount = i;
|
|
Com_AddToGrowList(&clusterSurfaces, surface);
|
|
}
|
|
|
|
mark++;
|
|
}
|
|
}
|
|
|
|
cluster->origin[0] = (mins[0] + maxs[0]) / 2;
|
|
cluster->origin[1] = (mins[1] + maxs[1]) / 2;
|
|
cluster->origin[2] = (mins[2] + maxs[2]) / 2;
|
|
|
|
//ri.Printf(PRINT_ALL, "cluster %i origin at (%i %i %i)\n", i, (int)cluster->origin[0], (int)cluster->origin[1], (int)cluster->origin[2]);
|
|
|
|
// move cluster surfaces list to hunk
|
|
cluster->numMarkSurfaces = clusterSurfaces.currentElements;
|
|
cluster->markSurfaces = ri.Hunk_Alloc(cluster->numMarkSurfaces * sizeof(*cluster->markSurfaces), h_low);
|
|
|
|
for(j = 0; j < cluster->numMarkSurfaces; j++)
|
|
{
|
|
cluster->markSurfaces[j] = (bspSurface_t *) Com_GrowListElement(&clusterSurfaces, j);
|
|
}
|
|
|
|
Com_DestroyGrowList(&clusterSurfaces);
|
|
|
|
//ri.Printf(PRINT_ALL, "cluster %i contains %i bsp surfaces\n", i, cluster->numMarkSurfaces);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
numClusters = 0;
|
|
|
|
s_worldData.numClusters = numClusters;
|
|
s_worldData.clusters = ri.Hunk_Alloc((numClusters + 1) * sizeof(*s_worldData.clusters), h_low); // + supercluster
|
|
}
|
|
|
|
// create a super cluster that will be always used when no view cluster can be found
|
|
Com_InitGrowList(&clusterSurfaces, 10000);
|
|
|
|
for(i = 0, surface = s_worldData.surfaces; i < s_worldData.numWorldSurfaces; i++, surface++)
|
|
{
|
|
Com_AddToGrowList(&clusterSurfaces, surface);
|
|
}
|
|
|
|
cluster = &s_worldData.clusters[numClusters];
|
|
cluster->numMarkSurfaces = clusterSurfaces.currentElements;
|
|
cluster->markSurfaces = ri.Hunk_Alloc(cluster->numMarkSurfaces * sizeof(*cluster->markSurfaces), h_low);
|
|
|
|
for(j = 0; j < cluster->numMarkSurfaces; j++)
|
|
{
|
|
cluster->markSurfaces[j] = (bspSurface_t *) Com_GrowListElement(&clusterSurfaces, j);
|
|
}
|
|
|
|
Com_DestroyGrowList(&clusterSurfaces);
|
|
|
|
for(i = 0; i < MAX_VISCOUNTS; i++)
|
|
{
|
|
Com_InitGrowList(&s_worldData.clusterVBOSurfaces[i], 100);
|
|
}
|
|
|
|
//ri.Printf(PRINT_ALL, "noVis cluster contains %i bsp surfaces\n", cluster->numMarkSurfaces);
|
|
|
|
ri.Printf(PRINT_ALL, "%i world clusters created\n", numClusters + 1);
|
|
#endif // #if defined(USE_BSP_CLUSTERSURFACE_MERGING)
|
|
|
|
// reset surfaces' viewCount
|
|
for(i = 0, surface = s_worldData.surfaces; i < s_worldData.numSurfaces; i++, surface++)
|
|
{
|
|
surface->viewCount = -1;
|
|
}
|
|
|
|
for(j = 0, node = s_worldData.nodes; j < s_worldData.numnodes; j++, node++)
|
|
{
|
|
node->visCounts[0] = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
SmoothNormals()
|
|
smooths together coincident vertex normals across the bsp
|
|
*/
|
|
#if 0
|
|
#define MAX_SAMPLES 256
|
|
#define THETA_EPSILON 0.000001
|
|
#define EQUAL_NORMAL_EPSILON 0.01
|
|
|
|
void SmoothNormals(const char *name, srfVert_t * verts, int numTotalVerts)
|
|
{
|
|
int i, j, k, f, cs, numVerts, numVotes, fOld, startTime, endTime;
|
|
float shadeAngle, defaultShadeAngle, maxShadeAngle, dot, testAngle;
|
|
|
|
// shaderInfo_t *si;
|
|
float *shadeAngles;
|
|
byte *smoothed;
|
|
vec3_t average, diff;
|
|
int indexes[MAX_SAMPLES];
|
|
vec3_t votes[MAX_SAMPLES];
|
|
|
|
srfVert_t *yDrawVerts;
|
|
|
|
ri.Printf(PRINT_ALL, "smoothing normals for mesh '%s'\n", name);
|
|
|
|
yDrawVerts = Com_Allocate(numTotalVerts * sizeof(srfVert_t));
|
|
memcpy(yDrawVerts, verts, numTotalVerts * sizeof(srfVert_t));
|
|
|
|
// allocate shade angle table
|
|
shadeAngles = Com_Allocate(numTotalVerts * sizeof(float));
|
|
memset(shadeAngles, 0, numTotalVerts * sizeof(float));
|
|
|
|
// allocate smoothed table
|
|
cs = (numTotalVerts / 8) + 1;
|
|
smoothed = Com_Allocate(cs);
|
|
memset(smoothed, 0, cs);
|
|
|
|
// set default shade angle
|
|
defaultShadeAngle = DEG2RAD(179);
|
|
maxShadeAngle = defaultShadeAngle;
|
|
|
|
// run through every surface and flag verts belonging to non-lightmapped surfaces
|
|
// and set per-vertex smoothing angle
|
|
/*
|
|
for(i = 0; i < numBSPDrawSurfaces; i++)
|
|
{
|
|
// get drawsurf
|
|
ds = &bspDrawSurfaces[i];
|
|
|
|
// get shader for shade angle
|
|
si = surfaceInfos[i].si;
|
|
if(si->shadeAngleDegrees)
|
|
shadeAngle = DEG2RAD(si->shadeAngleDegrees);
|
|
else
|
|
shadeAngle = defaultShadeAngle;
|
|
if(shadeAngle > maxShadeAngle)
|
|
maxShadeAngle = shadeAngle;
|
|
|
|
// flag its verts
|
|
for(j = 0; j < ds->numVerts; j++)
|
|
{
|
|
f = ds->firstVert + j;
|
|
shadeAngles[f] = shadeAngle;
|
|
if(ds->surfaceType == MST_TRIANGLE_SOUP)
|
|
smoothed[f >> 3] |= (1 << (f & 7));
|
|
}
|
|
}
|
|
|
|
// bail if no surfaces have a shade angle
|
|
if(maxShadeAngle == 0)
|
|
{
|
|
Com_Dealloc(shadeAngles);
|
|
Com_Dealloc(smoothed);
|
|
return;
|
|
}
|
|
*/
|
|
|
|
// init pacifier
|
|
fOld = -1;
|
|
startTime = ri.Milliseconds();
|
|
|
|
// go through the list of vertexes
|
|
for(i = 0; i < numTotalVerts; i++)
|
|
{
|
|
// print pacifier
|
|
f = 10 * i / numTotalVerts;
|
|
if(f != fOld)
|
|
{
|
|
fOld = f;
|
|
ri.Printf(PRINT_ALL, "%i...", f);
|
|
}
|
|
|
|
// already smoothed?
|
|
if(smoothed[i >> 3] & (1 << (i & 7)))
|
|
continue;
|
|
|
|
// clear
|
|
VectorClear(average);
|
|
numVerts = 0;
|
|
numVotes = 0;
|
|
|
|
// build a table of coincident vertexes
|
|
for(j = i; j < numTotalVerts && numVerts < MAX_SAMPLES; j++)
|
|
{
|
|
// already smoothed?
|
|
if(smoothed[j >> 3] & (1 << (j & 7)))
|
|
continue;
|
|
|
|
// test vertexes
|
|
if(CompareWorldVertSmoothNormal(&yDrawVerts[i], &yDrawVerts[j]) == qfalse)
|
|
continue;
|
|
|
|
// use smallest shade angle
|
|
//shadeAngle = (shadeAngles[i] < shadeAngles[j] ? shadeAngles[i] : shadeAngles[j]);
|
|
shadeAngle = maxShadeAngle;
|
|
|
|
// check shade angle
|
|
dot = DotProduct(verts[i].normal, verts[j].normal);
|
|
if(dot > 1.0)
|
|
dot = 1.0;
|
|
else if(dot < -1.0)
|
|
dot = -1.0;
|
|
testAngle = acos(dot) + THETA_EPSILON;
|
|
if(testAngle >= shadeAngle)
|
|
{
|
|
//Sys_Printf( "F(%3.3f >= %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) );
|
|
continue;
|
|
}
|
|
//Sys_Printf( "P(%3.3f < %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) );
|
|
|
|
// add to the list
|
|
indexes[numVerts++] = j;
|
|
|
|
// flag vertex
|
|
smoothed[j >> 3] |= (1 << (j & 7));
|
|
|
|
// see if this normal has already been voted
|
|
for(k = 0; k < numVotes; k++)
|
|
{
|
|
VectorSubtract(verts[j].normal, votes[k], diff);
|
|
if(fabs(diff[0]) < EQUAL_NORMAL_EPSILON &&
|
|
fabs(diff[1]) < EQUAL_NORMAL_EPSILON && fabs(diff[2]) < EQUAL_NORMAL_EPSILON)
|
|
break;
|
|
}
|
|
|
|
// add a new vote?
|
|
if(k == numVotes && numVotes < MAX_SAMPLES)
|
|
{
|
|
VectorAdd(average, verts[j].normal, average);
|
|
VectorCopy(verts[j].normal, votes[numVotes]);
|
|
numVotes++;
|
|
}
|
|
}
|
|
|
|
// don't average for less than 2 verts
|
|
if(numVerts < 2)
|
|
continue;
|
|
|
|
// average normal
|
|
if(VectorNormalize(average) > 0)
|
|
{
|
|
// smooth
|
|
for(j = 0; j < numVerts; j++)
|
|
VectorCopy(average, yDrawVerts[indexes[j]].normal);
|
|
}
|
|
}
|
|
|
|
// copy yDrawVerts normals back
|
|
for(i = 0; i < numTotalVerts; i++)
|
|
{
|
|
VectorCopy(yDrawVerts[i].normal, verts[i].normal);
|
|
}
|
|
|
|
// free the tables
|
|
Com_Dealloc(yDrawVerts);
|
|
Com_Dealloc(shadeAngles);
|
|
Com_Dealloc(smoothed);
|
|
|
|
endTime = ri.Milliseconds();
|
|
ri.Printf(PRINT_ALL, " (%5.2f seconds)\n", (endTime - startTime) / 1000.0);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
===============
|
|
R_CreateWorldVBO
|
|
===============
|
|
*/
|
|
static void R_CreateWorldVBO()
|
|
{
|
|
int i, j, k;
|
|
|
|
int numVerts;
|
|
srfVert_t *verts;
|
|
|
|
// srfVert_t *optimizedVerts;
|
|
|
|
int numTriangles;
|
|
srfTriangle_t *triangles;
|
|
|
|
// int numSurfaces;
|
|
bspSurface_t *surface;
|
|
|
|
// trRefLight_t *light;
|
|
|
|
int startTime, endTime;
|
|
|
|
startTime = ri.Milliseconds();
|
|
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numWorldSurfaces; k++, surface++)
|
|
{
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *face = (srfSurfaceFace_t *) surface->data;
|
|
|
|
if(face->numVerts)
|
|
numVerts += face->numVerts;
|
|
|
|
if(face->numTriangles)
|
|
numTriangles += face->numTriangles;
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *grid = (srfGridMesh_t *) surface->data;
|
|
|
|
if(grid->numVerts)
|
|
numVerts += grid->numVerts;
|
|
|
|
if(grid->numTriangles)
|
|
numTriangles += grid->numTriangles;
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *tri = (srfTriangles_t *) surface->data;
|
|
|
|
if(tri->numVerts)
|
|
numVerts += tri->numVerts;
|
|
|
|
if(tri->numTriangles)
|
|
numTriangles += tri->numTriangles;
|
|
}
|
|
}
|
|
|
|
if(!numVerts || !numTriangles)
|
|
return;
|
|
|
|
ri.Printf(PRINT_ALL, "...calculating world VBO ( %i verts %i tris )\n", numVerts, numTriangles);
|
|
|
|
// create arrays
|
|
|
|
s_worldData.numVerts = numVerts;
|
|
s_worldData.verts = verts = ri.Hunk_Alloc(numVerts * sizeof(srfVert_t), h_low);
|
|
//optimizedVerts = ri.Malloc(numVerts * sizeof(srfVert_t));
|
|
|
|
s_worldData.numTriangles = numTriangles;
|
|
s_worldData.triangles = triangles = ri.Hunk_Alloc(numTriangles * sizeof(srfTriangle_t), h_low);
|
|
|
|
// set up triangle indices
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numWorldSurfaces; k++, surface++)
|
|
{
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
|
|
|
|
srf->firstTriangle = numTriangles;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
|
|
for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
|
|
}
|
|
}
|
|
|
|
numTriangles += srf->numTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface->data;
|
|
|
|
srf->firstTriangle = numTriangles;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
|
|
for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
|
|
}
|
|
}
|
|
|
|
numTriangles += srf->numTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface->data;
|
|
|
|
srf->firstTriangle = numTriangles;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
|
|
for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
|
|
}
|
|
}
|
|
|
|
numTriangles += srf->numTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
}
|
|
|
|
// build vertices
|
|
numVerts = 0;
|
|
for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numWorldSurfaces; k++, surface++)
|
|
{
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
|
|
|
|
srf->firstVert = numVerts;
|
|
|
|
if(srf->numVerts)
|
|
{
|
|
for(i = 0; i < srf->numVerts; i++)
|
|
{
|
|
CopyVert(&srf->verts[i], &verts[numVerts + i]);
|
|
}
|
|
|
|
numVerts += srf->numVerts;
|
|
}
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface->data;
|
|
|
|
srf->firstVert = numVerts;
|
|
|
|
if(srf->numVerts)
|
|
{
|
|
for(i = 0; i < srf->numVerts; i++)
|
|
{
|
|
CopyVert(&srf->verts[i], &verts[numVerts + i]);
|
|
}
|
|
|
|
numVerts += srf->numVerts;
|
|
}
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface->data;
|
|
|
|
srf->firstVert = numVerts;
|
|
|
|
if(srf->numVerts)
|
|
{
|
|
for(i = 0; i < srf->numVerts; i++)
|
|
{
|
|
CopyVert(&srf->verts[i], &verts[numVerts + i]);
|
|
}
|
|
|
|
numVerts += srf->numVerts;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
numVerts = OptimizeVertices(numVerts, verts, numTriangles, triangles, optimizedVerts, CompareWorldVert);
|
|
if(c_redundantVertexes)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER,
|
|
"...removed %i redundant vertices from staticWorldMesh %i ( %s, %i verts %i tris )\n",
|
|
c_redundantVertexes, vboSurfaces.currentElements, shader->name, numVerts, numTriangles);
|
|
}
|
|
|
|
s_worldData.vbo = R_CreateVBO2(va("bspModelMesh_vertices %i", 0), numVerts, optimizedVerts,
|
|
ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_TANGENT | ATTR_BINORMAL |
|
|
ATTR_NORMAL | ATTR_COLOR | GLCS_LIGHTCOLOR | ATTR_LIGHTDIRECTION);
|
|
#else
|
|
s_worldData.vbo = R_CreateVBO2(va("staticBspModel0_VBO %i", 0), numVerts, verts,
|
|
ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_TANGENT | ATTR_BINORMAL |
|
|
ATTR_NORMAL | ATTR_COLOR
|
|
#if !defined(COMPAT_Q3A) && !defined(COMPAT_ET)
|
|
| ATTR_PAINTCOLOR | ATTR_LIGHTDIRECTION
|
|
#endif
|
|
, VBO_USAGE_STATIC);
|
|
#endif
|
|
|
|
s_worldData.ibo = R_CreateIBO2(va("staticBspModel0_IBO %i", 0), numTriangles, triangles, VBO_USAGE_STATIC);
|
|
|
|
endTime = ri.Milliseconds();
|
|
ri.Printf(PRINT_ALL, "world VBO calculation time = %5.2f seconds\n", (endTime - startTime) / 1000.0);
|
|
|
|
|
|
// point triangle surfaces to world VBO
|
|
for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numWorldSurfaces; k++, surface++)
|
|
{
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
|
|
|
|
//if(r_vboFaces->integer && srf->numVerts && srf->numTriangles)
|
|
{
|
|
srf->vbo = s_worldData.vbo;
|
|
srf->ibo = s_worldData.ibo;
|
|
//srf->ibo = R_CreateIBO2(va("staticBspModel0_planarSurface_IBO %i", k), srf->numTriangles, triangles + srf->firstTriangle, VBO_USAGE_STATIC);
|
|
}
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface->data;
|
|
|
|
//if(r_vboCurves->integer && srf->numVerts && srf->numTriangles)
|
|
{
|
|
srf->vbo = s_worldData.vbo;
|
|
srf->ibo = s_worldData.ibo;
|
|
//srf->ibo = R_CreateIBO2(va("staticBspModel0_curveSurface_IBO %i", k), srf->numTriangles, triangles + srf->firstTriangle, VBO_USAGE_STATIC);
|
|
}
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface->data;
|
|
|
|
//if(r_vboTriangles->integer && srf->numVerts && srf->numTriangles)
|
|
{
|
|
srf->vbo = s_worldData.vbo;
|
|
srf->ibo = s_worldData.ibo;
|
|
//srf->ibo = R_CreateIBO2(va("staticBspModel0_triangleSurface_IBO %i", k), srf->numTriangles, triangles + srf->firstTriangle, VBO_USAGE_STATIC);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
startTime = ri.Milliseconds();
|
|
|
|
// Tr3B: FIXME move this to somewhere else?
|
|
#if CALC_REDUNDANT_SHADOWVERTS
|
|
s_worldData.redundantVertsCalculationNeeded = 0;
|
|
for(i = 0; i < s_worldData.numLights; i++)
|
|
{
|
|
light = &s_worldData.lights[i];
|
|
|
|
if((r_precomputedLighting->integer || r_vertexLighting->integer) && !light->noRadiosity)
|
|
continue;
|
|
|
|
s_worldData.redundantVertsCalculationNeeded++;
|
|
}
|
|
|
|
if(s_worldData.redundantVertsCalculationNeeded)
|
|
{
|
|
ri.Printf(PRINT_ALL, "...calculating redundant world vertices ( %i verts )\n", numVerts);
|
|
|
|
s_worldData.redundantLightVerts = ri.Hunk_Alloc(numVerts * sizeof(int), h_low);
|
|
BuildRedundantIndices(numVerts, verts, s_worldData.redundantLightVerts, CompareLightVert);
|
|
|
|
s_worldData.redundantShadowVerts = ri.Hunk_Alloc(numVerts * sizeof(int), h_low);
|
|
BuildRedundantIndices(numVerts, verts, s_worldData.redundantShadowVerts, CompareShadowVert);
|
|
|
|
s_worldData.redundantShadowAlphaTestVerts = ri.Hunk_Alloc(numVerts * sizeof(int), h_low);
|
|
BuildRedundantIndices(numVerts, verts, s_worldData.redundantShadowAlphaTestVerts, CompareShadowVertAlphaTest);
|
|
}
|
|
|
|
endTime = ri.Milliseconds();
|
|
ri.Printf(PRINT_ALL, "redundant world vertices calculation time = %5.2f seconds\n", (endTime - startTime) / 1000.0);
|
|
#endif
|
|
|
|
//ri.Free(triangles);
|
|
//ri.Free(optimizedVerts);
|
|
//ri.Free(verts);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_CreateSubModelVBOs
|
|
===============
|
|
*/
|
|
static void R_CreateSubModelVBOs()
|
|
{
|
|
int i, j, k, l, m;
|
|
|
|
int numVerts;
|
|
srfVert_t *verts;
|
|
|
|
srfVert_t *optimizedVerts;
|
|
|
|
int numTriangles;
|
|
srfTriangle_t *triangles;
|
|
|
|
shader_t *shader, *oldShader;
|
|
int lightmapNum, oldLightmapNum;
|
|
|
|
int numSurfaces;
|
|
bspSurface_t *surface, *surface2;
|
|
bspSurface_t **surfacesSorted;
|
|
|
|
bspModel_t *model;
|
|
|
|
growList_t vboSurfaces;
|
|
srfVBOMesh_t *vboSurf;
|
|
|
|
for(m = 1, model = s_worldData.models; m < s_worldData.numModels; m++, model++)
|
|
{
|
|
// count number of static area surfaces
|
|
numSurfaces = 0;
|
|
for(k = 0; k < model->numSurfaces; k++)
|
|
{
|
|
surface = model->firstSurface + k;
|
|
shader = surface->shader;
|
|
|
|
if(shader->isSky)
|
|
continue;
|
|
|
|
if(shader->isPortal)
|
|
continue;
|
|
|
|
if(ShaderRequiresCPUDeforms(shader))
|
|
continue;
|
|
|
|
numSurfaces++;
|
|
}
|
|
|
|
if(!numSurfaces)
|
|
continue;
|
|
|
|
// build interaction caches list
|
|
surfacesSorted = ri.Malloc(numSurfaces * sizeof(surfacesSorted[0]));
|
|
|
|
numSurfaces = 0;
|
|
for(k = 0; k < model->numSurfaces; k++)
|
|
{
|
|
surface = model->firstSurface + k;
|
|
shader = surface->shader;
|
|
|
|
if(shader->isSky)
|
|
continue;
|
|
|
|
if(shader->isPortal)
|
|
continue;
|
|
|
|
if(ShaderRequiresCPUDeforms(shader))
|
|
continue;
|
|
|
|
surfacesSorted[numSurfaces] = surface;
|
|
numSurfaces++;
|
|
}
|
|
|
|
Com_InitGrowList(&vboSurfaces, 100);
|
|
|
|
// sort surfaces by shader
|
|
qsort(surfacesSorted, numSurfaces, sizeof(surfacesSorted), BSPSurfaceCompare);
|
|
|
|
// create a VBO for each shader
|
|
shader = oldShader = NULL;
|
|
lightmapNum = oldLightmapNum = -1;
|
|
|
|
for(k = 0; k < numSurfaces; k++)
|
|
{
|
|
surface = surfacesSorted[k];
|
|
shader = surface->shader;
|
|
lightmapNum = surface->lightmapNum;
|
|
|
|
if(shader != oldShader || (r_precomputedLighting->integer ? lightmapNum != oldLightmapNum : 0))
|
|
{
|
|
oldShader = shader;
|
|
oldLightmapNum = lightmapNum;
|
|
|
|
// count vertices and indices
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
|
|
for(l = k; l < numSurfaces; l++)
|
|
{
|
|
surface2 = surfacesSorted[l];
|
|
|
|
if(surface2->shader != shader)
|
|
continue;
|
|
|
|
if(*surface2->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *face = (srfSurfaceFace_t *) surface2->data;
|
|
|
|
if(face->numVerts)
|
|
numVerts += face->numVerts;
|
|
|
|
if(face->numTriangles)
|
|
numTriangles += face->numTriangles;
|
|
}
|
|
else if(*surface2->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *grid = (srfGridMesh_t *) surface2->data;
|
|
|
|
if(grid->numVerts)
|
|
numVerts += grid->numVerts;
|
|
|
|
if(grid->numTriangles)
|
|
numTriangles += grid->numTriangles;
|
|
}
|
|
else if(*surface2->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *tri = (srfTriangles_t *) surface2->data;
|
|
|
|
if(tri->numVerts)
|
|
numVerts += tri->numVerts;
|
|
|
|
if(tri->numTriangles)
|
|
numTriangles += tri->numTriangles;
|
|
}
|
|
}
|
|
|
|
if(!numVerts || !numTriangles)
|
|
continue;
|
|
|
|
ri.Printf(PRINT_DEVELOPER, "...calculating entity mesh VBOs ( %s, %i verts %i tris )\n", shader->name, numVerts,
|
|
numTriangles);
|
|
|
|
// create surface
|
|
vboSurf = ri.Hunk_Alloc(sizeof(*vboSurf), h_low);
|
|
Com_AddToGrowList(&vboSurfaces, vboSurf);
|
|
|
|
vboSurf->surfaceType = SF_VBO_MESH;
|
|
vboSurf->numIndexes = numTriangles * 3;
|
|
vboSurf->numVerts = numVerts;
|
|
|
|
vboSurf->shader = shader;
|
|
vboSurf->lightmapNum = lightmapNum;
|
|
|
|
// create arrays
|
|
verts = ri.Malloc(numVerts * sizeof(srfVert_t));
|
|
optimizedVerts = ri.Malloc(numVerts * sizeof(srfVert_t));
|
|
numVerts = 0;
|
|
|
|
triangles = ri.Malloc(numTriangles * sizeof(srfTriangle_t));
|
|
numTriangles = 0;
|
|
|
|
ClearBounds(vboSurf->bounds[0], vboSurf->bounds[1]);
|
|
|
|
// build triangle indices
|
|
for(l = k; l < numSurfaces; l++)
|
|
{
|
|
surface2 = surfacesSorted[l];
|
|
|
|
if(surface2->shader != shader)
|
|
continue;
|
|
|
|
// set up triangle indices
|
|
if(*surface2->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface2->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
|
|
for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
|
|
}
|
|
}
|
|
|
|
numTriangles += srf->numTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface2->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface2->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
|
|
for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
|
|
}
|
|
}
|
|
|
|
numTriangles += srf->numTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface2->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface2->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
srfTriangle_t *tri;
|
|
|
|
for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
|
|
}
|
|
}
|
|
|
|
numTriangles += srf->numTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
}
|
|
|
|
// build vertices
|
|
numVerts = 0;
|
|
for(l = k; l < numSurfaces; l++)
|
|
{
|
|
surface2 = surfacesSorted[l];
|
|
|
|
if(surface2->shader != shader)
|
|
continue;
|
|
|
|
if(*surface2->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *cv = (srfSurfaceFace_t *) surface2->data;
|
|
|
|
if(cv->numVerts)
|
|
{
|
|
for(i = 0; i < cv->numVerts; i++)
|
|
{
|
|
CopyVert(&cv->verts[i], &verts[numVerts + i]);
|
|
|
|
AddPointToBounds(cv->verts[i].xyz, vboSurf->bounds[0], vboSurf->bounds[1]);
|
|
}
|
|
|
|
numVerts += cv->numVerts;
|
|
}
|
|
}
|
|
else if(*surface2->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *cv = (srfGridMesh_t *) surface2->data;
|
|
|
|
if(cv->numVerts)
|
|
{
|
|
for(i = 0; i < cv->numVerts; i++)
|
|
{
|
|
CopyVert(&cv->verts[i], &verts[numVerts + i]);
|
|
|
|
AddPointToBounds(cv->verts[i].xyz, vboSurf->bounds[0], vboSurf->bounds[1]);
|
|
}
|
|
|
|
numVerts += cv->numVerts;
|
|
}
|
|
}
|
|
else if(*surface2->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *cv = (srfTriangles_t *) surface2->data;
|
|
|
|
if(cv->numVerts)
|
|
{
|
|
for(i = 0; i < cv->numVerts; i++)
|
|
{
|
|
CopyVert(&cv->verts[i], &verts[numVerts + i]);
|
|
|
|
AddPointToBounds(cv->verts[i].xyz, vboSurf->bounds[0], vboSurf->bounds[1]);
|
|
}
|
|
|
|
numVerts += cv->numVerts;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
numVerts = OptimizeVertices(numVerts, verts, numTriangles, triangles, optimizedVerts, CompareWorldVert);
|
|
if(c_redundantVertexes)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER,
|
|
"...removed %i redundant vertices from staticEntityMesh %i ( %s, %i verts %i tris )\n",
|
|
c_redundantVertexes, vboSurfaces.currentElements, shader->name, numVerts, numTriangles);
|
|
}
|
|
|
|
vboSurf->vbo =
|
|
R_CreateVBO2(va("staticBspModel%i_VBO %i", m, vboSurfaces.currentElements), numVerts, optimizedVerts,
|
|
ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_TANGENT | ATTR_BINORMAL | ATTR_NORMAL
|
|
| ATTR_COLOR | GLCS_LIGHTCOLOR | ATTR_LIGHTDIRECTION, VBO_USAGE_STATIC);
|
|
#else
|
|
vboSurf->vbo =
|
|
R_CreateVBO2(va("staticBspModel%i_VBO %i", m, vboSurfaces.currentElements), numVerts, verts,
|
|
ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_TANGENT | ATTR_BINORMAL | ATTR_NORMAL
|
|
| ATTR_COLOR
|
|
#if !defined(COMPAT_Q3A) && !defined(COMPAT_ET)
|
|
| ATTR_PAINTCOLOR | ATTR_LIGHTDIRECTION
|
|
#endif
|
|
, VBO_USAGE_STATIC);
|
|
#endif
|
|
|
|
vboSurf->ibo =
|
|
R_CreateIBO2(va("staticBspModel%i_IBO %i", m, vboSurfaces.currentElements), numTriangles, triangles,
|
|
VBO_USAGE_STATIC);
|
|
|
|
ri.Free(triangles);
|
|
ri.Free(optimizedVerts);
|
|
ri.Free(verts);
|
|
}
|
|
}
|
|
|
|
ri.Free(surfacesSorted);
|
|
|
|
// move VBO surfaces list to hunk
|
|
model->numVBOSurfaces = vboSurfaces.currentElements;
|
|
model->vboSurfaces = ri.Hunk_Alloc(model->numVBOSurfaces * sizeof(*model->vboSurfaces), h_low);
|
|
|
|
for(i = 0; i < model->numVBOSurfaces; i++)
|
|
{
|
|
model->vboSurfaces[i] = (srfVBOMesh_t *) Com_GrowListElement(&vboSurfaces, i);
|
|
}
|
|
|
|
Com_DestroyGrowList(&vboSurfaces);
|
|
|
|
ri.Printf(PRINT_ALL, "%i VBO surfaces created for BSP submodel %i\n", model->numVBOSurfaces, m);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_LoadSurfaces
|
|
===============
|
|
*/
|
|
static void R_LoadSurfaces(lump_t * surfs, lump_t * verts, lump_t * indexLump)
|
|
{
|
|
dsurface_t *in;
|
|
bspSurface_t *out;
|
|
drawVert_t *dv;
|
|
int *indexes;
|
|
int count;
|
|
int numFaces, numMeshes, numTriSurfs, numFlares, numFoliages;
|
|
int i;
|
|
|
|
ri.Printf(PRINT_ALL, "...loading surfaces\n");
|
|
|
|
numFaces = 0;
|
|
numMeshes = 0;
|
|
numTriSurfs = 0;
|
|
numFlares = 0;
|
|
numFoliages = 0;
|
|
|
|
in = (void *)(fileBase + surfs->fileofs);
|
|
if(surfs->filelen % sizeof(*in))
|
|
ri.Error(ERR_DROP, "LoadMap: funny lump size in %s", s_worldData.name);
|
|
count = surfs->filelen / sizeof(*in);
|
|
|
|
dv = (void *)(fileBase + verts->fileofs);
|
|
if(verts->filelen % sizeof(*dv))
|
|
ri.Error(ERR_DROP, "LoadMap: funny lump size in %s", s_worldData.name);
|
|
|
|
indexes = (void *)(fileBase + indexLump->fileofs);
|
|
if(indexLump->filelen % sizeof(*indexes))
|
|
ri.Error(ERR_DROP, "LoadMap: funny lump size in %s", s_worldData.name);
|
|
|
|
out = ri.Hunk_Alloc(count * sizeof(*out), h_low);
|
|
|
|
s_worldData.surfaces = out;
|
|
s_worldData.numSurfaces = count;
|
|
|
|
for(i = 0; i < count; i++, in++, out++)
|
|
{
|
|
switch (LittleLong(in->surfaceType))
|
|
{
|
|
case MST_PATCH:
|
|
ParseMesh(in, dv, out);
|
|
numMeshes++;
|
|
break;
|
|
case MST_TRIANGLE_SOUP:
|
|
ParseTriSurf(in, dv, out, indexes);
|
|
numTriSurfs++;
|
|
break;
|
|
case MST_PLANAR:
|
|
ParseFace(in, dv, out, indexes);
|
|
numFaces++;
|
|
break;
|
|
case MST_FLARE:
|
|
ParseFlare(in, dv, out, indexes);
|
|
numFlares++;
|
|
break;
|
|
//case MST_FOLIAGE:
|
|
// // Tr3B: TODO ParseFoliage
|
|
// ParseTriSurf(in, dv, out, indexes);
|
|
// numFoliages++;
|
|
// break;
|
|
default:
|
|
ri.Error(ERR_DROP, "Bad surfaceType");
|
|
}
|
|
}
|
|
|
|
ri.Printf(PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares %i foliages\n", numFaces, numMeshes, numTriSurfs,
|
|
numFlares, numFoliages);
|
|
|
|
if(r_stitchCurves->integer)
|
|
{
|
|
R_StitchAllPatches();
|
|
}
|
|
|
|
R_FixSharedVertexLodError();
|
|
|
|
if(r_stitchCurves->integer)
|
|
{
|
|
R_MovePatchSurfacesToHunk();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=================
|
|
R_LoadSubmodels
|
|
=================
|
|
*/
|
|
static void R_LoadSubmodels(lump_t * l)
|
|
{
|
|
dmodel_t *in;
|
|
bspModel_t *out;
|
|
int i, j, count;
|
|
|
|
ri.Printf(PRINT_ALL, "...loading submodels\n");
|
|
|
|
in = (void *)(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.numModels = count;
|
|
s_worldData.models = out = 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_BSP;
|
|
model->bsp = 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 = s_worldData.surfaces + LittleLong(in->firstSurface);
|
|
out->numSurfaces = LittleLong(in->numSurfaces);
|
|
|
|
if(i == 0)
|
|
{
|
|
// Tr3B: add this for limiting VBO surface creation
|
|
s_worldData.numWorldSurfaces = out->numSurfaces;
|
|
}
|
|
|
|
// ydnar: for attaching fog brushes to models
|
|
//out->firstBrush = LittleLong(in->firstBrush);
|
|
//out->numBrushes = LittleLong(in->numBrushes);
|
|
|
|
// ydnar: allocate decal memory
|
|
j = (i == 0 ? MAX_WORLD_DECALS : MAX_ENTITY_DECALS);
|
|
out->decals = ri.Hunk_Alloc(j * sizeof(*out->decals), h_low);
|
|
memset(out->decals, 0, j * sizeof(*out->decals));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//==================================================================
|
|
|
|
/*
|
|
=================
|
|
R_SetParent
|
|
=================
|
|
*/
|
|
static void R_SetParent(bspNode_t * node, bspNode_t * parent)
|
|
{
|
|
node->parent = parent;
|
|
|
|
|
|
if(node->contents != CONTENTS_NODE)
|
|
{
|
|
/*
|
|
node->sameAABBAsParent = VectorCompare(node->mins, parent->mins) && VectorCompare(node->maxs, parent->maxs);
|
|
if(node->sameAABBAsParent)
|
|
{
|
|
//ri.Printf(PRINT_ALL, "node %i has same AABB as their parent\n", node - s_worldData.nodes);
|
|
}
|
|
*/
|
|
|
|
// add node surfaces to bounds
|
|
if(node->numMarkSurfaces > 0)
|
|
{
|
|
int c;
|
|
bspSurface_t **mark;
|
|
srfGeneric_t *gen;
|
|
qboolean mergedSurfBounds;
|
|
|
|
// add node surfaces to bounds
|
|
mark = node->markSurfaces;
|
|
c = node->numMarkSurfaces;
|
|
ClearBounds(node->surfMins, node->surfMaxs);
|
|
mergedSurfBounds = qfalse;
|
|
while(c--)
|
|
{
|
|
gen = (srfGeneric_t *) (**mark).data;
|
|
if(gen->surfaceType != SF_FACE &&
|
|
gen->surfaceType != SF_GRID && gen->surfaceType != SF_TRIANGLES)// && gen->surfaceType != SF_FOLIAGE)
|
|
{
|
|
continue;
|
|
}
|
|
AddPointToBounds(gen->bounds[0], node->surfMins, node->surfMaxs);
|
|
AddPointToBounds(gen->bounds[1], node->surfMins, node->surfMaxs);
|
|
mark++;
|
|
mergedSurfBounds = qtrue;
|
|
}
|
|
|
|
if(!mergedSurfBounds)
|
|
{
|
|
VectorCopy(node->mins, node->surfMins);
|
|
VectorCopy(node->maxs, node->surfMaxs);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
R_SetParent(node->children[0], node);
|
|
R_SetParent(node->children[1], node);
|
|
|
|
// ydnar: surface bounds
|
|
#if 1
|
|
AddPointToBounds(node->children[0]->surfMins, node->surfMins, node->surfMaxs);
|
|
AddPointToBounds(node->children[0]->surfMins, node->surfMins, node->surfMaxs);
|
|
AddPointToBounds(node->children[1]->surfMins, node->surfMins, node->surfMaxs);
|
|
AddPointToBounds(node->children[1]->surfMaxs, node->surfMins, node->surfMaxs);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_LoadNodesAndLeafs
|
|
=================
|
|
*/
|
|
static void R_LoadNodesAndLeafs(lump_t * nodeLump, lump_t * leafLump)
|
|
{
|
|
int i, j, p;
|
|
dnode_t *in;
|
|
dleaf_t *inLeaf;
|
|
bspNode_t *out;
|
|
int numNodes, numLeafs;
|
|
srfVert_t *verts = NULL;
|
|
srfTriangle_t *triangles = NULL;
|
|
IBO_t *volumeIBO;
|
|
vec3_t mins, maxs;
|
|
// vec3_t offset = {0.01, 0.01, 0.01};
|
|
|
|
ri.Printf(PRINT_ALL, "...loading nodes and leaves\n");
|
|
|
|
in = (void *)(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 = ri.Hunk_Alloc((numNodes + numLeafs) * sizeof(*out), h_low);
|
|
|
|
s_worldData.nodes = out;
|
|
s_worldData.numnodes = numNodes + numLeafs;
|
|
s_worldData.numDecisionNodes = numNodes;
|
|
|
|
// ydnar: skybox optimization
|
|
s_worldData.numSkyNodes = 0;
|
|
s_worldData.skyNodes = ri.Hunk_Alloc(WORLD_MAX_SKY_NODES * sizeof(*s_worldData.skyNodes), h_low);
|
|
|
|
// 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]);
|
|
}
|
|
|
|
// ydnar: surface bounds
|
|
VectorCopy(out->mins, out->surfMins);
|
|
VectorCopy(out->maxs, out->surfMaxs);
|
|
|
|
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 = (void *)(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]);
|
|
}
|
|
|
|
// ydnar: surface bounds
|
|
ClearBounds(out->surfMins, out->surfMaxs);
|
|
|
|
out->cluster = LittleLong(inLeaf->cluster);
|
|
out->area = LittleLong(inLeaf->area);
|
|
|
|
if(out->cluster >= s_worldData.numClusters)
|
|
{
|
|
s_worldData.numClusters = out->cluster + 1;
|
|
}
|
|
|
|
out->markSurfaces = s_worldData.markSurfaces + LittleLong(inLeaf->firstLeafSurface);
|
|
out->numMarkSurfaces = LittleLong(inLeaf->numLeafSurfaces);
|
|
}
|
|
|
|
// chain decendants and compute surface bounds
|
|
R_SetParent(s_worldData.nodes, NULL);
|
|
|
|
// calculate occlusion query volumes
|
|
for(j = 0, out = &s_worldData.nodes[0]; j < s_worldData.numnodes; j++, out++)
|
|
{
|
|
//if(out->contents != -1 && !out->numMarkSurfaces)
|
|
// ri.Error(ERR_DROP, "leaf %i is empty", j);
|
|
|
|
Com_Memset(out->lastVisited, -1, sizeof(out->lastVisited));
|
|
Com_Memset(out->visible, qfalse, sizeof(out->visible));
|
|
//out->occlusionQuerySamples[0] = 1;
|
|
|
|
InitLink(&out->visChain, out);
|
|
InitLink(&out->occlusionQuery, out);
|
|
InitLink(&out->occlusionQuery2, out);
|
|
//QueueInit(&node->multiQuery);
|
|
|
|
glGenQueriesARB(MAX_VIEWS, out->occlusionQueryObjects);
|
|
|
|
tess.multiDrawPrimitives = 0;
|
|
tess.numIndexes = 0;
|
|
tess.numVertexes = 0;
|
|
|
|
#if 0
|
|
out->shrinkedAABB = qfalse;
|
|
if(out->contents != CONTENTS_NODE && out->numMarkSurfaces)
|
|
{
|
|
// BSP leaves don't have an optimal size so shrink them if possible by their surfaces
|
|
#if 1
|
|
mins[0] = Q_min(Q_max(out->mins[0], out->surfMins[0]), out->maxs[0]);
|
|
mins[1] = Q_min(Q_max(out->mins[1], out->surfMins[1]), out->maxs[1]);
|
|
mins[2] = Q_min(Q_max(out->mins[2], out->surfMins[2]), out->maxs[2]);
|
|
|
|
maxs[0] = Q_max(Q_min(out->maxs[0], out->surfMaxs[0]), out->mins[0]);
|
|
maxs[1] = Q_max(Q_min(out->maxs[1], out->surfMaxs[1]), out->mins[1]);
|
|
maxs[2] = Q_max(Q_min(out->maxs[2], out->surfMaxs[2]), out->mins[2]);
|
|
#else
|
|
mins[0] = Q_max(out->mins[0], out->surfMins[0]);
|
|
mins[1] = Q_max(out->mins[1], out->surfMins[1]);
|
|
mins[2] = Q_max(out->mins[2], out->surfMins[2]);
|
|
|
|
maxs[0] = Q_min(out->maxs[0], out->surfMaxs[0]);
|
|
maxs[1] = Q_min(out->maxs[1], out->surfMaxs[1]);
|
|
maxs[2] = Q_min(out->maxs[2], out->surfMaxs[2]);
|
|
#endif
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
if(mins[i] > maxs[i])
|
|
{
|
|
float tmp = mins[i];
|
|
mins[i] = maxs[i];
|
|
maxs[i] = tmp;
|
|
}
|
|
}
|
|
|
|
VectorCopy(out->surfMins, mins);
|
|
VectorCopy(out->surfMaxs, maxs);
|
|
|
|
if(!VectorCompareEpsilon(out->mins, mins, 1.0) || !VectorCompareEpsilon(out->maxs, maxs, 1.0))
|
|
out->shrinkedAABB = qtrue;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
VectorCopy(out->mins, mins);
|
|
VectorCopy(out->maxs, maxs);
|
|
}
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
out->origin[i] = (mins[i] + maxs[i]) * 0.5f;
|
|
}
|
|
|
|
#if 0
|
|
// HACK: make the AABB a little bit smaller to avoid z-fighting for the occlusion queries
|
|
VectorAdd(mins, offset, mins);
|
|
VectorSubtract(maxs, offset, maxs);
|
|
#endif
|
|
Tess_AddCube(vec3_origin, mins, maxs, colorWhite);
|
|
|
|
if(j == 0)
|
|
{
|
|
verts = ri.Malloc(tess.numVertexes * sizeof(srfVert_t));
|
|
triangles = ri.Malloc((tess.numIndexes / 3) * sizeof(srfTriangle_t));
|
|
}
|
|
|
|
for(i = 0; i < tess.numVertexes; i++)
|
|
{
|
|
VectorCopy(tess.xyz[i], verts[i].xyz);
|
|
}
|
|
|
|
for(i = 0; i < (tess.numIndexes / 3); i++)
|
|
{
|
|
triangles[i].indexes[0] = tess.indexes[i * 3 + 0];
|
|
triangles[i].indexes[1] = tess.indexes[i * 3 + 1];
|
|
triangles[i].indexes[2] = tess.indexes[i * 3 + 2];
|
|
}
|
|
|
|
out->volumeVBO = R_CreateVBO2(va("staticBspNode_VBO %i", j), tess.numVertexes, verts, ATTR_POSITION, VBO_USAGE_STATIC);
|
|
|
|
if(j == 0)
|
|
{
|
|
out->volumeIBO = volumeIBO = R_CreateIBO2(va("staticBspNode_IBO %i", j), tess.numIndexes / 3, triangles, VBO_USAGE_STATIC);
|
|
}
|
|
else
|
|
{
|
|
out->volumeIBO = volumeIBO;
|
|
}
|
|
|
|
out->volumeVerts = tess.numVertexes;
|
|
out->volumeIndexes = tess.numIndexes;
|
|
}
|
|
|
|
if(triangles)
|
|
{
|
|
ri.Free(triangles);
|
|
}
|
|
if(verts)
|
|
{
|
|
ri.Free(verts);
|
|
}
|
|
|
|
tess.multiDrawPrimitives = 0;
|
|
tess.numIndexes = 0;
|
|
tess.numVertexes = 0;
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
=================
|
|
R_LoadShaders
|
|
=================
|
|
*/
|
|
static void R_LoadShaders(lump_t * l)
|
|
{
|
|
int i, count;
|
|
dshader_t *in, *out;
|
|
|
|
ri.Printf(PRINT_ALL, "...loading shaders\n");
|
|
|
|
in = (void *)(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 = 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++)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER, "shader: '%s'\n", out[i].shader);
|
|
|
|
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;
|
|
bspSurface_t **out;
|
|
|
|
ri.Printf(PRINT_ALL, "...loading mark surfaces\n");
|
|
|
|
in = (void *)(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 = 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] = s_worldData.surfaces + j;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
R_LoadPlanes
|
|
=================
|
|
*/
|
|
static void R_LoadPlanes(lump_t * l)
|
|
{
|
|
int i, j;
|
|
cplane_t *out;
|
|
dplane_t *in;
|
|
int count;
|
|
int bits;
|
|
|
|
ri.Printf(PRINT_ALL, "...loading planes\n");
|
|
|
|
in = (void *)(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 = 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 = 0;
|
|
|
|
ri.Printf(PRINT_ALL, "...loading fogs\n");
|
|
|
|
fogs = (void *)(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 = ri.Hunk_Alloc(s_worldData.numFogs * sizeof(*out), h_low);
|
|
out = s_worldData.fogs + 1;
|
|
|
|
// ydnar: reset global fog
|
|
s_worldData.globalFog = -1;
|
|
|
|
if(!count)
|
|
{
|
|
ri.Printf(PRINT_ALL, "no fog volumes loaded\n");
|
|
return;
|
|
}
|
|
|
|
brushes = (void *)(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 = (void *)(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);
|
|
|
|
// ydnar: global fog has a brush number of -1, and no visible side
|
|
if(out->originalBrushNumber == -1)
|
|
{
|
|
VectorSet(out->bounds[0], MIN_WORLD_COORD, MIN_WORLD_COORD, MIN_WORLD_COORD);
|
|
VectorSet(out->bounds[1], MAX_WORLD_COORD, MAX_WORLD_COORD, MAX_WORLD_COORD);
|
|
}
|
|
else
|
|
{
|
|
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, SHADER_3D_DYNAMIC, qtrue);
|
|
|
|
out->fogParms = shader->fogParms;
|
|
|
|
out->color[0] = shader->fogParms.color[0] * tr.identityLight;
|
|
out->color[1] = shader->fogParms.color[1] * tr.identityLight;
|
|
out->color[2] = shader->fogParms.color[2] * tr.identityLight;
|
|
out->color[3] = 1;
|
|
|
|
d = shader->fogParms.depthForOpaque < 1 ? 1 : shader->fogParms.depthForOpaque;
|
|
out->tcScale = 1.0f / (d * 8);
|
|
|
|
// ydnar: global fog sets clearcolor/zfar
|
|
if(out->originalBrushNumber == -1)
|
|
{
|
|
s_worldData.globalFog = i + 1;
|
|
VectorCopy(shader->fogParms.color, s_worldData.globalOriginalFog);
|
|
s_worldData.globalOriginalFog[3] = shader->fogParms.depthForOpaque;
|
|
}
|
|
|
|
// set the gradient vector
|
|
sideNum = LittleLong(fogs->visibleSide);
|
|
|
|
// ydnar: made this check a little more strenuous (was sideNum == -1)
|
|
if(sideNum < 0 || sideNum >= sidesCount)
|
|
{
|
|
out->hasSurface = qfalse;
|
|
}
|
|
else
|
|
{
|
|
out->hasSurface = qtrue;
|
|
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++;
|
|
}
|
|
|
|
|
|
ri.Printf(PRINT_ALL, "%i fog volumes loaded\n", s_worldData.numFogs);
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
R_LoadLightGrid
|
|
================
|
|
*/
|
|
void R_LoadLightGrid(lump_t * l)
|
|
{
|
|
// int i, j, k;
|
|
// vec3_t maxs;
|
|
// world_t *w;
|
|
// float *wMins, *wMaxs;
|
|
// dgridPoint_t *in;
|
|
// bspGridPoint_t *gridPoint;
|
|
// float lat, lng;
|
|
// int gridStep[3];
|
|
// int pos[3];
|
|
// float posFloat[3];
|
|
//
|
|
// ri.Printf(PRINT_ALL, "...loading light grid\n");
|
|
//
|
|
// 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->models[0].bounds[0];
|
|
// wMaxs = w->models[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;
|
|
// }
|
|
//
|
|
// w->numLightGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2];
|
|
//
|
|
// ri.Printf(PRINT_ALL, "grid size (%i %i %i)\n", (int)w->lightGridSize[0], (int)w->lightGridSize[1],
|
|
// (int)w->lightGridSize[2]);
|
|
// ri.Printf(PRINT_ALL, "grid bounds (%i %i %i)\n", (int)w->lightGridBounds[0], (int)w->lightGridBounds[1],
|
|
// (int)w->lightGridBounds[2]);
|
|
//
|
|
// if(l->filelen != w->numLightGridPoints * sizeof(dgridPoint_t))
|
|
// {
|
|
// ri.Printf(PRINT_WARNING, "WARNING: light grid mismatch\n");
|
|
// w->lightGridData = NULL;
|
|
// return;
|
|
// }
|
|
//
|
|
// in = (void *)(fileBase + l->fileofs);
|
|
// if(l->filelen % sizeof(*in))
|
|
// ri.Error(ERR_DROP, "LoadMap: funny lump size in %s", s_worldData.name);
|
|
// gridPoint = ri.Hunk_Alloc(w->numLightGridPoints * sizeof(*gridPoint), h_low);
|
|
//
|
|
// w->lightGridData = gridPoint;
|
|
// //Com_Memcpy(w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen);
|
|
//
|
|
// for(i = 0; i < w->numLightGridPoints; i++, in++, gridPoint++)
|
|
// {
|
|
//#if defined(COMPAT_Q3A) || defined(COMPAT_ET)
|
|
// byte tmpAmbient[4];
|
|
// byte tmpDirected[4];
|
|
//
|
|
// tmpAmbient[0] = in->ambient[0];
|
|
// tmpAmbient[1] = in->ambient[1];
|
|
// tmpAmbient[2] = in->ambient[2];
|
|
// tmpAmbient[3] = 255;
|
|
//
|
|
// tmpDirected[0] = in->directed[0];
|
|
// tmpDirected[1] = in->directed[1];
|
|
// tmpDirected[2] = in->directed[2];
|
|
// tmpDirected[3] = 255;
|
|
//
|
|
// R_ColorShiftLightingBytes(tmpAmbient, tmpAmbient);
|
|
// R_ColorShiftLightingBytes(tmpDirected, tmpDirected);
|
|
//
|
|
// for(j = 0; j < 3; j++)
|
|
// {
|
|
// gridPoint->ambientColor[j] = tmpAmbient[j] * (1.0f / 255.0f);
|
|
// gridPoint->directedColor[j] = tmpDirected[j] * (1.0f / 255.0f);
|
|
// }
|
|
//#else
|
|
// for(j = 0; j < 3; j++)
|
|
// {
|
|
//
|
|
// gridPoint->ambientColor[j] = LittleFloat(in->ambient[j]);
|
|
// gridPoint->directedColor[j] = LittleFloat(in->directed[j]);
|
|
// }
|
|
//#endif
|
|
//
|
|
// gridPoint->ambientColor[3] = 1.0f;
|
|
// gridPoint->directedColor[3] = 1.0f;
|
|
//
|
|
// // standard spherical coordinates to cartesian coordinates conversion
|
|
//
|
|
// // decode X as cos( lat ) * sin( long )
|
|
// // decode Y as sin( lat ) * sin( long )
|
|
// // decode Z as cos( long )
|
|
//
|
|
// // RB: having a look in NormalToLatLong used by q3map2 shows the order of latLong
|
|
//
|
|
// // Lat = 0 at (1,0,0) to 360 (-1,0,0), encoded in 8-bit sine table format
|
|
// // Lng = 0 at (0,0,1) to 180 (0,0,-1), encoded in 8-bit sine table format
|
|
//
|
|
// lat = DEG2RAD(in->latLong[1] * (360.0f / 255.0f));
|
|
// lng = DEG2RAD(in->latLong[0] * (360.0f / 255.0f));
|
|
//
|
|
// gridPoint->direction[0] = cos(lat) * sin(lng);
|
|
// gridPoint->direction[1] = sin(lat) * sin(lng);
|
|
// gridPoint->direction[2] = cos(lng);
|
|
//
|
|
//#if 0
|
|
// // debug print to see if the XBSP format is correct
|
|
// ri.Printf(PRINT_ALL, "%9d Amb: (%03.1f %03.1f %03.1f) Dir: (%03.1f %03.1f %03.1f)\n",
|
|
// i, gridPoint->ambient[0], gridPoint->ambient[1], gridPoint->ambient[2], gridPoint->directed[0], gridPoint->directed[1], gridPoint->directed[2]);
|
|
//#endif
|
|
//
|
|
//#if !defined(COMPAT_Q3A) && !defined(COMPAT_ET)
|
|
// // deal with overbright bits
|
|
// R_HDRTonemapLightingColors(gridPoint->ambientColor, gridPoint->ambientColor, qtrue);
|
|
// R_HDRTonemapLightingColors(gridPoint->directedColor, gridPoint->directedColor, qtrue);
|
|
//#endif
|
|
// }
|
|
//
|
|
// // calculate grid point positions
|
|
// gridStep[0] = 1;
|
|
// gridStep[1] = w->lightGridBounds[0];
|
|
// gridStep[2] = w->lightGridBounds[0] * w->lightGridBounds[1];
|
|
//
|
|
// for(i = 0; i < w->lightGridBounds[0]; i += 1)
|
|
// {
|
|
// for(j = 0; j < w->lightGridBounds[1]; j += 1)
|
|
// {
|
|
// for(k = 0; k < w->lightGridBounds[2]; k += 1)
|
|
// {
|
|
// pos[0] = i;
|
|
// pos[1] = j;
|
|
// pos[2] = k;
|
|
//
|
|
// posFloat[0] = i * w->lightGridSize[0];
|
|
// posFloat[1] = j * w->lightGridSize[1];
|
|
// posFloat[2] = k * w->lightGridSize[2];
|
|
//
|
|
// gridPoint = w->lightGridData + pos[0] * gridStep[0] + pos[1] * gridStep[1] + pos[2] * gridStep[2];
|
|
//
|
|
// VectorAdd(posFloat, w->lightGridOrigin, gridPoint->origin);
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// ri.Printf(PRINT_ALL, "%i light grid points created\n", w->numLightGridPoints);
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_LoadEntities
|
|
================
|
|
*/
|
|
void R_LoadEntities(lump_t * l)
|
|
{
|
|
int i;
|
|
char *p, *pOld, *token, *s;
|
|
char keyname[MAX_TOKEN_CHARS];
|
|
char value[MAX_TOKEN_CHARS];
|
|
world_t *w;
|
|
qboolean isLight = qfalse;
|
|
int numEntities = 0;
|
|
int numLights = 0;
|
|
int numOmniLights = 0;
|
|
int numProjLights = 0;
|
|
int numParallelLights = 0;
|
|
trRefLight_t *light;
|
|
|
|
ri.Printf(PRINT_ALL, "...loading entities\n");
|
|
|
|
w = &s_worldData;
|
|
w->lightGridSize[0] = 64;
|
|
w->lightGridSize[1] = 64;
|
|
w->lightGridSize[2] = 128;
|
|
|
|
// store for reference by the cgame
|
|
w->entityString = ri.Hunk_Alloc(l->filelen + 1, h_low);
|
|
//strcpy(w->entityString, (char *)(fileBase + l->fileofs));
|
|
Q_strncpyz(w->entityString, (char *)(fileBase + l->fileofs), l->filelen + 1);
|
|
w->entityParsePoint = w->entityString;
|
|
|
|
#if 1
|
|
p = w->entityString;
|
|
#else
|
|
p = (char *)(fileBase + l->fileofs);
|
|
#endif
|
|
|
|
// only parse the world spawn
|
|
while(1)
|
|
{
|
|
// parse key
|
|
token = COM_ParseExt(&p, qtrue);
|
|
|
|
if(!*token)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: unexpected end of entities string while parsing worldspawn\n", token);
|
|
break;
|
|
}
|
|
|
|
if(*token == '{')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(*token == '}')
|
|
{
|
|
break;
|
|
}
|
|
|
|
Q_strncpyz(keyname, token, sizeof(keyname));
|
|
|
|
// parse value
|
|
token = COM_ParseExt(&p, qfalse);
|
|
|
|
if(!*token)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Q_strncpyz(value, token, sizeof(value));
|
|
|
|
// check for remapping of shaders for vertex lighting
|
|
s = "vertexremapshader";
|
|
if(!Q_strncmp(keyname, s, strlen(s)))
|
|
{
|
|
s = strchr(value, ';');
|
|
if(!s)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: no semi colon in vertexshaderremap '%s'\n", value);
|
|
break;
|
|
}
|
|
*s++ = 0;
|
|
continue;
|
|
}
|
|
|
|
// check for remapping of shaders
|
|
s = "remapshader";
|
|
if(!Q_strncmp(keyname, s, strlen(s)))
|
|
{
|
|
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 ambient color
|
|
else if(!Q_stricmp(keyname, "_color") || !Q_stricmp(keyname, "ambientColor"))
|
|
{
|
|
if(r_forceAmbient->value <= 0)
|
|
{
|
|
sscanf(value, "%f %f %f", &tr.worldEntity.ambientLight[0], &tr.worldEntity.ambientLight[1],
|
|
&tr.worldEntity.ambientLight[2]);
|
|
|
|
VectorCopy(tr.worldEntity.ambientLight, tr.worldEntity.ambientLight);
|
|
VectorScale(tr.worldEntity.ambientLight, r_ambientScale->value, tr.worldEntity.ambientLight);
|
|
}
|
|
}
|
|
|
|
// check for fog color
|
|
else if(!Q_stricmp(keyname, "fogColor"))
|
|
{
|
|
sscanf(value, "%f %f %f", &tr.fogColor[0], &tr.fogColor[1], &tr.fogColor[2]);
|
|
}
|
|
|
|
// check for fog density
|
|
else if(!Q_stricmp(keyname, "fogDensity"))
|
|
{
|
|
tr.fogDensity = atof(value);
|
|
}
|
|
|
|
// check for deluxe mapping support
|
|
if(!Q_stricmp(keyname, "deluxeMapping") && !Q_stricmp(value, "1"))
|
|
{
|
|
ri.Printf(PRINT_ALL, "map features directional light mapping\n");
|
|
tr.worldDeluxeMapping = qtrue;
|
|
continue;
|
|
}
|
|
|
|
// check for mapOverBrightBits override
|
|
else if(!Q_stricmp(keyname, "mapOverBrightBits"))
|
|
{
|
|
tr.mapOverBrightBits = Q_bound(0, atof(value), 3);
|
|
}
|
|
|
|
// check for deluxe mapping provided by NetRadiant's q3map2
|
|
if(!Q_stricmp(keyname, "_q3map2_cmdline"))
|
|
{
|
|
s = strstr(value, "-deluxe");
|
|
if(s)
|
|
{
|
|
ri.Printf(PRINT_ALL, "map features directional light mapping\n");
|
|
tr.worldDeluxeMapping = qtrue;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
|
|
// check for HDR light mapping support
|
|
if(!Q_stricmp(keyname, "hdrRGBE") && !Q_stricmp(value, "1"))
|
|
{
|
|
ri.Printf(PRINT_ALL, "map features HDR light mapping\n");
|
|
tr.worldHDR_RGBE = qtrue;
|
|
continue;
|
|
}
|
|
|
|
if(!Q_stricmp(keyname, "classname") && Q_stricmp(value, "worldspawn"))
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: expected worldspawn found '%s'\n", value);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// ri.Printf(PRINT_ALL, "-----------\n%s\n----------\n", p);
|
|
|
|
pOld = p;
|
|
numEntities = 1; // parsed worldspawn so far
|
|
|
|
// count lights
|
|
while(1)
|
|
{
|
|
// parse {
|
|
token = COM_ParseExt(&p, qtrue);
|
|
|
|
if(!*token)
|
|
{
|
|
// end of entities string
|
|
break;
|
|
}
|
|
|
|
if(*token != '{')
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: expected { found '%s'\n", token);
|
|
break;
|
|
}
|
|
|
|
// new entity
|
|
isLight = qfalse;
|
|
|
|
// parse epairs
|
|
while(1)
|
|
{
|
|
// parse key
|
|
token = COM_ParseExt(&p, qtrue);
|
|
|
|
if(*token == '}')
|
|
{
|
|
break;
|
|
}
|
|
|
|
if(!*token)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: EOF without closing bracket\n");
|
|
break;
|
|
}
|
|
|
|
Q_strncpyz(keyname, token, sizeof(keyname));
|
|
|
|
// parse value
|
|
token = COM_ParseExt(&p, qfalse);
|
|
|
|
if(!*token)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing value for key '%s'\n", keyname);
|
|
continue;
|
|
}
|
|
|
|
Q_strncpyz(value, token, sizeof(value));
|
|
|
|
// check if this entity is a light
|
|
if(!Q_stricmp(keyname, "classname") && !Q_stricmp(value, "light"))
|
|
{
|
|
isLight = qtrue;
|
|
}
|
|
}
|
|
|
|
if(*token != '}')
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: expected } found '%s'\n", token);
|
|
break;
|
|
}
|
|
|
|
if(isLight)
|
|
{
|
|
numLights++;
|
|
}
|
|
|
|
numEntities++;
|
|
}
|
|
|
|
ri.Printf(PRINT_ALL, "%i total entities counted\n", numEntities);
|
|
ri.Printf(PRINT_ALL, "%i total lights counted\n", numLights);
|
|
|
|
s_worldData.numLights = numLights;
|
|
|
|
// Tr3B: FIXME add 1 dummy light so we don't trash the hunk memory system ...
|
|
s_worldData.lights = ri.Hunk_Alloc((s_worldData.numLights + 1) * sizeof(trRefLight_t), h_low);
|
|
|
|
// basic light setup
|
|
for(i = 0, light = s_worldData.lights; i < s_worldData.numLights; i++, light++)
|
|
{
|
|
QuatClear(light->l.rotation);
|
|
VectorClear(light->l.center);
|
|
|
|
light->l.color[0] = 1;
|
|
light->l.color[1] = 1;
|
|
light->l.color[2] = 1;
|
|
|
|
light->l.scale = r_lightScale->value;
|
|
|
|
light->l.radius[0] = 300;
|
|
light->l.radius[1] = 300;
|
|
light->l.radius[2] = 300;
|
|
|
|
VectorClear(light->l.projTarget);
|
|
VectorClear(light->l.projRight);
|
|
VectorClear(light->l.projUp);
|
|
VectorClear(light->l.projStart);
|
|
VectorClear(light->l.projEnd);
|
|
|
|
light->l.inverseShadows = qfalse;
|
|
|
|
light->isStatic = qtrue;
|
|
light->noRadiosity = qfalse;
|
|
light->additive = qtrue;
|
|
|
|
light->shadowLOD = 0;
|
|
}
|
|
|
|
// parse lights
|
|
p = pOld;
|
|
numEntities = 1;
|
|
light = &s_worldData.lights[0];
|
|
|
|
while(1)
|
|
{
|
|
// parse {
|
|
token = COM_ParseExt(&p, qtrue);
|
|
|
|
if(!*token)
|
|
{
|
|
// end of entities string
|
|
break;
|
|
}
|
|
|
|
if(*token != '{')
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: expected { found '%s'\n", token);
|
|
break;
|
|
}
|
|
|
|
// new entity
|
|
isLight = qfalse;
|
|
|
|
// parse epairs
|
|
while(1)
|
|
{
|
|
// parse key
|
|
token = COM_ParseExt(&p, qtrue);
|
|
|
|
if(*token == '}')
|
|
{
|
|
break;
|
|
}
|
|
|
|
if(!*token)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: EOF without closing bracket\n");
|
|
break;
|
|
}
|
|
|
|
Q_strncpyz(keyname, token, sizeof(keyname));
|
|
|
|
// parse value
|
|
token = COM_ParseExt(&p, qfalse);
|
|
|
|
if(!*token)
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing value for key '%s'\n", keyname);
|
|
continue;
|
|
}
|
|
|
|
Q_strncpyz(value, token, sizeof(value));
|
|
|
|
// check if this entity is a light
|
|
if(!Q_stricmp(keyname, "classname") && !Q_stricmp(value, "light"))
|
|
{
|
|
isLight = qtrue;
|
|
}
|
|
// check for origin
|
|
else if(!Q_stricmp(keyname, "origin") || !Q_stricmp(keyname, "light_origin"))
|
|
{
|
|
//sscanf(value, "%f %f %f", &light->l.origin[0], &light->l.origin[1], &light->l.origin[2]);
|
|
s = &value[0];
|
|
Com_Parse1DMatrix(&s, 3, light->l.origin, qfalse);
|
|
}
|
|
// check for center
|
|
else if(!Q_stricmp(keyname, "light_center"))
|
|
{
|
|
//sscanf(value, "%f %f %f", &light->l.center[0], &light->l.center[1], &light->l.center[2]);
|
|
s = &value[0];
|
|
Com_Parse1DMatrix(&s, 3, light->l.center, qfalse);
|
|
}
|
|
// check for color
|
|
else if(!Q_stricmp(keyname, "_color"))
|
|
{
|
|
//sscanf(value, "%f %f %f", &light->l.color[0], &light->l.color[1], &light->l.color[2]);
|
|
s = &value[0];
|
|
Com_Parse1DMatrix(&s, 3, light->l.color, qfalse);
|
|
}
|
|
// check for radius
|
|
else if(!Q_stricmp(keyname, "light_radius"))
|
|
{
|
|
//sscanf(value, "%f %f %f", &light->l.radius[0], &light->l.radius[1], &light->l.radius[2]);
|
|
s = &value[0];
|
|
Com_Parse1DMatrix(&s, 3, light->l.radius, qfalse);
|
|
}
|
|
// check for light_target
|
|
else if(!Q_stricmp(keyname, "light_target"))
|
|
{
|
|
//sscanf(value, "%f %f %f", &light->l.projTarget[0], &light->l.projTarget[1], &light->l.projTarget[2]);
|
|
s = &value[0];
|
|
Com_Parse1DMatrix(&s, 3, light->l.projTarget, qfalse);
|
|
light->l.rlType = RL_PROJ;
|
|
}
|
|
// check for light_right
|
|
else if(!Q_stricmp(keyname, "light_right"))
|
|
{
|
|
//sscanf(value, "%f %f %f", &light->l.projRight[0], &light->l.projRight[1], &light->l.projRight[2]);
|
|
s = &value[0];
|
|
Com_Parse1DMatrix(&s, 3, light->l.projRight, qfalse);
|
|
light->l.rlType = RL_PROJ;
|
|
}
|
|
// check for light_up
|
|
else if(!Q_stricmp(keyname, "light_up"))
|
|
{
|
|
//sscanf(value, "%f %f %f", &light->l.projUp[0], &light->l.projUp[1], &light->l.projUp[2]);
|
|
s = &value[0];
|
|
Com_Parse1DMatrix(&s, 3, light->l.projUp, qfalse);
|
|
light->l.rlType = RL_PROJ;
|
|
}
|
|
// check for light_start
|
|
else if(!Q_stricmp(keyname, "light_start"))
|
|
{
|
|
//sscanf(value, "%f %f %f", &light->l.projStart[0], &light->l.projStart[1], &light->l.projStart[2]);
|
|
s = &value[0];
|
|
Com_Parse1DMatrix(&s, 3, light->l.projStart, qfalse);
|
|
light->l.rlType = RL_PROJ;
|
|
}
|
|
// check for light_end
|
|
else if(!Q_stricmp(keyname, "light_end"))
|
|
{
|
|
//sscanf(value, "%f %f %f", &light->l.projEnd[0], &light->l.projEnd[1], &light->l.projEnd[2]);
|
|
s = &value[0];
|
|
Com_Parse1DMatrix(&s, 3, light->l.projEnd, qfalse);
|
|
light->l.rlType = RL_PROJ;
|
|
}
|
|
// check for radius
|
|
else if(!Q_stricmp(keyname, "light") || !Q_stricmp(keyname, "_light"))
|
|
{
|
|
vec_t value2;
|
|
|
|
value2 = atof(value);
|
|
light->l.radius[0] = value2;
|
|
light->l.radius[1] = value2;
|
|
light->l.radius[2] = value2;
|
|
}
|
|
// check for scale
|
|
else if(!Q_stricmp(keyname, "light_scale"))
|
|
{
|
|
light->l.scale = atof(value);
|
|
|
|
if(!r_hdrRendering->integer || !glConfig2.textureFloatAvailable || !glConfig2.framebufferObjectAvailable || !glConfig2.framebufferBlitAvailable)
|
|
{
|
|
if(light->l.scale >= r_lightScale->value)
|
|
{
|
|
light->l.scale = r_lightScale->value;
|
|
}
|
|
}
|
|
}
|
|
// check for light shader
|
|
else if(!Q_stricmp(keyname, "texture"))
|
|
{
|
|
light->l.attenuationShader = RE_RegisterShaderLightAttenuation(value);
|
|
}
|
|
// check for rotation
|
|
else if(!Q_stricmp(keyname, "rotation") || !Q_stricmp(keyname, "light_rotation"))
|
|
{
|
|
matrix_t rotation;
|
|
|
|
sscanf(value, "%f %f %f %f %f %f %f %f %f", &rotation[0], &rotation[1], &rotation[2],
|
|
&rotation[4], &rotation[5], &rotation[6], &rotation[8], &rotation[9], &rotation[10]);
|
|
|
|
QuatFromMatrix(light->l.rotation, rotation);
|
|
}
|
|
// check if this light does not cast any shadows
|
|
else if(!Q_stricmp(keyname, "noshadows") && !Q_stricmp(value, "1"))
|
|
{
|
|
light->l.noShadows = qtrue;
|
|
}
|
|
// check if this light does not contribute to the global lightmapping
|
|
else if(!Q_stricmp(keyname, "noradiosity") && !Q_stricmp(value, "1"))
|
|
{
|
|
light->noRadiosity = qtrue;
|
|
}
|
|
// check if this light is a parallel sun light
|
|
else if(!Q_stricmp(keyname, "parallel") && !Q_stricmp(value, "1"))
|
|
{
|
|
light->l.rlType = RL_DIRECTIONAL;
|
|
}
|
|
}
|
|
|
|
if(*token != '}')
|
|
{
|
|
ri.Printf(PRINT_WARNING, "WARNING: expected } found '%s'\n", token);
|
|
break;
|
|
}
|
|
|
|
if(!isLight)
|
|
{
|
|
// reset rotation because it may be set to the rotation of other entities
|
|
QuatClear(light->l.rotation);
|
|
}
|
|
else
|
|
{
|
|
if((numOmniLights + numProjLights + numParallelLights) < s_worldData.numLights);
|
|
{
|
|
switch (light->l.rlType)
|
|
{
|
|
case RL_OMNI:
|
|
numOmniLights++;
|
|
break;
|
|
|
|
case RL_PROJ:
|
|
numProjLights++;
|
|
break;
|
|
|
|
case RL_DIRECTIONAL:
|
|
numParallelLights++;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
light++;
|
|
}
|
|
}
|
|
|
|
numEntities++;
|
|
}
|
|
|
|
if((numOmniLights + numProjLights + numParallelLights) != s_worldData.numLights)
|
|
{
|
|
ri.Error(ERR_DROP, "counted %i lights and parsed %i lights", s_worldData.numLights, (numOmniLights + numProjLights + numParallelLights));
|
|
}
|
|
|
|
ri.Printf(PRINT_ALL, "%i total entities parsed\n", numEntities);
|
|
ri.Printf(PRINT_ALL, "%i total lights parsed\n", numOmniLights + numProjLights);
|
|
ri.Printf(PRINT_ALL, "%i omni-directional lights parsed\n", numOmniLights);
|
|
ri.Printf(PRINT_ALL, "%i projective lights parsed\n", numProjLights);
|
|
ri.Printf(PRINT_ALL, "%i directional lights parsed\n", numParallelLights);
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
R_GetEntityToken
|
|
=================
|
|
*/
|
|
qboolean 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 qfalse;
|
|
}
|
|
else
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
R_PrecacheInteraction
|
|
=================
|
|
*/
|
|
static void R_PrecacheInteraction(trRefLight_t * light, bspSurface_t * surface)
|
|
{
|
|
interactionCache_t *iaCache;
|
|
|
|
iaCache = ri.Hunk_Alloc(sizeof(*iaCache), h_low);
|
|
Com_AddToGrowList(&s_interactions, iaCache);
|
|
|
|
// connect to interaction grid
|
|
if(!light->firstInteractionCache)
|
|
{
|
|
light->firstInteractionCache = iaCache;
|
|
}
|
|
|
|
if(light->lastInteractionCache)
|
|
{
|
|
light->lastInteractionCache->next = iaCache;
|
|
}
|
|
|
|
light->lastInteractionCache = iaCache;
|
|
|
|
iaCache->next = NULL;
|
|
iaCache->surface = surface;
|
|
|
|
iaCache->redundant = qfalse;
|
|
}
|
|
|
|
/*
|
|
static int R_BuildShadowVolume(int numTriangles, const srfTriangle_t * triangles, int numVerts, int indexes[SHADER_MAX_INDEXES])
|
|
{
|
|
int i;
|
|
int numIndexes;
|
|
const srfTriangle_t *tri;
|
|
|
|
// calculate zfail shadow volume
|
|
numIndexes = 0;
|
|
|
|
// set up indices for silhouette edges
|
|
for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
if(!sh.facing[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(tri->neighbors[0] < 0 || !sh.facing[tri->neighbors[0]])
|
|
{
|
|
indexes[numIndexes + 0] = tri->indexes[1];
|
|
indexes[numIndexes + 1] = tri->indexes[0];
|
|
indexes[numIndexes + 2] = tri->indexes[0] + numVerts;
|
|
|
|
indexes[numIndexes + 3] = tri->indexes[1];
|
|
indexes[numIndexes + 4] = tri->indexes[0] + numVerts;
|
|
indexes[numIndexes + 5] = tri->indexes[1] + numVerts;
|
|
|
|
numIndexes += 6;
|
|
}
|
|
|
|
if(tri->neighbors[1] < 0 || !sh.facing[tri->neighbors[1]])
|
|
{
|
|
indexes[numIndexes + 0] = tri->indexes[2];
|
|
indexes[numIndexes + 1] = tri->indexes[1];
|
|
indexes[numIndexes + 2] = tri->indexes[1] + numVerts;
|
|
|
|
indexes[numIndexes + 3] = tri->indexes[2];
|
|
indexes[numIndexes + 4] = tri->indexes[1] + numVerts;
|
|
indexes[numIndexes + 5] = tri->indexes[2] + numVerts;
|
|
|
|
numIndexes += 6;
|
|
}
|
|
|
|
if(tri->neighbors[2] < 0 || !sh.facing[tri->neighbors[2]])
|
|
{
|
|
indexes[numIndexes + 0] = tri->indexes[0];
|
|
indexes[numIndexes + 1] = tri->indexes[2];
|
|
indexes[numIndexes + 2] = tri->indexes[2] + numVerts;
|
|
|
|
indexes[numIndexes + 3] = tri->indexes[0];
|
|
indexes[numIndexes + 4] = tri->indexes[2] + numVerts;
|
|
indexes[numIndexes + 5] = tri->indexes[0] + numVerts;
|
|
|
|
numIndexes += 6;
|
|
}
|
|
}
|
|
|
|
// set up indices for light and dark caps
|
|
for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
if(!sh.facing[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// light cap
|
|
indexes[numIndexes + 0] = tri->indexes[0];
|
|
indexes[numIndexes + 1] = tri->indexes[1];
|
|
indexes[numIndexes + 2] = tri->indexes[2];
|
|
|
|
// dark cap
|
|
indexes[numIndexes + 3] = tri->indexes[2] + numVerts;
|
|
indexes[numIndexes + 4] = tri->indexes[1] + numVerts;
|
|
indexes[numIndexes + 5] = tri->indexes[0] + numVerts;
|
|
|
|
numIndexes += 6;
|
|
}
|
|
|
|
return numIndexes;
|
|
}
|
|
*/
|
|
|
|
/*
|
|
static int R_BuildShadowPlanes(int numTriangles, const srfTriangle_t * triangles, int numVerts, srfVert_t * verts,
|
|
cplane_t shadowPlanes[SHADER_MAX_TRIANGLES], trRefLight_t * light)
|
|
{
|
|
int i;
|
|
int numShadowPlanes;
|
|
const srfTriangle_t *tri;
|
|
vec3_t pos[3];
|
|
|
|
// vec3_t lightDir;
|
|
vec4_t plane;
|
|
|
|
if(r_noShadowFrustums->integer)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// calculate shadow frustum
|
|
numShadowPlanes = 0;
|
|
|
|
// set up indices for silhouette edges
|
|
for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
if(!sh.facing[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(tri->neighbors[0] < 0 || !sh.facing[tri->neighbors[0]])
|
|
{
|
|
//indexes[numIndexes + 0] = tri->indexes[1];
|
|
//indexes[numIndexes + 1] = tri->indexes[0];
|
|
//indexes[numIndexes + 2] = tri->indexes[0] + numVerts;
|
|
|
|
VectorCopy(verts[tri->indexes[1]].xyz, pos[0]);
|
|
VectorCopy(verts[tri->indexes[0]].xyz, pos[1]);
|
|
VectorCopy(light->origin, pos[2]);
|
|
|
|
// extrude the infinite one
|
|
//VectorSubtract(verts[tri->indexes[0]].xyz, light->origin, lightDir);
|
|
//VectorAdd(verts[tri->indexes[0]].xyz, lightDir, pos[2]);
|
|
//VectorNormalize(lightDir);
|
|
//VectorMA(verts[tri->indexes[0]].xyz, 9999, lightDir, pos[2]);
|
|
|
|
if(PlaneFromPoints(plane, pos[0], pos[1], pos[2], qtrue))
|
|
{
|
|
shadowPlanes[numShadowPlanes].normal[0] = plane[0];
|
|
shadowPlanes[numShadowPlanes].normal[1] = plane[1];
|
|
shadowPlanes[numShadowPlanes].normal[2] = plane[2];
|
|
shadowPlanes[numShadowPlanes].dist = plane[3];
|
|
|
|
numShadowPlanes++;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if(tri->neighbors[1] < 0 || !sh.facing[tri->neighbors[1]])
|
|
{
|
|
//indexes[numIndexes + 0] = tri->indexes[2];
|
|
//indexes[numIndexes + 1] = tri->indexes[1];
|
|
//indexes[numIndexes + 2] = tri->indexes[1] + numVerts;
|
|
|
|
VectorCopy(verts[tri->indexes[2]].xyz, pos[0]);
|
|
VectorCopy(verts[tri->indexes[1]].xyz, pos[1]);
|
|
VectorCopy(light->origin, pos[2]);
|
|
|
|
// extrude the infinite one
|
|
//VectorSubtract(verts[tri->indexes[1]].xyz, light->origin, lightDir);
|
|
//VectorNormalize(lightDir);
|
|
//VectorMA(verts[tri->indexes[1]].xyz, 9999, lightDir, pos[2]);
|
|
|
|
if(PlaneFromPoints(plane, pos[0], pos[1], pos[2], qtrue))
|
|
{
|
|
shadowPlanes[numShadowPlanes].normal[0] = plane[0];
|
|
shadowPlanes[numShadowPlanes].normal[1] = plane[1];
|
|
shadowPlanes[numShadowPlanes].normal[2] = plane[2];
|
|
shadowPlanes[numShadowPlanes].dist = plane[3];
|
|
|
|
numShadowPlanes++;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if(tri->neighbors[2] < 0 || !sh.facing[tri->neighbors[2]])
|
|
{
|
|
//indexes[numIndexes + 0] = tri->indexes[0];
|
|
//indexes[numIndexes + 1] = tri->indexes[2];
|
|
//indexes[numIndexes + 2] = tri->indexes[2] + numVerts;
|
|
|
|
VectorCopy(verts[tri->indexes[0]].xyz, pos[0]);
|
|
VectorCopy(verts[tri->indexes[2]].xyz, pos[1]);
|
|
VectorCopy(light->origin, pos[2]);
|
|
|
|
// extrude the infinite one
|
|
//VectorSubtract(verts[tri->indexes[2]].xyz, light->origin, lightDir);
|
|
//VectorNormalize(lightDir);
|
|
//VectorMA(verts[tri->indexes[2]].xyz, 9999, lightDir, pos[2]);
|
|
|
|
if(PlaneFromPoints(plane, pos[0], pos[1], pos[2], qtrue))
|
|
{
|
|
shadowPlanes[numShadowPlanes].normal[0] = plane[0];
|
|
shadowPlanes[numShadowPlanes].normal[1] = plane[1];
|
|
shadowPlanes[numShadowPlanes].normal[2] = plane[2];
|
|
shadowPlanes[numShadowPlanes].dist = plane[3];
|
|
|
|
numShadowPlanes++;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set up indices for light and dark caps
|
|
for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
if(!sh.facing[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// light cap
|
|
//indexes[numIndexes + 0] = tri->indexes[0];
|
|
//indexes[numIndexes + 1] = tri->indexes[1];
|
|
//indexes[numIndexes + 2] = tri->indexes[2];
|
|
|
|
VectorCopy(verts[tri->indexes[0]].xyz, pos[0]);
|
|
VectorCopy(verts[tri->indexes[1]].xyz, pos[1]);
|
|
VectorCopy(verts[tri->indexes[2]].xyz, pos[2]);
|
|
|
|
if(PlaneFromPoints(plane, pos[0], pos[1], pos[2], qfalse))
|
|
{
|
|
shadowPlanes[numShadowPlanes].normal[0] = plane[0];
|
|
shadowPlanes[numShadowPlanes].normal[1] = plane[1];
|
|
shadowPlanes[numShadowPlanes].normal[2] = plane[2];
|
|
shadowPlanes[numShadowPlanes].dist = plane[3];
|
|
|
|
numShadowPlanes++;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
for(i = 0; i < numShadowPlanes; i++)
|
|
{
|
|
//vec_t length, ilength;
|
|
|
|
shadowPlanes[i].type = PLANE_NON_AXIAL;
|
|
|
|
|
|
SetPlaneSignbits(&shadowPlanes[i]);
|
|
}
|
|
|
|
return numShadowPlanes;
|
|
}
|
|
*/
|
|
|
|
static qboolean R_PrecacheFaceInteraction(srfSurfaceFace_t * cv, shader_t * shader, trRefLight_t * light)
|
|
{
|
|
// check if bounds intersect
|
|
if(!BoundsIntersect(light->worldBounds[0], light->worldBounds[1], cv->bounds[0], cv->bounds[1]))
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static int R_PrecacheGridInteraction(srfGridMesh_t * cv, shader_t * shader, trRefLight_t * light)
|
|
{
|
|
// check if bounds intersect
|
|
if(!BoundsIntersect(light->worldBounds[0], light->worldBounds[1], cv->bounds[0], cv->bounds[1]))
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static int R_PrecacheTrisurfInteraction(srfTriangles_t * cv, shader_t * shader, trRefLight_t * light)
|
|
{
|
|
// check if bounds intersect
|
|
if(!BoundsIntersect(light->worldBounds[0], light->worldBounds[1], cv->bounds[0], cv->bounds[1]))
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
/*
|
|
======================
|
|
R_PrecacheInteractionSurface
|
|
======================
|
|
*/
|
|
static void R_PrecacheInteractionSurface(bspSurface_t * surf, trRefLight_t * light)
|
|
{
|
|
qboolean intersects;
|
|
|
|
if(surf->lightCount == s_lightCount)
|
|
{
|
|
return; // already checked this surface
|
|
}
|
|
surf->lightCount = s_lightCount;
|
|
|
|
// skip all surfaces that don't matter for lighting only pass
|
|
if(surf->shader->isSky || (!surf->shader->interactLight && surf->shader->noShadows))
|
|
return;
|
|
|
|
if(*surf->data == SF_FACE)
|
|
{
|
|
intersects = R_PrecacheFaceInteraction((srfSurfaceFace_t *) surf->data, surf->shader, light);
|
|
}
|
|
else if(*surf->data == SF_GRID)
|
|
{
|
|
intersects = R_PrecacheGridInteraction((srfGridMesh_t *) surf->data, surf->shader, light);
|
|
}
|
|
else if(*surf->data == SF_TRIANGLES)
|
|
{
|
|
intersects = R_PrecacheTrisurfInteraction((srfTriangles_t *) surf->data, surf->shader, light);
|
|
}
|
|
else
|
|
{
|
|
intersects = qfalse;
|
|
}
|
|
|
|
if(intersects)
|
|
{
|
|
R_PrecacheInteraction(light, surf);
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_RecursivePrecacheInteractionNode
|
|
================
|
|
*/
|
|
static void R_RecursivePrecacheInteractionNode(bspNode_t * node, trRefLight_t * light)
|
|
{
|
|
int r;
|
|
|
|
// light already hit node
|
|
if(node->lightCount == s_lightCount)
|
|
{
|
|
return;
|
|
}
|
|
node->lightCount = s_lightCount;
|
|
|
|
if(node->contents != -1)
|
|
{
|
|
// leaf node, so add mark surfaces
|
|
int c;
|
|
bspSurface_t *surf, **mark;
|
|
|
|
// add the individual surfaces
|
|
mark = node->markSurfaces;
|
|
c = node->numMarkSurfaces;
|
|
while(c--)
|
|
{
|
|
// the surface may have already been added if it
|
|
// spans multiple leafs
|
|
surf = *mark;
|
|
R_PrecacheInteractionSurface(surf, light);
|
|
mark++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// node is just a decision point, so go down both sides
|
|
// since we don't care about sort orders, just go positive to negative
|
|
r = BoxOnPlaneSide(light->worldBounds[0], light->worldBounds[1], node->plane);
|
|
|
|
switch (r)
|
|
{
|
|
case 1:
|
|
R_RecursivePrecacheInteractionNode(node->children[0], light);
|
|
break;
|
|
|
|
case 2:
|
|
R_RecursivePrecacheInteractionNode(node->children[1], light);
|
|
break;
|
|
|
|
case 3:
|
|
default:
|
|
// recurse down the children, front side first
|
|
R_RecursivePrecacheInteractionNode(node->children[0], light);
|
|
R_RecursivePrecacheInteractionNode(node->children[1], light);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_RecursiveAddInteractionNode
|
|
================
|
|
*/
|
|
static void R_RecursiveAddInteractionNode(bspNode_t * node, trRefLight_t * light)
|
|
{
|
|
int r;
|
|
|
|
// light already hit node
|
|
if(node->lightCount == s_lightCount)
|
|
{
|
|
return;
|
|
}
|
|
node->lightCount = s_lightCount;
|
|
|
|
if(node->contents != -1)
|
|
{
|
|
vec3_t worldBounds[2];
|
|
|
|
VectorCopy(node->mins, worldBounds[0]);
|
|
VectorCopy(node->maxs, worldBounds[1]);
|
|
|
|
if(R_CullLightWorldBounds(light, worldBounds) != CULL_OUT)
|
|
{
|
|
link_t *l;
|
|
|
|
l = ri.Hunk_Alloc(sizeof(*l), h_low);
|
|
InitLink(l, node);
|
|
|
|
InsertLink(l, &light->leafs);
|
|
|
|
light->leafs.numElements++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// node is just a decision point, so go down both sides
|
|
// since we don't care about sort orders, just go positive to negative
|
|
r = BoxOnPlaneSide(light->worldBounds[0], light->worldBounds[1], node->plane);
|
|
|
|
switch (r)
|
|
{
|
|
case 1:
|
|
R_RecursiveAddInteractionNode(node->children[0], light);
|
|
break;
|
|
|
|
case 2:
|
|
R_RecursiveAddInteractionNode(node->children[1], light);
|
|
break;
|
|
|
|
case 3:
|
|
default:
|
|
// recurse down the children, front side first
|
|
R_RecursiveAddInteractionNode(node->children[0], light);
|
|
R_RecursiveAddInteractionNode(node->children[1], light);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_ShadowFrustumCullWorldBounds
|
|
|
|
Returns CULL_IN, CULL_CLIP, or CULL_OUT
|
|
=================
|
|
*/
|
|
int R_ShadowFrustumCullWorldBounds(int numShadowPlanes, cplane_t * shadowPlanes, vec3_t worldBounds[2])
|
|
{
|
|
int i;
|
|
cplane_t *plane;
|
|
qboolean anyClip;
|
|
int r;
|
|
|
|
if(!numShadowPlanes)
|
|
return CULL_CLIP;
|
|
|
|
// check against frustum planes
|
|
anyClip = qfalse;
|
|
for(i = 0; i < numShadowPlanes; i++)
|
|
{
|
|
plane = &shadowPlanes[i];
|
|
|
|
r = BoxOnPlaneSide(worldBounds[0], worldBounds[1], plane);
|
|
|
|
if(r == 2)
|
|
{
|
|
// completely outside frustum
|
|
return CULL_OUT;
|
|
}
|
|
if(r == 3)
|
|
{
|
|
anyClip = qtrue;
|
|
}
|
|
}
|
|
|
|
if(!anyClip)
|
|
{
|
|
// completely inside frustum
|
|
return CULL_IN;
|
|
}
|
|
|
|
// partially clipped
|
|
return CULL_CLIP;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
R_KillRedundantInteractions
|
|
=============
|
|
*/
|
|
/*
|
|
static void R_KillRedundantInteractions(trRefLight_t * light)
|
|
{
|
|
interactionCache_t *iaCache, *iaCache2;
|
|
bspSurface_t *surface;
|
|
vec3_t localBounds[2];
|
|
|
|
if(r_shadows->integer <= SHADOWING_BLOB)
|
|
return;
|
|
|
|
if(!light->firstInteractionCache)
|
|
{
|
|
// this light has no interactions precached
|
|
return;
|
|
}
|
|
|
|
if(light->l.noShadows)
|
|
{
|
|
// actually noShadows lights are quite bad concerning this optimization
|
|
return;
|
|
}
|
|
|
|
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
|
|
{
|
|
surface = iaCache->surface;
|
|
|
|
if(surface->shader->sort > SS_OPAQUE)
|
|
continue;
|
|
|
|
if(surface->shader->noShadows)
|
|
continue;
|
|
|
|
// HACK: allow fancy alphatest shadows with shadow mapping
|
|
if(r_shadows->integer >= SHADOWING_ESM16 && surface->shader->alphaTest)
|
|
continue;
|
|
|
|
for(iaCache2 = light->firstInteractionCache; iaCache2; iaCache2 = iaCache2->next)
|
|
{
|
|
if(iaCache == iaCache2)
|
|
{
|
|
// don't check the surface of the current interaction with its shadow frustum
|
|
continue;
|
|
}
|
|
|
|
surface = iaCache2->surface;
|
|
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *face;
|
|
|
|
face = (srfSurfaceFace_t *) surface->data;
|
|
|
|
VectorCopy(face->bounds[0], localBounds[0]);
|
|
VectorCopy(face->bounds[1], localBounds[1]);
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *grid;
|
|
|
|
grid = (srfGridMesh_t *) surface->data;
|
|
|
|
VectorCopy(grid->meshBounds[0], localBounds[0]);
|
|
VectorCopy(grid->meshBounds[1], localBounds[1]);
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *tri;
|
|
|
|
tri = (srfTriangles_t *) surface->data;
|
|
|
|
VectorCopy(tri->bounds[0], localBounds[0]);
|
|
VectorCopy(tri->bounds[1], localBounds[1]);
|
|
}
|
|
else
|
|
{
|
|
iaCache2->redundant = qfalse;
|
|
continue;
|
|
}
|
|
|
|
if(R_ShadowFrustumCullWorldBounds(iaCache->numShadowPlanes, iaCache->shadowPlanes, localBounds) == CULL_IN)
|
|
{
|
|
iaCache2->redundant = qtrue;
|
|
c_redundantInteractions++;
|
|
}
|
|
}
|
|
|
|
if(iaCache->redundant)
|
|
{
|
|
c_redundantInteractions++;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
|
|
/*
|
|
=================
|
|
R_CreateInteractionVBO
|
|
=================
|
|
*/
|
|
static interactionVBO_t *R_CreateInteractionVBO(trRefLight_t * light)
|
|
{
|
|
interactionVBO_t *iaVBO;
|
|
|
|
iaVBO = ri.Hunk_Alloc(sizeof(*iaVBO), h_low);
|
|
|
|
// connect to interaction grid
|
|
if(!light->firstInteractionVBO)
|
|
{
|
|
light->firstInteractionVBO = iaVBO;
|
|
}
|
|
|
|
if(light->lastInteractionVBO)
|
|
{
|
|
light->lastInteractionVBO->next = iaVBO;
|
|
}
|
|
|
|
light->lastInteractionVBO = iaVBO;
|
|
iaVBO->next = NULL;
|
|
|
|
return iaVBO;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
InteractionCacheCompare
|
|
compare function for qsort()
|
|
=================
|
|
*/
|
|
static int InteractionCacheCompare(const void *a, const void *b)
|
|
{
|
|
interactionCache_t *aa, *bb;
|
|
|
|
aa = *(interactionCache_t **) a;
|
|
bb = *(interactionCache_t **) b;
|
|
|
|
// shader first
|
|
if(aa->surface->shader < bb->surface->shader)
|
|
return -1;
|
|
|
|
else if(aa->surface->shader > bb->surface->shader)
|
|
return 1;
|
|
|
|
// then alphaTest
|
|
if(aa->surface->shader->alphaTest < bb->surface->shader->alphaTest)
|
|
return -1;
|
|
|
|
else if(aa->surface->shader->alphaTest > bb->surface->shader->alphaTest)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int UpdateLightTriangles(const srfVert_t * verts, int numTriangles, srfTriangle_t * triangles, shader_t * surfaceShader,
|
|
trRefLight_t * light)
|
|
{
|
|
int i;
|
|
srfTriangle_t *tri;
|
|
int numFacing;
|
|
|
|
numFacing = 0;
|
|
for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
|
|
{
|
|
#if 1
|
|
vec3_t pos[3];
|
|
float d;
|
|
|
|
VectorCopy(verts[tri->indexes[0]].xyz, pos[0]);
|
|
VectorCopy(verts[tri->indexes[1]].xyz, pos[1]);
|
|
VectorCopy(verts[tri->indexes[2]].xyz, pos[2]);
|
|
|
|
if(PlaneFromPoints(tri->plane, pos[0], pos[1], pos[2], qtrue))
|
|
{
|
|
tri->degenerated = qfalse;
|
|
|
|
if(light->l.rlType == RL_DIRECTIONAL)
|
|
{
|
|
vec3_t lightDirection;
|
|
|
|
// light direction is from surface to light
|
|
#if 1
|
|
VectorCopy(tr.sunDirection, lightDirection);
|
|
#else
|
|
VectorCopy(light->direction, lightDirection);
|
|
#endif
|
|
|
|
d = DotProduct(tri->plane, lightDirection);
|
|
|
|
if(surfaceShader->cullType == CT_TWO_SIDED || (d > 0 && surfaceShader->cullType != CT_BACK_SIDED))
|
|
{
|
|
tri->facingLight = qtrue;
|
|
}
|
|
else
|
|
{
|
|
tri->facingLight = qfalse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// check if light origin is behind triangle
|
|
d = DotProduct(tri->plane, light->origin) - tri->plane[3];
|
|
|
|
if(surfaceShader->cullType == CT_TWO_SIDED || (d > 0 && surfaceShader->cullType != CT_BACK_SIDED))
|
|
{
|
|
tri->facingLight = qtrue;
|
|
}
|
|
else
|
|
{
|
|
tri->facingLight = qfalse;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tri->degenerated = qtrue;
|
|
tri->facingLight = qtrue; // FIXME ?
|
|
}
|
|
|
|
if(R_CullLightTriangle(light, pos) == CULL_OUT)
|
|
{
|
|
tri->facingLight = qfalse;
|
|
}
|
|
|
|
if(tri->facingLight)
|
|
numFacing++;
|
|
#else
|
|
tri->facingLight = qtrue;
|
|
numFacing++;
|
|
#endif
|
|
}
|
|
|
|
return numFacing;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_CreateVBOLightMeshes
|
|
===============
|
|
*/
|
|
static void R_CreateVBOLightMeshes(trRefLight_t * light)
|
|
{
|
|
#if 1
|
|
int i, j, k, l;
|
|
|
|
int numVerts;
|
|
|
|
int numTriangles, numLitTriangles;
|
|
srfTriangle_t *triangles;
|
|
srfTriangle_t *tri;
|
|
|
|
interactionVBO_t *iaVBO;
|
|
|
|
interactionCache_t *iaCache, *iaCache2;
|
|
interactionCache_t **iaCachesSorted;
|
|
int numCaches;
|
|
|
|
shader_t *shader, *oldShader;
|
|
|
|
bspSurface_t *surface;
|
|
|
|
srfVBOMesh_t *vboSurf;
|
|
vec3_t bounds[2];
|
|
|
|
if(!r_vboLighting->integer)
|
|
return;
|
|
|
|
if(r_deferredShading->integer && r_shadows->integer < SHADOWING_ESM16)
|
|
return;
|
|
|
|
if(!light->firstInteractionCache)
|
|
{
|
|
// this light has no interactions precached
|
|
return;
|
|
}
|
|
|
|
// count number of interaction caches
|
|
numCaches = 0;
|
|
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
|
|
{
|
|
if(iaCache->redundant)
|
|
continue;
|
|
|
|
surface = iaCache->surface;
|
|
|
|
if(!surface->shader->interactLight)
|
|
continue;
|
|
|
|
if(surface->shader->isPortal)
|
|
continue;
|
|
|
|
if(ShaderRequiresCPUDeforms(surface->shader))
|
|
continue;
|
|
|
|
numCaches++;
|
|
}
|
|
|
|
// build interaction caches list
|
|
iaCachesSorted = ri.Malloc(numCaches * sizeof(iaCachesSorted[0]));
|
|
|
|
numCaches = 0;
|
|
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
|
|
{
|
|
if(iaCache->redundant)
|
|
continue;
|
|
|
|
surface = iaCache->surface;
|
|
|
|
if(!surface->shader->interactLight)
|
|
continue;
|
|
|
|
if(surface->shader->isPortal)
|
|
continue;
|
|
|
|
if(ShaderRequiresCPUDeforms(surface->shader))
|
|
continue;
|
|
|
|
iaCachesSorted[numCaches] = iaCache;
|
|
numCaches++;
|
|
}
|
|
|
|
|
|
// sort interaction caches by shader
|
|
qsort(iaCachesSorted, numCaches, sizeof(iaCachesSorted), InteractionCacheCompare);
|
|
|
|
// create a VBO for each shader
|
|
shader = oldShader = NULL;
|
|
|
|
for(k = 0; k < numCaches; k++)
|
|
{
|
|
iaCache = iaCachesSorted[k];
|
|
|
|
iaCache->mergedIntoVBO = qtrue;
|
|
|
|
shader = iaCache->surface->shader;
|
|
|
|
if(shader != oldShader)
|
|
{
|
|
oldShader = shader;
|
|
|
|
// count vertices and indices
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
|
|
ClearBounds(bounds[0], bounds[1]);
|
|
for(l = k; l < numCaches; l++)
|
|
{
|
|
iaCache2 = iaCachesSorted[l];
|
|
|
|
surface = iaCache2->surface;
|
|
|
|
if(surface->shader != shader)
|
|
continue;
|
|
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
numLitTriangles = UpdateLightTriangles(s_worldData.verts, srf->numTriangles, s_worldData.triangles + srf->firstTriangle, surface->shader, light);
|
|
if(numLitTriangles)
|
|
{
|
|
BoundsAdd(bounds[0], bounds[1], srf->bounds[0], srf->bounds[1]);
|
|
}
|
|
|
|
numTriangles += numLitTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
numLitTriangles = UpdateLightTriangles(s_worldData.verts, srf->numTriangles, s_worldData.triangles + srf->firstTriangle, surface->shader, light);
|
|
if(numLitTriangles)
|
|
{
|
|
BoundsAdd(bounds[0], bounds[1], srf->bounds[0], srf->bounds[1]);
|
|
}
|
|
|
|
numTriangles += numLitTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
numLitTriangles = UpdateLightTriangles(s_worldData.verts, srf->numTriangles, s_worldData.triangles + srf->firstTriangle, surface->shader, light);
|
|
if(numLitTriangles)
|
|
{
|
|
BoundsAdd(bounds[0], bounds[1], srf->bounds[0], srf->bounds[1]);
|
|
}
|
|
|
|
numTriangles += numLitTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
}
|
|
|
|
if(!numVerts || !numTriangles)
|
|
continue;
|
|
|
|
//ri.Printf(PRINT_ALL, "...calculating light mesh VBOs ( %s, %i verts %i tris )\n", shader->name, vertexesNum, indexesNum / 3);
|
|
|
|
// create surface
|
|
vboSurf = ri.Hunk_Alloc(sizeof(*vboSurf), h_low);
|
|
vboSurf->surfaceType = SF_VBO_MESH;
|
|
vboSurf->numIndexes = numTriangles * 3;
|
|
vboSurf->numVerts = numVerts;
|
|
vboSurf->lightmapNum = -1;
|
|
|
|
VectorCopy(bounds[0], vboSurf->bounds[0]);
|
|
VectorCopy(bounds[1], vboSurf->bounds[1]);
|
|
|
|
// create arrays
|
|
triangles = ri.Malloc(numTriangles * sizeof(srfTriangle_t));
|
|
numTriangles = 0;
|
|
|
|
// build triangle indices
|
|
for(l = k; l < numCaches; l++)
|
|
{
|
|
iaCache2 = iaCachesSorted[l];
|
|
|
|
surface = iaCache2->surface;
|
|
|
|
if(surface->shader != shader)
|
|
continue;
|
|
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
|
|
|
|
for(i = 0, tri = s_worldData.triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
if(tri->facingLight)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles].indexes[j] = tri->indexes[j];
|
|
}
|
|
|
|
numTriangles++;
|
|
}
|
|
}
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface->data;
|
|
|
|
for(i = 0, tri = s_worldData.triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
if(tri->facingLight)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles].indexes[j] = tri->indexes[j];
|
|
}
|
|
|
|
numTriangles++;
|
|
}
|
|
}
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface->data;
|
|
|
|
for(i = 0, tri = s_worldData.triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
if(tri->facingLight)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles].indexes[j] = tri->indexes[j];
|
|
}
|
|
|
|
numTriangles++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
numVerts = OptimizeVertices(numVerts, verts, numTriangles, triangles, optimizedVerts, CompareLightVert);
|
|
if(c_redundantVertexes)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER,
|
|
"...removed %i redundant vertices from staticLightMesh %i ( %s, %i verts %i tris )\n",
|
|
c_redundantVertexes, c_vboLightSurfaces, shader->name, numVerts, numTriangles);
|
|
}
|
|
|
|
vboSurf->vbo = R_CreateVBO2(va("staticLightMesh_vertices %i", c_vboLightSurfaces), numVerts, optimizedVerts,
|
|
ATTR_POSITION | ATTR_TEXCOORD | ATTR_TANGENT | ATTR_BINORMAL | ATTR_NORMAL |
|
|
ATTR_COLOR, VBO_USAGE_STATIC);
|
|
*/
|
|
|
|
#if CALC_REDUNDANT_SHADOWVERTS
|
|
OptimizeTrianglesLite(s_worldData.redundantLightVerts, numTriangles, triangles);
|
|
if(c_redundantVertexes)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER, "...removed %i redundant vertices from staticLightMesh %i ( %s, %i verts %i tris )\n",
|
|
c_redundantVertexes, c_vboLightSurfaces, shader->name, numVerts, numTriangles);
|
|
}
|
|
#endif
|
|
vboSurf->vbo = s_worldData.vbo;
|
|
vboSurf->ibo =
|
|
R_CreateIBO2(va("staticLightMesh_IBO %i", c_vboLightSurfaces), numTriangles, triangles, VBO_USAGE_STATIC);
|
|
|
|
ri.Free(triangles);
|
|
|
|
// add everything needed to the light
|
|
iaVBO = R_CreateInteractionVBO(light);
|
|
iaVBO->shader = (struct shader_s *)shader;
|
|
iaVBO->vboLightMesh = (struct srfVBOMesh_s *)vboSurf;
|
|
|
|
c_vboLightSurfaces++;
|
|
}
|
|
}
|
|
|
|
ri.Free(iaCachesSorted);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_CreateVBOShadowMeshes
|
|
===============
|
|
*/
|
|
static void R_CreateVBOShadowMeshes(trRefLight_t * light)
|
|
{
|
|
#if 1
|
|
int i, j, k, l;
|
|
|
|
int numVerts;
|
|
|
|
int numTriangles, numLitTriangles;
|
|
srfTriangle_t *triangles;
|
|
srfTriangle_t *tri;
|
|
|
|
interactionVBO_t *iaVBO;
|
|
|
|
interactionCache_t *iaCache, *iaCache2;
|
|
interactionCache_t **iaCachesSorted;
|
|
int numCaches;
|
|
|
|
shader_t *shader, *oldShader;
|
|
qboolean alphaTest, oldAlphaTest;
|
|
|
|
bspSurface_t *surface;
|
|
|
|
srfVBOMesh_t *vboSurf;
|
|
vec3_t bounds[2];
|
|
|
|
if(!r_vboShadows->integer)
|
|
return;
|
|
|
|
if(r_shadows->integer < SHADOWING_ESM16)
|
|
return;
|
|
|
|
if(!light->firstInteractionCache)
|
|
{
|
|
// this light has no interactions precached
|
|
return;
|
|
}
|
|
|
|
if(light->l.noShadows)
|
|
return;
|
|
|
|
switch (light->l.rlType)
|
|
{
|
|
case RL_OMNI:
|
|
return;
|
|
|
|
case RL_DIRECTIONAL:
|
|
case RL_PROJ:
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// count number of interaction caches
|
|
numCaches = 0;
|
|
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
|
|
{
|
|
if(iaCache->redundant)
|
|
continue;
|
|
|
|
surface = iaCache->surface;
|
|
|
|
if(!surface->shader->interactLight)
|
|
continue;
|
|
|
|
if(surface->shader->isSky)
|
|
continue;
|
|
|
|
if(surface->shader->noShadows)
|
|
continue;
|
|
|
|
if(surface->shader->sort > SS_OPAQUE)
|
|
continue;
|
|
|
|
if(surface->shader->isPortal)
|
|
continue;
|
|
|
|
if(ShaderRequiresCPUDeforms(surface->shader))
|
|
continue;
|
|
|
|
numCaches++;
|
|
}
|
|
|
|
// build interaction caches list
|
|
iaCachesSorted = ri.Malloc(numCaches * sizeof(iaCachesSorted[0]));
|
|
|
|
numCaches = 0;
|
|
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
|
|
{
|
|
if(iaCache->redundant)
|
|
continue;
|
|
|
|
surface = iaCache->surface;
|
|
|
|
if(!surface->shader->interactLight)
|
|
continue;
|
|
|
|
if(surface->shader->isSky)
|
|
continue;
|
|
|
|
if(surface->shader->noShadows)
|
|
continue;
|
|
|
|
if(surface->shader->sort > SS_OPAQUE)
|
|
continue;
|
|
|
|
if(surface->shader->isPortal)
|
|
continue;
|
|
|
|
if(ShaderRequiresCPUDeforms(surface->shader))
|
|
continue;
|
|
|
|
iaCachesSorted[numCaches] = iaCache;
|
|
numCaches++;
|
|
}
|
|
|
|
|
|
// sort interaction caches by shader
|
|
qsort(iaCachesSorted, numCaches, sizeof(iaCachesSorted), InteractionCacheCompare);
|
|
|
|
// create a VBO for each shader
|
|
shader = oldShader = NULL;
|
|
oldAlphaTest = alphaTest = -1;
|
|
|
|
for(k = 0; k < numCaches; k++)
|
|
{
|
|
iaCache = iaCachesSorted[k];
|
|
|
|
iaCache->mergedIntoVBO = qtrue;
|
|
|
|
shader = iaCache->surface->shader;
|
|
alphaTest = shader->alphaTest;
|
|
|
|
iaCache->mergedIntoVBO = qtrue;
|
|
|
|
if(alphaTest ? shader != oldShader : alphaTest != oldAlphaTest)
|
|
{
|
|
oldShader = shader;
|
|
oldAlphaTest = alphaTest;
|
|
|
|
// count vertices and indices
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
|
|
ClearBounds(bounds[0], bounds[1]);
|
|
for(l = k; l < numCaches; l++)
|
|
{
|
|
iaCache2 = iaCachesSorted[l];
|
|
|
|
surface = iaCache2->surface;
|
|
|
|
#if 0
|
|
if(surface->shader != shader)
|
|
break;
|
|
#else
|
|
if(alphaTest)
|
|
{
|
|
if(surface->shader != shader)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if(surface->shader->alphaTest != alphaTest)
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
numLitTriangles = UpdateLightTriangles(s_worldData.verts, srf->numTriangles, s_worldData.triangles + srf->firstTriangle, surface->shader, light);
|
|
if(numLitTriangles)
|
|
{
|
|
BoundsAdd(bounds[0], bounds[1], srf->bounds[0], srf->bounds[1]);
|
|
}
|
|
|
|
numTriangles += numLitTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
numLitTriangles = UpdateLightTriangles(s_worldData.verts, srf->numTriangles, s_worldData.triangles + srf->firstTriangle, surface->shader, light);
|
|
if(numLitTriangles)
|
|
{
|
|
BoundsAdd(bounds[0], bounds[1], srf->bounds[0], srf->bounds[1]);
|
|
}
|
|
|
|
numTriangles += numLitTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface->data;
|
|
|
|
if(srf->numTriangles)
|
|
{
|
|
numLitTriangles = UpdateLightTriangles(s_worldData.verts, srf->numTriangles, s_worldData.triangles + srf->firstTriangle, surface->shader, light);
|
|
if(numLitTriangles)
|
|
{
|
|
BoundsAdd(bounds[0], bounds[1], srf->bounds[0], srf->bounds[1]);
|
|
}
|
|
|
|
numTriangles += numLitTriangles;
|
|
}
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
}
|
|
|
|
if(!numVerts || !numTriangles)
|
|
continue;
|
|
|
|
//ri.Printf(PRINT_ALL, "...calculating light mesh VBOs ( %s, %i verts %i tris )\n", shader->name, vertexesNum, indexesNum / 3);
|
|
|
|
// create surface
|
|
vboSurf = ri.Hunk_Alloc(sizeof(*vboSurf), h_low);
|
|
vboSurf->surfaceType = SF_VBO_MESH;
|
|
vboSurf->numIndexes = numTriangles * 3;
|
|
vboSurf->numVerts = numVerts;
|
|
vboSurf->lightmapNum = -1;
|
|
|
|
VectorCopy(bounds[0], vboSurf->bounds[0]);
|
|
VectorCopy(bounds[1], vboSurf->bounds[1]);
|
|
|
|
// create arrays
|
|
triangles = ri.Malloc(numTriangles * sizeof(srfTriangle_t));
|
|
numTriangles = 0;
|
|
|
|
// build triangle indices
|
|
for(l = k; l < numCaches; l++)
|
|
{
|
|
iaCache2 = iaCachesSorted[l];
|
|
|
|
surface = iaCache2->surface;
|
|
|
|
#if 0
|
|
if(surface->shader != shader)
|
|
break;
|
|
#else
|
|
if(alphaTest)
|
|
{
|
|
if(surface->shader != shader)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if(surface->shader->alphaTest != alphaTest)
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
|
|
|
|
for(i = 0, tri = s_worldData.triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
if(tri->facingLight)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles].indexes[j] = tri->indexes[j];
|
|
}
|
|
|
|
numTriangles++;
|
|
}
|
|
}
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface->data;
|
|
|
|
for(i = 0, tri = s_worldData.triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
if(tri->facingLight)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles].indexes[j] = tri->indexes[j];
|
|
}
|
|
|
|
numTriangles++;
|
|
}
|
|
}
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface->data;
|
|
|
|
for(i = 0, tri = s_worldData.triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
if(tri->facingLight)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles].indexes[j] = tri->indexes[j];
|
|
}
|
|
|
|
numTriangles++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
numVerts = OptimizeVertices(numVerts, verts, numTriangles, triangles, optimizedVerts, CompareLightVert);
|
|
if(c_redundantVertexes)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER,
|
|
"...removed %i redundant vertices from staticLightMesh %i ( %s, %i verts %i tris )\n",
|
|
c_redundantVertexes, c_vboLightSurfaces, shader->name, numVerts, numTriangles);
|
|
}
|
|
|
|
vboSurf->vbo = R_CreateVBO2(va("staticLightMesh_vertices %i", c_vboLightSurfaces), numVerts, optimizedVerts,
|
|
ATTR_POSITION | ATTR_TEXCOORD | ATTR_TANGENT | ATTR_BINORMAL | ATTR_NORMAL |
|
|
ATTR_COLOR, VBO_USAGE_STATIC);
|
|
*/
|
|
|
|
#if CALC_REDUNDANT_SHADOWVERTS
|
|
OptimizeTrianglesLite(s_worldData.redundantShadowVerts, numTriangles, triangles);
|
|
if(c_redundantVertexes)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER, "...removed %i redundant vertices from staticLightMesh %i ( %s, %i verts %i tris )\n",
|
|
c_redundantVertexes, c_vboLightSurfaces, shader->name, numVerts, numTriangles);
|
|
}
|
|
#endif
|
|
vboSurf->vbo = s_worldData.vbo;
|
|
vboSurf->ibo = R_CreateIBO2(va("staticShadowMesh_IBO %i", c_vboLightSurfaces), numTriangles, triangles, VBO_USAGE_STATIC);
|
|
|
|
ri.Free(triangles);
|
|
|
|
// add everything needed to the light
|
|
iaVBO = R_CreateInteractionVBO(light);
|
|
iaVBO->shader = (struct shader_s *)shader;
|
|
iaVBO->vboShadowMesh = (struct srfVBOMesh_s *)vboSurf;
|
|
|
|
c_vboShadowSurfaces++;
|
|
}
|
|
}
|
|
|
|
ri.Free(iaCachesSorted);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_CreateVBOShadowCubeMeshes
|
|
===============
|
|
*/
|
|
static void R_CreateVBOShadowCubeMeshes(trRefLight_t * light)
|
|
{
|
|
int i, j, k, l;
|
|
|
|
int numVerts;
|
|
|
|
int numTriangles;
|
|
srfTriangle_t *triangles;
|
|
srfTriangle_t *tri;
|
|
|
|
interactionVBO_t *iaVBO;
|
|
|
|
interactionCache_t *iaCache, *iaCache2;
|
|
interactionCache_t **iaCachesSorted;
|
|
int numCaches;
|
|
|
|
shader_t *shader, *oldShader;
|
|
qboolean alphaTest, oldAlphaTest;
|
|
int cubeSide;
|
|
|
|
bspSurface_t *surface;
|
|
|
|
srfVBOMesh_t *vboSurf;
|
|
|
|
if(!r_vboShadows->integer)
|
|
return;
|
|
|
|
if(r_shadows->integer < SHADOWING_ESM16)
|
|
return;
|
|
|
|
if(!light->firstInteractionCache)
|
|
{
|
|
// this light has no interactions precached
|
|
return;
|
|
}
|
|
|
|
if(light->l.noShadows)
|
|
return;
|
|
|
|
if(light->l.rlType != RL_OMNI)
|
|
return;
|
|
|
|
// count number of interaction caches
|
|
numCaches = 0;
|
|
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
|
|
{
|
|
if(iaCache->redundant)
|
|
continue;
|
|
|
|
surface = iaCache->surface;
|
|
|
|
if(!surface->shader->interactLight)
|
|
continue;
|
|
|
|
if(surface->shader->isSky)
|
|
continue;
|
|
|
|
if(surface->shader->noShadows)
|
|
continue;
|
|
|
|
if(surface->shader->sort > SS_OPAQUE)
|
|
continue;
|
|
|
|
if(surface->shader->isPortal)
|
|
continue;
|
|
|
|
if(ShaderRequiresCPUDeforms(surface->shader))
|
|
continue;
|
|
|
|
numCaches++;
|
|
}
|
|
|
|
// build interaction caches list
|
|
iaCachesSorted = ri.Malloc(numCaches * sizeof(iaCachesSorted[0]));
|
|
|
|
numCaches = 0;
|
|
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
|
|
{
|
|
if(iaCache->redundant)
|
|
continue;
|
|
|
|
surface = iaCache->surface;
|
|
|
|
if(!surface->shader->interactLight)
|
|
continue;
|
|
|
|
if(surface->shader->isSky)
|
|
continue;
|
|
|
|
if(surface->shader->noShadows)
|
|
continue;
|
|
|
|
if(surface->shader->sort > SS_OPAQUE)
|
|
continue;
|
|
|
|
if(surface->shader->isPortal)
|
|
continue;
|
|
|
|
if(ShaderRequiresCPUDeforms(surface->shader))
|
|
continue;
|
|
|
|
iaCachesSorted[numCaches] = iaCache;
|
|
numCaches++;
|
|
}
|
|
|
|
// sort interaction caches by shader
|
|
qsort(iaCachesSorted, numCaches, sizeof(iaCachesSorted), InteractionCacheCompare);
|
|
|
|
// create a VBO for each shader
|
|
for(cubeSide = 0; cubeSide < 6; cubeSide++)
|
|
{
|
|
shader = oldShader = NULL;
|
|
oldAlphaTest = alphaTest = -1;
|
|
|
|
for(k = 0; k < numCaches; k++)
|
|
{
|
|
iaCache = iaCachesSorted[k];
|
|
shader = iaCache->surface->shader;
|
|
alphaTest = shader->alphaTest;
|
|
|
|
iaCache->mergedIntoVBO = qtrue;
|
|
|
|
//if(!(iaCache->cubeSideBits & (1 << cubeSide)))
|
|
// continue;
|
|
|
|
//if(shader != oldShader)
|
|
if(alphaTest ? shader != oldShader : alphaTest != oldAlphaTest)
|
|
{
|
|
oldShader = shader;
|
|
oldAlphaTest = alphaTest;
|
|
|
|
// count vertices and indices
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
|
|
for(l = k; l < numCaches; l++)
|
|
{
|
|
iaCache2 = iaCachesSorted[l];
|
|
|
|
surface = iaCache2->surface;
|
|
|
|
#if 0
|
|
if(surface->shader != shader)
|
|
break;
|
|
#else
|
|
if(alphaTest)
|
|
{
|
|
if(surface->shader != shader)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if(surface->shader->alphaTest != alphaTest)
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
if(!(iaCache2->cubeSideBits & (1 << cubeSide)))
|
|
continue;
|
|
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
|
|
|
|
if(srf->numTriangles)
|
|
numTriangles +=
|
|
UpdateLightTriangles(s_worldData.verts, srf->numTriangles,
|
|
s_worldData.triangles + srf->firstTriangle, surface->shader, light);
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface->data;
|
|
|
|
if(srf->numTriangles)
|
|
numTriangles +=
|
|
UpdateLightTriangles(s_worldData.verts, srf->numTriangles,
|
|
s_worldData.triangles + srf->firstTriangle, surface->shader, light);
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface->data;
|
|
|
|
if(srf->numTriangles)
|
|
numTriangles +=
|
|
UpdateLightTriangles(s_worldData.verts, srf->numTriangles,
|
|
s_worldData.triangles + srf->firstTriangle, surface->shader, light);
|
|
|
|
if(srf->numVerts)
|
|
numVerts += srf->numVerts;
|
|
}
|
|
}
|
|
|
|
if(!numVerts || !numTriangles)
|
|
continue;
|
|
|
|
//ri.Printf(PRINT_ALL, "...calculating light mesh VBOs ( %s, %i verts %i tris )\n", shader->name, vertexesNum, indexesNum / 3);
|
|
|
|
// create surface
|
|
vboSurf = ri.Hunk_Alloc(sizeof(*vboSurf), h_low);
|
|
vboSurf->surfaceType = SF_VBO_MESH;
|
|
vboSurf->numIndexes = numTriangles * 3;
|
|
vboSurf->numVerts = numVerts;
|
|
vboSurf->lightmapNum = -1;
|
|
ZeroBounds(vboSurf->bounds[0], vboSurf->bounds[1]);
|
|
|
|
// create arrays
|
|
triangles = ri.Malloc(numTriangles * sizeof(srfTriangle_t));
|
|
numTriangles = 0;
|
|
|
|
// build triangle indices
|
|
for(l = k; l < numCaches; l++)
|
|
{
|
|
iaCache2 = iaCachesSorted[l];
|
|
|
|
surface = iaCache2->surface;
|
|
|
|
#if 0
|
|
if(surface->shader != shader)
|
|
break;
|
|
#else
|
|
if(alphaTest)
|
|
{
|
|
if(surface->shader != shader)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if(surface->shader->alphaTest != alphaTest)
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
if(!(iaCache2->cubeSideBits & (1 << cubeSide)))
|
|
continue;
|
|
|
|
if(*surface->data == SF_FACE)
|
|
{
|
|
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
|
|
|
|
for(i = 0, tri = s_worldData.triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
if(tri->facingLight)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles].indexes[j] = tri->indexes[j];
|
|
}
|
|
|
|
numTriangles++;
|
|
}
|
|
}
|
|
}
|
|
else if(*surface->data == SF_GRID)
|
|
{
|
|
srfGridMesh_t *srf = (srfGridMesh_t *) surface->data;
|
|
|
|
for(i = 0, tri = s_worldData.triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
if(tri->facingLight)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles].indexes[j] = tri->indexes[j];
|
|
}
|
|
|
|
numTriangles++;
|
|
}
|
|
}
|
|
}
|
|
else if(*surface->data == SF_TRIANGLES)
|
|
{
|
|
srfTriangles_t *srf = (srfTriangles_t *) surface->data;
|
|
|
|
for(i = 0, tri = s_worldData.triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
|
|
{
|
|
if(tri->facingLight)
|
|
{
|
|
for(j = 0; j < 3; j++)
|
|
{
|
|
triangles[numTriangles].indexes[j] = tri->indexes[j];
|
|
}
|
|
|
|
numTriangles++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(alphaTest)
|
|
{
|
|
#if CALC_REDUNDANT_SHADOWVERTS
|
|
//OptimizeTriangles(s_worldData.numVerts, s_worldData.verts, numTriangles, triangles, CompareShadowVertAlphaTest);
|
|
OptimizeTrianglesLite(s_worldData.redundantShadowAlphaTestVerts, numTriangles, triangles);
|
|
if(c_redundantVertexes)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER,
|
|
"...removed %i redundant vertices from staticShadowPyramidMesh %i ( %s, %i verts %i tris )\n",
|
|
c_redundantVertexes, c_vboShadowSurfaces, shader->name, numVerts, numTriangles);
|
|
}
|
|
#endif
|
|
vboSurf->vbo = s_worldData.vbo;
|
|
vboSurf->ibo =
|
|
R_CreateIBO2(va("staticShadowPyramidMesh_IBO %i", c_vboShadowSurfaces), numTriangles, triangles,
|
|
VBO_USAGE_STATIC);
|
|
}
|
|
else
|
|
{
|
|
#if CALC_REDUNDANT_SHADOWVERTS
|
|
//OptimizeTriangles(s_worldData.numVerts, s_worldData.verts, numTriangles, triangles, CompareShadowVert);
|
|
OptimizeTrianglesLite(s_worldData.redundantShadowVerts, numTriangles, triangles);
|
|
if(c_redundantVertexes)
|
|
{
|
|
ri.Printf(PRINT_DEVELOPER,
|
|
"...removed %i redundant vertices from staticShadowPyramidMesh %i ( %s, %i verts %i tris )\n",
|
|
c_redundantVertexes, c_vboShadowSurfaces, shader->name, numVerts, numTriangles);
|
|
}
|
|
#endif
|
|
vboSurf->vbo = s_worldData.vbo;
|
|
vboSurf->ibo =
|
|
R_CreateIBO2(va("staticShadowPyramidMesh_IBO %i", c_vboShadowSurfaces), numTriangles, triangles,
|
|
VBO_USAGE_STATIC);
|
|
}
|
|
|
|
ri.Free(triangles);
|
|
|
|
// add everything needed to the light
|
|
iaVBO = R_CreateInteractionVBO(light);
|
|
iaVBO->cubeSideBits = (1 << cubeSide);
|
|
iaVBO->shader = (struct shader_s *)shader;
|
|
iaVBO->vboShadowMesh = (struct srfVBOMesh_s *)vboSurf;
|
|
|
|
c_vboShadowSurfaces++;
|
|
}
|
|
}
|
|
}
|
|
|
|
ri.Free(iaCachesSorted);
|
|
}
|
|
|
|
static void R_CalcInteractionCubeSideBits(trRefLight_t * light)
|
|
{
|
|
interactionCache_t *iaCache;
|
|
bspSurface_t *surface;
|
|
vec3_t localBounds[2];
|
|
|
|
if(r_shadows->integer <= SHADOWING_BLOB)
|
|
return;
|
|
|
|
if(!light->firstInteractionCache)
|
|
{
|
|
// this light has no interactions precached
|
|
return;
|
|
}
|
|
|
|
if(light->l.noShadows)
|
|
{
|
|
// actually noShadows lights are quite bad concerning this optimization
|
|
return;
|
|
}
|
|
|
|
if(light->l.rlType != RL_OMNI)
|
|
return;
|
|
|
|
/*
|
|
if(glConfig.vertexBufferObjectAvailable && r_vboLighting->integer)
|
|
{
|
|
srfVBOLightMesh_t *srf;
|
|
|
|
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
|
|
{
|
|
if(iaCache->redundant)
|
|
continue;
|
|
|
|
if(!iaCache->vboLightMesh)
|
|
continue;
|
|
|
|
srf = iaCache->vboLightMesh;
|
|
|
|
VectorCopy(srf->bounds[0], localBounds[0]);
|
|
VectorCopy(srf->bounds[1], localBounds[1]);
|
|
|
|
light->shadowLOD = 0; // important for R_CalcLightCubeSideBits
|
|
iaCache->cubeSideBits = R_CalcLightCubeSideBits(light, localBounds);
|
|
}
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
|
|
{
|
|
surface = iaCache->surface;
|
|
|
|
if(*surface->data == SF_FACE || *surface->data == SF_GRID || *surface->data == SF_TRIANGLES)
|
|
{
|
|
srfGeneric_t *gen;
|
|
|
|
gen = (srfGeneric_t *) surface->data;
|
|
|
|
VectorCopy(gen->bounds[0], localBounds[0]);
|
|
VectorCopy(gen->bounds[1], localBounds[1]);
|
|
}
|
|
else
|
|
{
|
|
iaCache->cubeSideBits = CUBESIDE_CLIPALL;
|
|
continue;
|
|
}
|
|
|
|
light->shadowLOD = 0; // important for R_CalcLightCubeSideBits
|
|
iaCache->cubeSideBits = R_CalcLightCubeSideBits(light, localBounds);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
R_PrecacheInteractions
|
|
=============
|
|
*/
|
|
void R_PrecacheInteractions()
|
|
{
|
|
int i;
|
|
trRefLight_t *light;
|
|
bspSurface_t *surface;
|
|
// int numLeafs;
|
|
int startTime, endTime;
|
|
|
|
//if(r_precomputedLighting->integer)
|
|
// return;
|
|
|
|
startTime = ri.Milliseconds();
|
|
|
|
// reset surfaces' viewCount
|
|
s_lightCount = 0;
|
|
for(i = 0, surface = s_worldData.surfaces; i < s_worldData.numSurfaces; i++, surface++)
|
|
{
|
|
surface->lightCount = -1;
|
|
}
|
|
|
|
Com_InitGrowList(&s_interactions, 100);
|
|
|
|
c_redundantInteractions = 0;
|
|
c_vboWorldSurfaces = 0;
|
|
c_vboLightSurfaces = 0;
|
|
c_vboShadowSurfaces = 0;
|
|
|
|
ri.Printf(PRINT_ALL, "...precaching %i lights\n", s_worldData.numLights);
|
|
|
|
for(i = 0; i < s_worldData.numLights; i++)
|
|
{
|
|
light = &s_worldData.lights[i];
|
|
|
|
if((r_precomputedLighting->integer || r_vertexLighting->integer) && !light->noRadiosity)
|
|
continue;
|
|
|
|
#if 0
|
|
ri.Printf(PRINT_ALL, "light %i: origin(%i %i %i) radius(%i %i %i) color(%f %f %f)\n",
|
|
i,
|
|
(int)light->l.origin[0], (int)light->l.origin[1], (int)light->l.origin[2],
|
|
(int)light->l.radius[0], (int)light->l.radius[1], (int)light->l.radius[2],
|
|
light->l.color[0], light->l.color[1], light->l.color[2]);
|
|
#endif
|
|
|
|
// set up light transform matrix
|
|
MatrixSetupTransformFromQuat(light->transformMatrix, light->l.rotation, light->l.origin);
|
|
|
|
// set up light origin for lighting and shadowing
|
|
R_SetupLightOrigin(light);
|
|
|
|
// set up model to light view matrix
|
|
R_SetupLightView(light);
|
|
|
|
// set up projection
|
|
R_SetupLightProjection(light);
|
|
|
|
// calc local bounds for culling
|
|
R_SetupLightLocalBounds(light);
|
|
|
|
// setup world bounds for intersection tests
|
|
R_SetupLightWorldBounds(light);
|
|
|
|
// setup frustum planes for intersection tests
|
|
R_SetupLightFrustum(light);
|
|
|
|
// setup interactions
|
|
light->firstInteractionCache = NULL;
|
|
light->lastInteractionCache = NULL;
|
|
|
|
light->firstInteractionVBO = NULL;
|
|
light->lastInteractionVBO = NULL;
|
|
|
|
// perform culling and add all the potentially visible surfaces
|
|
s_lightCount++;
|
|
R_RecursivePrecacheInteractionNode(s_worldData.nodes, light);
|
|
|
|
// count number of leafs that touch this light
|
|
s_lightCount++;
|
|
QueueInit(&light->leafs);
|
|
R_RecursiveAddInteractionNode(s_worldData.nodes, light);
|
|
//ri.Printf(PRINT_ALL, "light %i touched %i leaves\n", i, QueueSize(&light->leafs));
|
|
|
|
#if 0
|
|
// Tr3b: this can cause really bad shadow problems :/
|
|
|
|
// check if interactions are inside shadows of other interactions
|
|
R_KillRedundantInteractions(light);
|
|
#endif
|
|
|
|
// create a static VBO surface for each light geometry batch
|
|
R_CreateVBOLightMeshes(light);
|
|
|
|
// create a static VBO surface for each shadow geometry batch
|
|
R_CreateVBOShadowMeshes(light);
|
|
|
|
// calculate pyramid bits for each interaction in omni-directional lights
|
|
R_CalcInteractionCubeSideBits(light);
|
|
|
|
// create a static VBO surface for each light geometry batch inside a cubemap pyramid
|
|
R_CreateVBOShadowCubeMeshes(light);
|
|
}
|
|
|
|
// move interactions grow list to hunk
|
|
s_worldData.numInteractions = s_interactions.currentElements;
|
|
s_worldData.interactions = ri.Hunk_Alloc(s_worldData.numInteractions * sizeof(*s_worldData.interactions), h_low);
|
|
|
|
for(i = 0; i < s_worldData.numInteractions; i++)
|
|
{
|
|
s_worldData.interactions[i] = (interactionCache_t *) Com_GrowListElement(&s_interactions, i);
|
|
}
|
|
|
|
Com_DestroyGrowList(&s_interactions);
|
|
|
|
|
|
ri.Printf(PRINT_ALL, "%i interactions precached\n", s_worldData.numInteractions);
|
|
ri.Printf(PRINT_ALL, "%i interactions were hidden in shadows\n", c_redundantInteractions);
|
|
|
|
if(r_shadows->integer >= SHADOWING_ESM16)
|
|
{
|
|
// only interesting for omni-directional shadow mapping
|
|
ri.Printf(PRINT_ALL, "%i omni pyramid tests\n", tr.pc.c_pyramidTests);
|
|
ri.Printf(PRINT_ALL, "%i omni pyramid surfaces visible\n", tr.pc.c_pyramid_cull_ent_in);
|
|
ri.Printf(PRINT_ALL, "%i omni pyramid surfaces clipped\n", tr.pc.c_pyramid_cull_ent_clip);
|
|
ri.Printf(PRINT_ALL, "%i omni pyramid surfaces culled\n", tr.pc.c_pyramid_cull_ent_out);
|
|
}
|
|
|
|
endTime = ri.Milliseconds();
|
|
|
|
ri.Printf(PRINT_ALL, "lights precaching time = %5.2f seconds\n", (endTime - startTime) / 1000.0);
|
|
}
|
|
|
|
|
|
#define HASHTABLE_SIZE 7919 // 32749 // 2039 /* prime, use % */
|
|
#define HASH_USE_EPSILON
|
|
|
|
#ifdef HASH_USE_EPSILON
|
|
#define HASH_XYZ_EPSILON 0.01f
|
|
#define HASH_XYZ_EPSILONSPACE_MULTIPLIER 1.f / HASH_XYZ_EPSILON
|
|
#endif
|
|
|
|
unsigned int VertexCoordGenerateHash(const vec3_t xyz)
|
|
{
|
|
unsigned int hash = 0;
|
|
|
|
#ifndef HASH_USE_EPSILON
|
|
hash += ~(*((unsigned int *)&xyz[0]) << 15);
|
|
hash ^= (*((unsigned int *)&xyz[0]) >> 10);
|
|
hash += (*((unsigned int *)&xyz[1]) << 3);
|
|
hash ^= (*((unsigned int *)&xyz[1]) >> 6);
|
|
hash += ~(*((unsigned int *)&xyz[2]) << 11);
|
|
hash ^= (*((unsigned int *)&xyz[2]) >> 16);
|
|
#else
|
|
vec3_t xyz_epsilonspace;
|
|
|
|
VectorScale(xyz, HASH_XYZ_EPSILONSPACE_MULTIPLIER, xyz_epsilonspace);
|
|
xyz_epsilonspace[0] = (double)floor(xyz_epsilonspace[0]);
|
|
xyz_epsilonspace[1] = (double)floor(xyz_epsilonspace[1]);
|
|
xyz_epsilonspace[2] = (double)floor(xyz_epsilonspace[2]);
|
|
|
|
hash += ~(*((unsigned int *)&xyz_epsilonspace[0]) << 15);
|
|
hash ^= (*((unsigned int *)&xyz_epsilonspace[0]) >> 10);
|
|
hash += (*((unsigned int *)&xyz_epsilonspace[1]) << 3);
|
|
hash ^= (*((unsigned int *)&xyz_epsilonspace[1]) >> 6);
|
|
hash += ~(*((unsigned int *)&xyz_epsilonspace[2]) << 11);
|
|
hash ^= (*((unsigned int *)&xyz_epsilonspace[2]) >> 16);
|
|
|
|
/*
|
|
// strict aliasing... better?
|
|
hash += ~({floatint_t __f; __f.f = xyz_epsilonspace[0]; __f.i << 15;});
|
|
hash ^= ({floatint_t __f; __f.f = xyz_epsilonspace[0]; __f.i >> 10;});
|
|
hash += ({floatint_t __f; __f.f = xyz_epsilonspace[1]; __f.i << 3;});
|
|
hash ^= ({floatint_t __f; __f.f = xyz_epsilonspace[1]; __f.i >> 6;});
|
|
hash += ~({floatint_t __f; __f.f = xyz_epsilonspace[2]; __f.i << 11;});
|
|
hash ^= ({floatint_t __f; __f.f = xyz_epsilonspace[2]; __f.i >> 16;});
|
|
*/
|
|
|
|
#endif
|
|
|
|
hash = (int)fabs(xyz[3]) / 8;
|
|
|
|
hash = hash % (HASHTABLE_SIZE);
|
|
return hash;
|
|
}
|
|
|
|
vertexHash_t **NewVertexHashTable(void)
|
|
{
|
|
vertexHash_t **hashTable = Com_Allocate(HASHTABLE_SIZE * sizeof(vertexHash_t *));
|
|
|
|
Com_Memset(hashTable, 0, HASHTABLE_SIZE * sizeof(vertexHash_t *));
|
|
|
|
return hashTable;
|
|
}
|
|
|
|
void FreeVertexHashTable(vertexHash_t ** hashTable)
|
|
{
|
|
int i;
|
|
vertexHash_t *vertexHash;
|
|
vertexHash_t *nextVertexHash;
|
|
|
|
if(hashTable == NULL)
|
|
return;
|
|
|
|
for(i = 0; i < HASHTABLE_SIZE; i++)
|
|
{
|
|
if(hashTable[i])
|
|
{
|
|
nextVertexHash = NULL;
|
|
|
|
for(vertexHash = hashTable[i]; vertexHash; vertexHash = nextVertexHash)
|
|
{
|
|
nextVertexHash = vertexHash->next;
|
|
/*
|
|
if(vertexHash->data != NULL)
|
|
{
|
|
Com_Dealloc(vertexHash->data);
|
|
}
|
|
*/
|
|
Com_Dealloc(vertexHash);
|
|
}
|
|
}
|
|
}
|
|
|
|
Com_Dealloc(hashTable);
|
|
}
|
|
|
|
vertexHash_t *FindVertexInHashTable(vertexHash_t ** hashTable, const vec3_t xyz, float distance)
|
|
{
|
|
unsigned int hash;
|
|
vertexHash_t *vertexHash;
|
|
|
|
if(hashTable == NULL || xyz == NULL)
|
|
return NULL;
|
|
|
|
hash = VertexCoordGenerateHash(xyz);
|
|
|
|
for(vertexHash = hashTable[hash]; vertexHash; vertexHash = vertexHash->next)
|
|
{
|
|
#ifndef HASH_USE_EPSILON
|
|
if((vertexHash->vcd.xyz[0] != xyz[0] || vertexHash->vcd.xyz[1] != xyz[1] ||
|
|
vertexHash->vcd.xyz[2] != xyz[2]))
|
|
continue;
|
|
|
|
#elif 1
|
|
if(Distance(xyz, vertexHash->xyz) > distance)
|
|
continue;
|
|
|
|
#else
|
|
if((fabs(xyz[0] - vertexHash->vcd.xyz[0])) > HASH_XYZ_EPSILON ||
|
|
(fabs(xyz[1] - vertexHash->vcd.xyz[1])) > HASH_XYZ_EPSILON ||
|
|
(fabs(xyz[2] - vertexHash->vcd.xyz[2])) > HASH_XYZ_EPSILON)
|
|
continue;
|
|
#endif
|
|
return vertexHash;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
vertexHash_t *AddVertexToHashTable(vertexHash_t ** hashTable, vec3_t xyz, void *data)
|
|
{
|
|
unsigned int hash;
|
|
vertexHash_t *vertexHash;
|
|
|
|
if(hashTable == NULL || xyz == NULL)
|
|
return NULL;
|
|
|
|
vertexHash = Com_Allocate(sizeof(vertexHash_t));
|
|
|
|
if(!vertexHash)
|
|
return NULL;
|
|
|
|
hash = VertexCoordGenerateHash(xyz);
|
|
|
|
VectorCopy(xyz, vertexHash->xyz);
|
|
vertexHash->data = data;
|
|
|
|
// link into table
|
|
vertexHash->next = hashTable[hash];
|
|
hashTable[hash] = vertexHash;
|
|
|
|
return vertexHash;
|
|
}
|
|
|
|
void GL_BindNearestCubeMap(const vec3_t xyz)
|
|
{
|
|
#if 0
|
|
int j;
|
|
float distance, maxDistance;
|
|
cubemapProbe_t *cubeProbe;
|
|
|
|
GLimp_LogComment("--- GL_BindNearestCubeMap ---\n");
|
|
|
|
maxDistance = 9999999.0f;
|
|
tr.autoCubeImage = tr.blackCubeImage;
|
|
for(j = 0; j < tr.cubeProbes.currentElements; j++)
|
|
{
|
|
cubeProbe = Com_GrowListElement(&tr.cubeProbes, j);
|
|
|
|
distance = Distance(cubeProbe->origin, xyz);
|
|
if(distance < maxDistance)
|
|
{
|
|
tr.autoCubeImage = cubeProbe->cubemap;
|
|
maxDistance = distance;
|
|
}
|
|
}
|
|
#else
|
|
float distance, maxDistance;
|
|
cubemapProbe_t *cubeProbe;
|
|
unsigned int hash;
|
|
vertexHash_t *vertexHash;
|
|
|
|
tr.autoCubeImage = tr.whiteCubeImage;
|
|
if(!r_reflectionMapping->integer)
|
|
return;
|
|
|
|
if(tr.cubeHashTable == NULL || xyz == NULL)
|
|
return;
|
|
|
|
maxDistance = 9999999.0f;
|
|
|
|
hash = VertexCoordGenerateHash(xyz);
|
|
|
|
for(vertexHash = tr.cubeHashTable[hash]; vertexHash; vertexHash = vertexHash->next)
|
|
{
|
|
cubeProbe = vertexHash->data;
|
|
|
|
distance = Distance(cubeProbe->origin, xyz);
|
|
if(distance < maxDistance)
|
|
{
|
|
tr.autoCubeImage = cubeProbe->cubemap;
|
|
maxDistance = distance;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(USE_D3D10)
|
|
// TODO
|
|
#else
|
|
GL_Bind(tr.autoCubeImage);
|
|
#endif
|
|
}
|
|
|
|
void R_FindTwoNearestCubeMaps(const vec3_t position, cubemapProbe_t **cubeProbeNearest, cubemapProbe_t **cubeProbeSecondNearest)
|
|
{
|
|
int j;
|
|
float distance, maxDistance, maxDistance2;
|
|
cubemapProbe_t *cubeProbe;
|
|
unsigned int hash;
|
|
vertexHash_t *vertexHash;
|
|
|
|
GLimp_LogComment("--- R_FindTwoNearestCubeMaps ---\n");
|
|
|
|
*cubeProbeNearest = NULL;
|
|
*cubeProbeSecondNearest = NULL;
|
|
|
|
if(tr.cubeHashTable == NULL || position == NULL)
|
|
return;
|
|
|
|
hash = VertexCoordGenerateHash(position);
|
|
maxDistance = maxDistance2 = 9999999.0f;
|
|
|
|
#if 0
|
|
for(j = 0; j < tr.cubeProbes.currentElements; j++)
|
|
{
|
|
cubeProbe = Com_GrowListElement(&tr.cubeProbes, j);
|
|
#else
|
|
for(j = 0, vertexHash = tr.cubeHashTable[hash]; vertexHash; vertexHash = vertexHash->next, j++)
|
|
{
|
|
cubeProbe = vertexHash->data;
|
|
#endif
|
|
distance = Distance(cubeProbe->origin, position);
|
|
if(distance < maxDistance)
|
|
{
|
|
*cubeProbeSecondNearest = *cubeProbeNearest;
|
|
maxDistance2 = maxDistance;
|
|
|
|
*cubeProbeNearest = cubeProbe;
|
|
maxDistance = distance;
|
|
}
|
|
else if(distance < maxDistance2 && distance > maxDistance)
|
|
{
|
|
*cubeProbeSecondNearest = cubeProbe;
|
|
maxDistance2 = distance;
|
|
}
|
|
}
|
|
|
|
//ri.Printf(PRINT_ALL, "iterated through %i cubeprobes\n", j);
|
|
}
|
|
|
|
void R_BuildCubeMaps(void)
|
|
{
|
|
#if 1
|
|
int i, j, k;
|
|
int ii, jj;
|
|
refdef_t rf;
|
|
qboolean flipx;
|
|
qboolean flipy;
|
|
int x, y, xy, xy2;
|
|
|
|
cubemapProbe_t *cubeProbe;
|
|
byte temp[REF_CUBEMAP_SIZE * REF_CUBEMAP_SIZE * 4];
|
|
byte *dest;
|
|
|
|
#if 0
|
|
byte *fileBuf;
|
|
char *fileName = NULL;
|
|
int fileCount = 0;
|
|
int fileBufX = 0;
|
|
int fileBufY = 0;
|
|
#endif
|
|
|
|
//
|
|
|
|
//int distance = 512;
|
|
//qboolean bad;
|
|
|
|
// srfSurfaceStatic_t *sv;
|
|
int startTime, endTime;
|
|
size_t tics = 0;
|
|
size_t nextTicCount = 0;
|
|
|
|
startTime = ri.Milliseconds();
|
|
|
|
memset(&rf, 0, sizeof(refdef_t));
|
|
|
|
for(i = 0; i < 6; i++)
|
|
{
|
|
tr.cubeTemp[i] = ri.Malloc(REF_CUBEMAP_SIZE * REF_CUBEMAP_SIZE * 4);
|
|
}
|
|
|
|
// fileBuf = ri.Z_Malloc(REF_CUBEMAP_STORE_SIZE * REF_CUBEMAP_STORE_SIZE * 4);
|
|
|
|
// calculate origins for our probes
|
|
Com_InitGrowList(&tr.cubeProbes, 4000);
|
|
tr.cubeHashTable = NewVertexHashTable();
|
|
|
|
#if 0
|
|
if(tr.world->vis)
|
|
{
|
|
bspCluster_t *cluster;
|
|
|
|
for(i = 0; i < tr.world->numClusters; i++)
|
|
{
|
|
cluster = &tr.world->clusters[i];
|
|
|
|
// check to see if this is a shit location
|
|
if(ri.CM_PointContents(cluster->origin, 0) == CONTENTS_SOLID)
|
|
continue;
|
|
|
|
if(FindVertexInHashTable(tr.cubeHashTable, cluster->origin, 256) == NULL)
|
|
{
|
|
cubeProbe = ri.Hunk_Alloc(sizeof(*cubeProbe), h_high);
|
|
Com_AddToGrowList(&tr.cubeProbes, cubeProbe);
|
|
|
|
VectorCopy(cluster->origin, cubeProbe->origin);
|
|
|
|
AddVertexToHashTable(tr.cubeHashTable, cubeProbe->origin, cubeProbe);
|
|
|
|
//gridPoint = tr.world->lightGridData + pos[0] * gridStep[0] + pos[1] * gridStep[1] + pos[2] * gridStep[2];
|
|
|
|
// TODO connect cubeProbe with gridPoint
|
|
}
|
|
}
|
|
}
|
|
#elif 1
|
|
{
|
|
bspNode_t *node;
|
|
|
|
for(i = 0; i < tr.world->numnodes; i++)
|
|
{
|
|
node = &tr.world->nodes[i];
|
|
|
|
// check to see if this is a shit location
|
|
if(node->contents == CONTENTS_NODE)
|
|
continue;
|
|
|
|
if(node->area == -1)
|
|
{
|
|
// location is in the void
|
|
continue;
|
|
}
|
|
|
|
if(FindVertexInHashTable(tr.cubeHashTable, node->origin, 256) == NULL)
|
|
{
|
|
cubeProbe = ri.Hunk_Alloc(sizeof(*cubeProbe), h_high);
|
|
Com_AddToGrowList(&tr.cubeProbes, cubeProbe);
|
|
|
|
VectorCopy(node->origin, cubeProbe->origin);
|
|
|
|
AddVertexToHashTable(tr.cubeHashTable, cubeProbe->origin, cubeProbe);
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
int numGridPoints;
|
|
bspGridPoint_t *gridPoint;
|
|
int gridStep[3];
|
|
int pos[3];
|
|
float posFloat[3];
|
|
|
|
gridStep[0] = 1;
|
|
gridStep[1] = tr.world->lightGridBounds[0];
|
|
gridStep[2] = tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1];
|
|
|
|
numGridPoints = tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1] * tr.world->lightGridBounds[2];
|
|
|
|
ri.Printf(PRINT_ALL, "...trying to allocate %d cubemaps", numGridPoints);
|
|
ri.Printf(PRINT_ALL, " with gridsize (%i %i %i)", (int)tr.world->lightGridSize[0], (int)tr.world->lightGridSize[1],
|
|
(int)tr.world->lightGridSize[2]);
|
|
ri.Printf(PRINT_ALL, " and gridbounds (%i %i %i)\n", (int)tr.world->lightGridBounds[0], (int)tr.world->lightGridBounds[1],
|
|
(int)tr.world->lightGridBounds[2]);
|
|
|
|
for(i = 0; i < tr.world->lightGridBounds[0]; i += 1)
|
|
{
|
|
for(j = 0; j < tr.world->lightGridBounds[1]; j += 1)
|
|
{
|
|
for(k = 0; k < tr.world->lightGridBounds[2]; k += 1)
|
|
{
|
|
pos[0] = i;
|
|
pos[1] = j;
|
|
pos[2] = k;
|
|
|
|
posFloat[0] = i * tr.world->lightGridSize[0];
|
|
posFloat[1] = j * tr.world->lightGridSize[1];
|
|
posFloat[2] = k * tr.world->lightGridSize[2];
|
|
|
|
VectorAdd(posFloat, tr.world->lightGridOrigin, posFloat);
|
|
|
|
// check to see if this is a shit location
|
|
if(ri.CM_PointContents(posFloat, 0) == CONTENTS_SOLID)
|
|
continue;
|
|
|
|
if(FindVertexInHashTable(tr.cubeHashTable, posFloat, 256) == NULL)
|
|
{
|
|
cubeProbe = ri.Hunk_Alloc(sizeof(*cubeProbe), h_high);
|
|
Com_AddToGrowList(&tr.cubeProbes, cubeProbe);
|
|
|
|
VectorCopy(posFloat, cubeProbe->origin);
|
|
|
|
AddVertexToHashTable(tr.cubeHashTable, posFloat, cubeProbe);
|
|
|
|
gridPoint = tr.world->lightGridData + pos[0] * gridStep[0] + pos[1] * gridStep[1] + pos[2] * gridStep[2];
|
|
|
|
// TODO connect cubeProbe with gridPoint
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// if we can't find one, fake one
|
|
if(tr.cubeProbes.currentElements == 0)
|
|
{
|
|
cubeProbe = ri.Hunk_Alloc(sizeof(*cubeProbe), h_low);
|
|
Com_AddToGrowList(&tr.cubeProbes, cubeProbe);
|
|
|
|
VectorClear(cubeProbe->origin);
|
|
}
|
|
|
|
ri.Printf(PRINT_ALL, "...pre-rendering %d cubemaps\n", tr.cubeProbes.currentElements);
|
|
ri.Cvar_Set("viewlog", "1");
|
|
ri.Printf(PRINT_ALL, "0%% 10 20 30 40 50 60 70 80 90 100%%\n");
|
|
ri.Printf(PRINT_ALL, "|----|----|----|----|----|----|----|----|----|----|\n");
|
|
for(j = 0; j < tr.cubeProbes.currentElements; j++)
|
|
{
|
|
cubeProbe = Com_GrowListElement(&tr.cubeProbes, j);
|
|
|
|
//ri.Printf(PRINT_ALL, "rendering cubemap at (%i %i %i)\n", (int)cubeProbe->origin[0], (int)cubeProbe->origin[1],
|
|
// (int)cubeProbe->origin[2]);
|
|
|
|
if((j + 1) >= nextTicCount)
|
|
{
|
|
size_t ticsNeeded = (size_t)(((double)(j + 1) / tr.cubeProbes.currentElements) * 50.0);
|
|
|
|
do
|
|
{
|
|
ri.Printf(PRINT_ALL, "*");
|
|
|
|
#if defined(COMPAT_ET)
|
|
ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
#endif
|
|
|
|
} while ( ++tics < ticsNeeded );
|
|
|
|
nextTicCount = (size_t)((tics / 50.0) * tr.cubeProbes.currentElements);
|
|
if((j + 1) == tr.cubeProbes.currentElements)
|
|
{
|
|
if(tics < 51)
|
|
ri.Printf(PRINT_ALL, "*");
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
}
|
|
}
|
|
|
|
VectorCopy(cubeProbe->origin, rf.vieworg);
|
|
|
|
AxisClear(rf.viewaxis);
|
|
|
|
rf.fov_x = 90;
|
|
rf.fov_y = 90;
|
|
rf.x = 0;
|
|
rf.y = 0;
|
|
rf.width = REF_CUBEMAP_SIZE;
|
|
rf.height = REF_CUBEMAP_SIZE;
|
|
rf.time = 0;
|
|
|
|
rf.rdflags = RDF_NOCUBEMAP | RDF_NOBLOOM;
|
|
|
|
for(i = 0; i < 6; i++)
|
|
{
|
|
flipx = qfalse;
|
|
flipy = qfalse;
|
|
switch (i)
|
|
{
|
|
|
|
case 0:
|
|
{
|
|
//X+
|
|
rf.viewaxis[0][0] = 1;
|
|
rf.viewaxis[0][1] = 0;
|
|
rf.viewaxis[0][2] = 0;
|
|
|
|
rf.viewaxis[1][0] = 0;
|
|
rf.viewaxis[1][1] = 0;
|
|
rf.viewaxis[1][2] = 1;
|
|
|
|
CrossProduct(rf.viewaxis[0], rf.viewaxis[1], rf.viewaxis[2]);
|
|
//flipx=qtrue;
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
//X-
|
|
rf.viewaxis[0][0] = -1;
|
|
rf.viewaxis[0][1] = 0;
|
|
rf.viewaxis[0][2] = 0;
|
|
|
|
rf.viewaxis[1][0] = 0;
|
|
rf.viewaxis[1][1] = 0;
|
|
rf.viewaxis[1][2] = -1;
|
|
|
|
CrossProduct(rf.viewaxis[0], rf.viewaxis[1], rf.viewaxis[2]);
|
|
//flipx=qtrue;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
//Y+
|
|
rf.viewaxis[0][0] = 0;
|
|
rf.viewaxis[0][1] = 1;
|
|
rf.viewaxis[0][2] = 0;
|
|
|
|
rf.viewaxis[1][0] = -1;
|
|
rf.viewaxis[1][1] = 0;
|
|
rf.viewaxis[1][2] = 0;
|
|
|
|
CrossProduct(rf.viewaxis[0], rf.viewaxis[1], rf.viewaxis[2]);
|
|
//flipx=qtrue;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
//Y-
|
|
rf.viewaxis[0][0] = 0;
|
|
rf.viewaxis[0][1] = -1;
|
|
rf.viewaxis[0][2] = 0;
|
|
|
|
rf.viewaxis[1][0] = -1; //-1
|
|
rf.viewaxis[1][1] = 0;
|
|
rf.viewaxis[1][2] = 0;
|
|
|
|
CrossProduct(rf.viewaxis[0], rf.viewaxis[1], rf.viewaxis[2]);
|
|
//flipx=qtrue;
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
//Z+
|
|
rf.viewaxis[0][0] = 0;
|
|
rf.viewaxis[0][1] = 0;
|
|
rf.viewaxis[0][2] = 1;
|
|
|
|
rf.viewaxis[1][0] = -1;
|
|
rf.viewaxis[1][1] = 0;
|
|
rf.viewaxis[1][2] = 0;
|
|
|
|
CrossProduct(rf.viewaxis[0], rf.viewaxis[1], rf.viewaxis[2]);
|
|
// flipx=qtrue;
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
//Z-
|
|
rf.viewaxis[0][0] = 0;
|
|
rf.viewaxis[0][1] = 0;
|
|
rf.viewaxis[0][2] = -1;
|
|
|
|
rf.viewaxis[1][0] = 1;
|
|
rf.viewaxis[1][1] = 0;
|
|
rf.viewaxis[1][2] = 0;
|
|
|
|
CrossProduct(rf.viewaxis[0], rf.viewaxis[1], rf.viewaxis[2]);
|
|
//flipx=qtrue;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
tr.refdef.pixelTarget = tr.cubeTemp[i];
|
|
memset(tr.cubeTemp[i], 255, REF_CUBEMAP_SIZE * REF_CUBEMAP_SIZE * 4);
|
|
tr.refdef.pixelTargetWidth = REF_CUBEMAP_SIZE;
|
|
tr.refdef.pixelTargetHeight = REF_CUBEMAP_SIZE;
|
|
|
|
RE_BeginFrame(STEREO_CENTER);
|
|
RE_RenderScene(&rf);
|
|
RE_EndFrame(&ii, &jj);
|
|
|
|
if(flipx)
|
|
{
|
|
dest = tr.cubeTemp[i];
|
|
memcpy(temp, dest, REF_CUBEMAP_SIZE * REF_CUBEMAP_SIZE * 4);
|
|
|
|
for(y = 0; y < REF_CUBEMAP_SIZE; y++)
|
|
{
|
|
for(x = 0; x < REF_CUBEMAP_SIZE; x++)
|
|
{
|
|
xy = ((y * REF_CUBEMAP_SIZE) + x) * 4;
|
|
xy2 = ((y * REF_CUBEMAP_SIZE) + ((REF_CUBEMAP_SIZE - 1) - x)) * 4;
|
|
dest[xy2 + 0] = temp[xy + 0];
|
|
dest[xy2 + 1] = temp[xy + 1];
|
|
dest[xy2 + 2] = temp[xy + 2];
|
|
dest[xy2 + 3] = temp[xy + 3];
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if(flipy)
|
|
{
|
|
dest = tr.cubeTemp[i];
|
|
memcpy(temp, dest, REF_CUBEMAP_SIZE * REF_CUBEMAP_SIZE * 4);
|
|
|
|
for(y = 0; y < REF_CUBEMAP_SIZE; y++)
|
|
{
|
|
for(x = 0; x < REF_CUBEMAP_SIZE; x++)
|
|
{
|
|
xy = ((y * REF_CUBEMAP_SIZE) + x) * 4;
|
|
xy2 = ((((REF_CUBEMAP_SIZE - 1) - y) * REF_CUBEMAP_SIZE) + x) * 4;
|
|
dest[xy2 + 0] = temp[xy + 0];
|
|
dest[xy2 + 1] = temp[xy + 1];
|
|
dest[xy2 + 2] = temp[xy + 2];
|
|
dest[xy2 + 3] = temp[xy + 3];
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// encode the pixel intensity into the alpha channel, saves work in the shader
|
|
if(qtrue)
|
|
{
|
|
byte r, g, b, best;
|
|
|
|
dest = tr.cubeTemp[i];
|
|
for(y = 0; y < REF_CUBEMAP_SIZE; y++)
|
|
{
|
|
for(x = 0; x < REF_CUBEMAP_SIZE; x++)
|
|
{
|
|
xy = ((y * REF_CUBEMAP_SIZE) + x) * 4;
|
|
|
|
r = dest[xy + 0];
|
|
g = dest[xy + 1];
|
|
b = dest[xy + 2];
|
|
|
|
if((r > g) && (r > b))
|
|
{
|
|
best = r;
|
|
}
|
|
else if((g > r) && (g > b))
|
|
{
|
|
best = g;
|
|
}
|
|
else
|
|
{
|
|
best = b;
|
|
}
|
|
|
|
dest[xy + 3] = best;
|
|
}
|
|
}
|
|
}
|
|
|
|
// collate cubemaps into one large image and write it out
|
|
#if 0
|
|
if(qfalse)
|
|
{
|
|
// Initialize output buffer
|
|
if(fileBufX == 0 && fileBufY == 0)
|
|
{
|
|
memset(fileBuf, 255, REF_CUBEMAP_STORE_SIZE * REF_CUBEMAP_STORE_SIZE * 4);
|
|
}
|
|
|
|
// Copy this cube map into buffer
|
|
R_SubImageCpy(fileBuf,
|
|
fileBufX * REF_CUBEMAP_SIZE, fileBufY * REF_CUBEMAP_SIZE,
|
|
REF_CUBEMAP_STORE_SIZE, REF_CUBEMAP_STORE_SIZE,
|
|
tr.cubeTemp[i],
|
|
REF_CUBEMAP_SIZE, REF_CUBEMAP_SIZE,
|
|
4, qtrue);
|
|
|
|
// Increment everything
|
|
fileBufX++;
|
|
if(fileBufX >= REF_CUBEMAP_STORE_SIDE)
|
|
{
|
|
fileBufY++;
|
|
fileBufX = 0;
|
|
}
|
|
if(fileBufY >= REF_CUBEMAP_STORE_SIDE)
|
|
{
|
|
// File is full, write it
|
|
fileName = va("maps/%s/cm_%04d.png", s_worldData.baseName, fileCount);
|
|
ri.Printf(PRINT_ALL, "\nwriting %s\n", fileName);
|
|
ri.FS_WriteFile(fileName, fileBuf, 1); // create path
|
|
SavePNG(fileName, fileBuf, REF_CUBEMAP_STORE_SIZE, REF_CUBEMAP_STORE_SIZE, 4, qfalse);
|
|
|
|
fileCount++;
|
|
fileBufY = 0;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(USE_D3D10)
|
|
// TODO
|
|
continue;
|
|
#else
|
|
// build the cubemap
|
|
//cubeProbe->cubemap = R_CreateCubeImage(va("_autoCube%d", j), (const byte **)tr.cubeTemp, REF_CUBEMAP_SIZE, REF_CUBEMAP_SIZE, IF_NOPICMIP, FT_LINEAR, WT_EDGE_CLAMP);
|
|
cubeProbe->cubemap = R_AllocImage(va("_autoCube%d", j), qfalse);
|
|
if(!cubeProbe->cubemap)
|
|
return;
|
|
|
|
cubeProbe->cubemap->type = GL_TEXTURE_CUBE_MAP_ARB;
|
|
|
|
cubeProbe->cubemap->width = REF_CUBEMAP_SIZE;
|
|
cubeProbe->cubemap->height = REF_CUBEMAP_SIZE;
|
|
|
|
cubeProbe->cubemap->bits = IF_NOPICMIP;
|
|
cubeProbe->cubemap->filterType = FT_LINEAR;
|
|
cubeProbe->cubemap->wrapType = WT_EDGE_CLAMP;
|
|
|
|
GL_Bind(cubeProbe->cubemap);
|
|
|
|
R_UploadImage((const byte **)tr.cubeTemp, 6, cubeProbe->cubemap);
|
|
|
|
glBindTexture(cubeProbe->cubemap->type, 0);
|
|
#endif
|
|
}
|
|
ri.Printf(PRINT_ALL, "\n");
|
|
|
|
#if 0
|
|
// write buffer if theres any still unwritten
|
|
if(fileBufX != 0 || fileBufY != 0)
|
|
{
|
|
fileName = va("maps/%s/cm_%04d.png", s_worldData.baseName, fileCount);
|
|
ri.Printf(PRINT_ALL, "writing %s\n", fileName);
|
|
ri.FS_WriteFile(fileName, fileBuf, 1); // create path
|
|
SavePNG(fileName, fileBuf, REF_CUBEMAP_STORE_SIZE, REF_CUBEMAP_STORE_SIZE, 4, qfalse);
|
|
}
|
|
ri.Printf(PRINT_ALL, "Wrote %d cubemaps in %d files.\n", j, fileCount+1);
|
|
ri.Free(fileBuf);
|
|
#endif
|
|
|
|
// turn pixel targets off
|
|
tr.refdef.pixelTarget = NULL;
|
|
|
|
|
|
// assign the surfs a cubemap
|
|
#if 0
|
|
for(i = 0; i < tr.world->numnodes; i++)
|
|
{
|
|
msurface_t **mark;
|
|
msurface_t *surf;
|
|
|
|
if(tr.world->nodes[i].contents != CONTENTS_SOLID)
|
|
{
|
|
mark = tr.world->nodes[i].firstmarksurface;
|
|
j = tr.world->nodes[i].nummarksurfaces;
|
|
while(j--)
|
|
{
|
|
int dist = 9999999;
|
|
int best = 0;
|
|
|
|
surf = *mark;
|
|
mark++;
|
|
sv = (void *)surf->data;
|
|
if(sv->surfaceType != SF_STATIC)
|
|
continue; //
|
|
if(sv->numIndices == 0 || sv->numVerts == 0)
|
|
continue;
|
|
if(sv->cubemap != NULL)
|
|
continue;
|
|
|
|
for(x = 0; x < tr.cubeProbesCount; x++)
|
|
{
|
|
vec3_t pos;
|
|
|
|
pos[0] = tr.cubeProbes[x].origin[0] - sv->origin[0];
|
|
pos[1] = tr.cubeProbes[x].origin[1] - sv->origin[1];
|
|
pos[2] = tr.cubeProbes[x].origin[2] - sv->origin[2];
|
|
|
|
distance = VectorLength(pos);
|
|
if(distance < dist)
|
|
{
|
|
dist = distance;
|
|
best = x;
|
|
}
|
|
}
|
|
sv->cubemap = tr.cubeProbes[best].cubemap;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
endTime = ri.Milliseconds();
|
|
ri.Printf(PRINT_ALL, "cubemap probes pre-rendering time of %i cubes = %5.2f seconds\n", tr.cubeProbes.currentElements,
|
|
(endTime - startTime) / 1000.0);
|
|
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=================
|
|
RE_LoadWorldMap
|
|
|
|
Called directly from cgame
|
|
=================
|
|
*/
|
|
void RE_LoadWorldMap(const char *name)
|
|
{
|
|
int i;
|
|
dheader_t *header;
|
|
byte *buffer;
|
|
byte *startMarker;
|
|
|
|
if(tr.worldMapLoaded)
|
|
{
|
|
ri.Error(ERR_DROP, "ERROR: attempted to redundantly load world map\n");
|
|
}
|
|
|
|
ri.Printf(PRINT_ALL, "----- RE_LoadWorldMap( %s ) -----\n", name);
|
|
|
|
// 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);
|
|
|
|
// inalidate fogs (likely to be re-initialized to new values by the current map)
|
|
// TODO:(SA)this is sort of silly. I'm going to do a general cleanup on fog stuff
|
|
// now that I can see how it's been used. (functionality can narrow since
|
|
// it's not used as much as it's designed for.)
|
|
|
|
#if defined(COMPAT_ET)
|
|
RE_SetFog(FOG_SKY, 0, 0, 0, 0, 0, 0);
|
|
RE_SetFog(FOG_PORTALVIEW, 0, 0, 0, 0, 0, 0);
|
|
RE_SetFog(FOG_HUD, 0, 0, 0, 0, 0, 0);
|
|
RE_SetFog(FOG_MAP, 0, 0, 0, 0, 0, 0);
|
|
RE_SetFog(FOG_CURRENT, 0, 0, 0, 0, 0, 0);
|
|
RE_SetFog(FOG_TARGET, 0, 0, 0, 0, 0, 0);
|
|
RE_SetFog(FOG_WATER, 0, 0, 0, 0, 0, 0);
|
|
RE_SetFog(FOG_SERVER, 0, 0, 0, 0, 0, 0);
|
|
|
|
tr.glfogNum = 0;
|
|
#endif
|
|
|
|
VectorCopy(colorMdGrey, tr.fogColor);
|
|
tr.fogDensity = 0;
|
|
|
|
// set default ambient color
|
|
tr.worldEntity.ambientLight[0] = r_forceAmbient->value;
|
|
tr.worldEntity.ambientLight[1] = r_forceAmbient->value;
|
|
tr.worldEntity.ambientLight[2] = r_forceAmbient->value;
|
|
|
|
tr.worldMapLoaded = qtrue;
|
|
|
|
// load it
|
|
ri.FS_ReadFile(name, (void **)&buffer);
|
|
if(!buffer)
|
|
{
|
|
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;
|
|
|
|
// tr.worldDeluxeMapping will be set by R_LoadEntities()
|
|
tr.worldDeluxeMapping = qfalse;
|
|
tr.worldHDR_RGBE = qfalse;
|
|
|
|
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 = ri.Hunk_Alloc(0, h_low);
|
|
|
|
header = (dheader_t *) buffer;
|
|
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
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
R_LoadEntities(&header->lumps[LUMP_ENTITIES]);
|
|
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
R_LoadShaders(&header->lumps[LUMP_SHADERS]);
|
|
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
R_LoadLightmaps(&header->lumps[LUMP_LIGHTMAPS], name);
|
|
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
R_LoadPlanes(&header->lumps[LUMP_PLANES]);
|
|
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
R_LoadSurfaces(&header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES]);
|
|
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
R_LoadMarksurfaces(&header->lumps[LUMP_LEAFSURFACES]);
|
|
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
R_LoadNodesAndLeafs(&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]);
|
|
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
R_LoadSubmodels(&header->lumps[LUMP_MODELS]);
|
|
|
|
// moved fog lump loading here, so fogs can be tagged with a model num
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
// R_LoadFogs(&header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES]);
|
|
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
R_LoadVisibility(&header->lumps[LUMP_VISIBILITY]);
|
|
|
|
// ri.Cmd_ExecuteText(EXEC_NOW, "updatescreen\n");
|
|
// R_LoadLightGrid(&header->lumps[LUMP_LIGHTGRID]);
|
|
|
|
// create static VBOS from the world
|
|
R_CreateWorldVBO();
|
|
R_CreateClusters();
|
|
R_CreateSubModelVBOs();
|
|
|
|
// we precache interactions between lights and surfaces
|
|
// to reduce the polygon count
|
|
R_PrecacheInteractions();
|
|
|
|
s_worldData.dataSize = (byte *) ri.Hunk_Alloc(0, h_low) - startMarker;
|
|
|
|
//ri.Printf(PRINT_ALL, "total world data size: %d.%02d MB\n", s_worldData.dataSize / (1024 * 1024),
|
|
// (s_worldData.dataSize % (1024 * 1024)) * 100 / (1024 * 1024));
|
|
|
|
// only set tr.world now that we know the entire level has loaded properly
|
|
tr.world = &s_worldData;
|
|
|
|
#if defined(COMPAT_ET)
|
|
// reset fog to world fog (if present)
|
|
RE_SetFog(FOG_CMD_SWITCHFOG, FOG_MAP, 20, 0, 0, 0, 0);
|
|
#endif
|
|
|
|
// make sure the VBO glState entries are save
|
|
R_BindNullVBO();
|
|
R_BindNullIBO();
|
|
|
|
//----(SA) set the sun shader if there is one
|
|
if(tr.sunShaderName)
|
|
{
|
|
tr.sunShader = R_FindShader(tr.sunShaderName, SHADER_3D_STATIC, qtrue);
|
|
}
|
|
//----(SA) end
|
|
|
|
// build cubemaps after the necessary vbo stuff is done
|
|
//R_BuildCubeMaps();
|
|
|
|
// never move this to RE_BeginFrame because we need it to set it here for the first frame
|
|
// but we need the information across 2 frames
|
|
ClearLink(&tr.traversalStack);
|
|
ClearLink(&tr.occlusionQueryQueue);
|
|
ClearLink(&tr.occlusionQueryList);
|
|
|
|
ri.FS_FreeFile(buffer);
|
|
}
|