cvar_t mod_q3bsp_lightmapmergepower = {CVAR_SAVE, "mod_q3bsp_lightmapmergepower", "4", "merges the quake3 128x128 lightmap textures into larger lightmap group textures to speed up rendering, 1 = 256x256, 2 = 512x512, 3 = 1024x1024, 4 = 2048x2048, 5 = 4096x4096, ..."};
cvar_t mod_q3bsp_nolightmaps = {CVAR_SAVE, "mod_q3bsp_nolightmaps", "0", "do not load lightmaps in Q3BSP maps (to save video RAM, but be warned: it looks ugly)"};
cvar_t mod_q3bsp_tracelineofsight_brushes = {0, "mod_q3bsp_tracelineofsight_brushes", "0", "enables culling of entities behind detail brushes, curves, etc"};
+cvar_t mod_q3bsp_sRGBlightmaps = {0, "mod_q3bsp_sRGBlightmaps", "0", "treat lightmaps from Q3 maps as sRGB when vid_sRGB is active"};
cvar_t mod_q3shader_default_offsetmapping = {CVAR_SAVE, "mod_q3shader_default_offsetmapping", "1", "use offsetmapping by default on all surfaces that are using q3 shader files"};
+cvar_t mod_q3shader_default_offsetmapping_scale = {CVAR_SAVE, "mod_q3shader_default_offsetmapping_scale", "1", "default scale used for offsetmapping"};
+cvar_t mod_q3shader_default_offsetmapping_bias = {CVAR_SAVE, "mod_q3shader_default_offsetmapping_bias", "0", "default bias used for offsetmapping"};
cvar_t mod_q3shader_default_polygonfactor = {0, "mod_q3shader_default_polygonfactor", "0", "biases depth values of 'polygonoffset' shaders to prevent z-fighting artifacts"};
cvar_t mod_q3shader_default_polygonoffset = {0, "mod_q3shader_default_polygonoffset", "-2", "biases depth values of 'polygonoffset' shaders to prevent z-fighting artifacts"};
-
+cvar_t mod_q3shader_force_addalpha = {0, "mod_q3shader_force_addalpha", "0", "treat GL_ONE GL_ONE (or add) blendfunc as GL_SRC_ALPHA GL_ONE for compatibility with older DarkPlaces releases"};
cvar_t mod_q1bsp_polygoncollisions = {0, "mod_q1bsp_polygoncollisions", "0", "disables use of precomputed cliphulls and instead collides with polygons (uses Bounding Interval Hierarchy optimizations)"};
cvar_t mod_collision_bih = {0, "mod_collision_bih", "1", "enables use of generated Bounding Interval Hierarchy tree instead of compiled bsp tree in collision code"};
cvar_t mod_recalculatenodeboxes = {0, "mod_recalculatenodeboxes", "1", "enables use of generated node bounding boxes based on BSP tree portal reconstruction, rather than the node boxes supplied by the map compiler"};
+extern cvar_t vid_sRGB;
+extern cvar_t vid_sRGB_fallback;
static texture_t mod_q1bsp_texture_solid;
static texture_t mod_q1bsp_texture_sky;
Cvar_RegisterVariable(&mod_q3bsp_debugtracebrush);
Cvar_RegisterVariable(&mod_q3bsp_lightmapmergepower);
Cvar_RegisterVariable(&mod_q3bsp_nolightmaps);
+ Cvar_RegisterVariable(&mod_q3bsp_sRGBlightmaps);
Cvar_RegisterVariable(&mod_q3bsp_tracelineofsight_brushes);
Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping);
+ Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping_scale);
+ Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping_bias);
Cvar_RegisterVariable(&mod_q3shader_default_polygonfactor);
Cvar_RegisterVariable(&mod_q3shader_default_polygonoffset);
+ Cvar_RegisterVariable(&mod_q3shader_force_addalpha);
Cvar_RegisterVariable(&mod_q1bsp_polygoncollisions);
Cvar_RegisterVariable(&mod_collision_bih);
Cvar_RegisterVariable(&mod_recalculatenodeboxes);
+ // these games were made for older DP engines and are no longer
+ // maintained; use this hack to show their textures properly
+ if(gamemode == GAME_NEXUIZ)
+ Cvar_SetQuick(&mod_q3shader_force_addalpha, "1");
+
memset(&mod_q1bsp_texture_solid, 0, sizeof(mod_q1bsp_texture_solid));
strlcpy(mod_q1bsp_texture_solid.name, "solid" , sizeof(mod_q1bsp_texture_solid.name));
mod_q1bsp_texture_solid.surfaceflags = 0;
tx->reflectfactor = 1;
Vector4Set(tx->reflectcolor4f, 1, 1, 1, 1);
tx->r_water_wateralpha = 1;
- tx->offsetmapping = OFFSETMAPPING_OFF;
+ tx->offsetmapping = OFFSETMAPPING_DEFAULT;
tx->offsetscale = 1;
+ tx->offsetbias = 0;
tx->specularscalemod = 1;
tx->specularpowermod = 1;
}
{
tx->supercontents = mod_q1bsp_texture_sky.supercontents;
tx->surfaceflags = mod_q1bsp_texture_sky.surfaceflags;
+ tx->supercontents |= SUPERCONTENTS_SOLID; // for the surface traceline we need to hit this surface as a solid...
}
else
{
skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va("textures/%s/%s", mapname, tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS, false);
if (!skinframe)
skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va("textures/%s", tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS, false);
+ if (skinframe)
+ tx->offsetmapping = OFFSETMAPPING_DEFAULT; // allow offsetmapping on external textures without a q3 shader
if (!skinframe)
{
// did not find external texture, load it from the bsp or wad3
int i, j, k;
if (!data)
return;
- if (!COM_ParseToken_Simple(&data, false, false))
+ if (!COM_ParseToken_Simple(&data, false, false, true))
return; // error
if (com_token[0] != '{')
return; // error
while (1)
{
- if (!COM_ParseToken_Simple(&data, false, false))
+ if (!COM_ParseToken_Simple(&data, false, false, true))
return; // error
if (com_token[0] == '}')
break; // end of worldspawn
strlcpy(key, com_token, sizeof(key));
while (key[strlen(key)-1] == ' ') // remove trailing spaces
key[strlen(key)-1] = 0;
- if (!COM_ParseToken_Simple(&data, false, false))
+ if (!COM_ParseToken_Simple(&data, false, false, true))
return; // error
dpsnprintf(value, sizeof(value), "%s", com_token);
if (!strcmp("wad", key)) // for HalfLife maps
if (!maptext)
return;
text = maptext;
- if (!COM_ParseToken_Simple(&data, false, false))
+ if (!COM_ParseToken_Simple(&data, false, false, true))
return; // error
submodel = 0;
for (;;)
{
- if (!COM_ParseToken_Simple(&data, false, false))
+ if (!COM_ParseToken_Simple(&data, false, false, true))
break;
if (com_token[0] != '{')
return; // error
brushes = Mem_Alloc(loadmodel->mempool, maxbrushes * sizeof(mbrush_t));
for (;;)
{
- if (!COM_ParseToken_Simple(&data, false, false))
+ if (!COM_ParseToken_Simple(&data, false, false, true))
return; // error
if (com_token[0] == '}')
break; // end of entity
}
for (;;)
{
- if (!COM_ParseToken_Simple(&data, false, false))
+ if (!COM_ParseToken_Simple(&data, false, false, true))
return; // error
if (com_token[0] == '}')
break; // end of brush
// FIXME: support hl .map format
for (pointnum = 0;pointnum < 3;pointnum++)
{
- COM_ParseToken_Simple(&data, false, false);
+ COM_ParseToken_Simple(&data, false, false, true);
for (componentnum = 0;componentnum < 3;componentnum++)
{
- COM_ParseToken_Simple(&data, false, false);
+ COM_ParseToken_Simple(&data, false, false, true);
point[pointnum][componentnum] = atof(com_token);
}
- COM_ParseToken_Simple(&data, false, false);
+ COM_ParseToken_Simple(&data, false, false, true);
}
- COM_ParseToken_Simple(&data, false, false);
+ COM_ParseToken_Simple(&data, false, false, true);
strlcpy(facetexture, com_token, sizeof(facetexture));
- COM_ParseToken_Simple(&data, false, false);
+ COM_ParseToken_Simple(&data, false, false, true);
//scroll_s = atof(com_token);
- COM_ParseToken_Simple(&data, false, false);
+ COM_ParseToken_Simple(&data, false, false, true);
//scroll_t = atof(com_token);
- COM_ParseToken_Simple(&data, false, false);
+ COM_ParseToken_Simple(&data, false, false, true);
//rotate = atof(com_token);
- COM_ParseToken_Simple(&data, false, false);
+ COM_ParseToken_Simple(&data, false, false, true);
//scale_s = atof(com_token);
- COM_ParseToken_Simple(&data, false, false);
+ COM_ParseToken_Simple(&data, false, false, true);
//scale_t = atof(com_token);
TriangleNormal(point[0], point[1], point[2], planenormal);
VectorNormalizeDouble(planenormal);
// some Q3 maps override the lightgrid_cellsize with a worldspawn key
// VorteX: q3map2 FS-R generates tangentspace deluxemaps for q3bsp and sets 'deluxeMaps' key
loadmodel->brushq3.deluxemapping = false;
- if (data && COM_ParseToken_Simple(&data, false, false) && com_token[0] == '{')
+ if (data && COM_ParseToken_Simple(&data, false, false, true) && com_token[0] == '{')
{
while (1)
{
- if (!COM_ParseToken_Simple(&data, false, false))
+ if (!COM_ParseToken_Simple(&data, false, false, true))
break; // error
if (com_token[0] == '}')
break; // end of worldspawn
strlcpy(key, com_token, sizeof(key));
while (key[strlen(key)-1] == ' ') // remove trailing spaces
key[strlen(key)-1] = 0;
- if (!COM_ParseToken_Simple(&data, false, false))
+ if (!COM_ParseToken_Simple(&data, false, false, true))
break; // error
strlcpy(value, com_token, sizeof(value));
if (!strcasecmp("gridsize", key)) // this one is case insensitive to 100% match q3map2
loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 0] = LittleFloat(in->lightmap2f[0]);
loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 1] = LittleFloat(in->lightmap2f[1]);
// svector/tvector are calculated later in face loading
- loadmodel->brushq3.data_color4f[i * 4 + 0] = in->color4ub[0] * (1.0f / 255.0f);
- loadmodel->brushq3.data_color4f[i * 4 + 1] = in->color4ub[1] * (1.0f / 255.0f);
- loadmodel->brushq3.data_color4f[i * 4 + 2] = in->color4ub[2] * (1.0f / 255.0f);
+ if(mod_q3bsp_sRGBlightmaps.integer)
+ {
+ // if lightmaps are sRGB, vertex colors are sRGB too, so we need to linearize them
+ // note: when this is in use, lightmap color 128 is no longer neutral, but "sRGB half power" is
+ // working like this may be odd, but matches q3map2 -gamma 2.2
+ if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D)
+ {
+ // actually we do: Image_sRGBFloatFromLinear_Lightmap(Image_LinearFloatFromsRGBFloat(x))
+ // neutral point is at Image_sRGBFloatFromLinearFloat(0.5)
+ // so we need to map Image_sRGBFloatFromLinearFloat(0.5) to 0.5
+ // factor is 0.5 / Image_sRGBFloatFromLinearFloat(0.5)
+ loadmodel->brushq3.data_color4f[i * 4 + 0] = in->color4ub[0] * (1.0f / 255.0f) * 0.679942f; // fixes neutral level
+ loadmodel->brushq3.data_color4f[i * 4 + 1] = in->color4ub[1] * (1.0f / 255.0f) * 0.679942f; // fixes neutral level
+ loadmodel->brushq3.data_color4f[i * 4 + 2] = in->color4ub[2] * (1.0f / 255.0f) * 0.679942f; // fixes neutral level
+ }
+ else
+ {
+ loadmodel->brushq3.data_color4f[i * 4 + 0] = Image_LinearFloatFromsRGB(in->color4ub[0]);
+ loadmodel->brushq3.data_color4f[i * 4 + 1] = Image_LinearFloatFromsRGB(in->color4ub[1]);
+ loadmodel->brushq3.data_color4f[i * 4 + 2] = Image_LinearFloatFromsRGB(in->color4ub[2]);
+ }
+ }
+ else
+ {
+ if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D)
+ {
+ loadmodel->brushq3.data_color4f[i * 4 + 0] = Image_sRGBFloatFromLinear_Lightmap(in->color4ub[0]);
+ loadmodel->brushq3.data_color4f[i * 4 + 1] = Image_sRGBFloatFromLinear_Lightmap(in->color4ub[1]);
+ loadmodel->brushq3.data_color4f[i * 4 + 2] = Image_sRGBFloatFromLinear_Lightmap(in->color4ub[2]);
+ }
+ else
+ {
+ loadmodel->brushq3.data_color4f[i * 4 + 0] = in->color4ub[0] * (1.0f / 255.0f);
+ loadmodel->brushq3.data_color4f[i * 4 + 1] = in->color4ub[1] * (1.0f / 255.0f);
+ loadmodel->brushq3.data_color4f[i * 4 + 2] = in->color4ub[2] * (1.0f / 255.0f);
+ }
+ }
loadmodel->brushq3.data_color4f[i * 4 + 3] = in->color4ub[3] * (1.0f / 255.0f);
if(in->color4ub[0] != 255 || in->color4ub[1] != 255 || in->color4ub[2] != 255)
loadmodel->lit = true;
mergebuf = (loadmodel->brushq3.deluxemapping && (i & 1)) ? mergeddeluxepixels : mergedpixels;
mergebuf += 4 * (realindex & (mergedcolumns-1))*size + 4 * ((realindex >> powerx) & (mergedrows-1))*mergedwidth*size;
if ((i & 1) == 0 || !loadmodel->brushq3.deluxemapping)
- Con_Printf("copying original lightmap %i (%ix%i) to %i (at %i,%i)\n", i, size, size, lightmapindex, (realindex & (mergedcolumns-1))*size, ((realindex >> powerx) & (mergedrows-1))*size);
+ Con_DPrintf("copying original lightmap %i (%ix%i) to %i (at %i,%i)\n", i, size, size, lightmapindex, (realindex & (mergedcolumns-1))*size, ((realindex >> powerx) & (mergedrows-1))*size);
// convert pixels from RGB or BGRA while copying them into the destination rectangle
for (j = 0;j < size;j++)
if (loadmodel->brushq3.deluxemapping && (i & 1))
loadmodel->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("deluxemap%04i", lightmapindex), mergedwidth, mergedheight, mergeddeluxepixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | (gl_texturecompression_q3bspdeluxemaps.integer ? TEXF_COMPRESS : 0), -1, NULL);
else
- loadmodel->brushq3.data_lightmaps [lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("lightmap%04i", lightmapindex), mergedwidth, mergedheight, mergedpixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), -1, NULL);
+ {
+ if(mod_q3bsp_sRGBlightmaps.integer)
+ {
+ textype_t t;
+ if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D)
+ {
+ // TODO (should we do this, or should we instead knowingly render brighter in sRGB fallback mode?)
+ int n = mergedwidth * mergedheight * 4;
+ int i;
+ for(i = 0; i < n; i += 4)
+ {
+ // actually we do: Image_sRGBFloatFromLinear_Lightmap(Image_LinearFloatFromsRGBFloat(x))
+ // neutral point is at Image_sRGBFloatFromLinearFloat(0.5)
+ // so we need to map Image_sRGBFloatFromLinearFloat(0.5) to 0.5
+ // factor is 0.5 / Image_sRGBFloatFromLinearFloat(0.5)
+ mergedpixels[i+0] = (mergedpixels[i+0] * (int)173 + 128) / 255;
+ mergedpixels[i+1] = (mergedpixels[i+1] * (int)173 + 128) / 255;
+ mergedpixels[i+2] = (mergedpixels[i+2] * (int)173 + 128) / 255;
+ }
+ t = TEXTYPE_BGRA; // in stupid fallback mode, we upload lightmaps in sRGB form and just fix their brightness
+ }
+ else
+ t = TEXTYPE_SRGB_BGRA; // normally, we upload lightmaps in sRGB form (possibly downconverted to linear)
+ loadmodel->brushq3.data_lightmaps [lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("lightmap%04i", lightmapindex), mergedwidth, mergedheight, mergedpixels, t, TEXF_FORCELINEAR | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), -1, NULL);
+ }
+ else
+ {
+ if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D)
+ Image_MakesRGBColorsFromLinear_Lightmap(mergedpixels, mergedpixels, mergedwidth * mergedheight);
+ loadmodel->brushq3.data_lightmaps [lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("lightmap%04i", lightmapindex), mergedwidth, mergedheight, mergedpixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), -1, NULL);
+ }
+ }
}
}
q3dlightgrid_t *in;
q3dlightgrid_t *out;
int count;
+ int i;
in = (q3dlightgrid_t *)(mod_base + l->fileofs);
if (l->filelen % sizeof(*in))
loadmodel->brushq3.num_lightgrid = count;
// no swapping or validation necessary
memcpy(out, in, count * (int)sizeof(*out));
+
+ if(mod_q3bsp_sRGBlightmaps.integer)
+ {
+ if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D)
+ {
+ // TODO (should we do this, or should we instead knowingly render brighter in sRGB fallback mode?)
+ for(i = 0; i < count; ++i)
+ {
+ // actually we do: Image_sRGBFloatFromLinear_Lightmap(Image_LinearFloatFromsRGBFloat(x))
+ // neutral point is at Image_sRGBFloatFromLinearFloat(0.5)
+ // so we need to map Image_sRGBFloatFromLinearFloat(0.5) to 0.5
+ // factor is 0.5 / Image_sRGBFloatFromLinearFloat(0.5)
+ out[i].ambientrgb[0] = (out[i].ambientrgb[0] * (int)173 + 128) / 255; // fixes neutral level
+ out[i].ambientrgb[1] = (out[i].ambientrgb[1] * (int)173 + 128) / 255; // fixes neutral level
+ out[i].ambientrgb[2] = (out[i].ambientrgb[2] * (int)173 + 128) / 255; // fixes neutral level
+ out[i].diffusergb[0] = (out[i].diffusergb[0] * (int)173 + 128) / 255; // fixes neutral level
+ out[i].diffusergb[1] = (out[i].diffusergb[1] * (int)173 + 128) / 255; // fixes neutral level
+ out[i].diffusergb[2] = (out[i].diffusergb[2] * (int)173 + 128) / 255; // fixes neutral level
+ }
+ }
+ else
+ {
+ for(i = 0; i < count; ++i)
+ {
+ out[i].ambientrgb[0] = floor(Image_LinearFloatFromsRGB(out[i].ambientrgb[0]) * 255.0f + 0.5f);
+ out[i].ambientrgb[1] = floor(Image_LinearFloatFromsRGB(out[i].ambientrgb[1]) * 255.0f + 0.5f);
+ out[i].ambientrgb[2] = floor(Image_LinearFloatFromsRGB(out[i].ambientrgb[2]) * 255.0f + 0.5f);
+ out[i].diffusergb[0] = floor(Image_LinearFloatFromsRGB(out[i].diffusergb[0]) * 255.0f + 0.5f);
+ out[i].diffusergb[1] = floor(Image_LinearFloatFromsRGB(out[i].diffusergb[1]) * 255.0f + 0.5f);
+ out[i].diffusergb[2] = floor(Image_LinearFloatFromsRGB(out[i].diffusergb[2]) * 255.0f + 0.5f);
+ }
+ }
+ }
+ else
+ {
+ if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D)
+ {
+ for(i = 0; i < count; ++i)
+ {
+ out[i].ambientrgb[0] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].ambientrgb[0]) * 255.0f + 0.5f);
+ out[i].ambientrgb[1] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].ambientrgb[1]) * 255.0f + 0.5f);
+ out[i].ambientrgb[2] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].ambientrgb[2]) * 255.0f + 0.5f);
+ out[i].diffusergb[0] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].diffusergb[0]) * 255.0f + 0.5f);
+ out[i].diffusergb[1] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].diffusergb[1]) * 255.0f + 0.5f);
+ out[i].diffusergb[2] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].diffusergb[2]) * 255.0f + 0.5f);
+ }
+ }
+ else
+ {
+ // all is good
+ }
+ }
}
}