quakespasm/source/r_brush.c
Tyler Young 04d4e532fa Fix map crash if there is no skybox specified
+ other misc engine fixes
2023-01-15 15:20:52 -05:00

1350 lines
32 KiB
C

/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2007-2008 Kristian Duske
Copyright (C) 2010-2014 QuakeSpasm developers
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// r_brush.c: brush model rendering. renamed from r_surf.c
#include "quakedef.h"
extern cvar_t gl_fullbrights, r_drawflat, gl_overbright, r_oldwater; //johnfitz
extern cvar_t gl_zfix; // QuakeSpasm z-fighting fix
extern int gl_lightmap_format;
int lightmap_bytes;
#define BLOCK_WIDTH 128
#define BLOCK_HEIGHT 128
gltexture_t *lightmap_textures[MAX_LIGHTMAPS]; //johnfitz -- changed to an array
unsigned blocklights[BLOCK_WIDTH*BLOCK_HEIGHT*3]; //johnfitz -- was 18*18, added lit support (*3) and loosened surface extents maximum (BLOCK_WIDTH*BLOCK_HEIGHT)
typedef struct glRect_s {
unsigned char l,t,w,h;
} glRect_t;
glpoly_t *lightmap_polys[MAX_LIGHTMAPS];
qboolean lightmap_modified[MAX_LIGHTMAPS];
glRect_t lightmap_rectchange[MAX_LIGHTMAPS];
int allocated[MAX_LIGHTMAPS][BLOCK_WIDTH];
int last_lightmap_allocated; //ericw -- optimization: remember the index of the last lightmap AllocBlock stored a surf in
// the lightmap texture data needs to be kept in
// main memory so texsubimage can update properly
byte lightmaps[4*MAX_LIGHTMAPS*BLOCK_WIDTH*BLOCK_HEIGHT];
/*
===============
R_TextureAnimation -- johnfitz -- added "frame" param to eliminate use of "currententity" global
Returns the proper texture for a given time and base texture
===============
*/
texture_t *R_TextureAnimation (texture_t *base, int frame)
{
int relative;
int count;
if (frame)
if (base->alternate_anims)
base = base->alternate_anims;
if (!base->anim_total)
return base;
relative = (int)(cl.time*10) % base->anim_total;
count = 0;
while (base->anim_min > relative || base->anim_max <= relative)
{
base = base->anim_next;
if (!base)
Sys_Error ("R_TextureAnimation: broken cycle");
if (++count > 100)
Sys_Error ("R_TextureAnimation: infinite cycle");
}
return base;
}
/*
================
DrawGLPoly
================
*/
void DrawGLPoly (glpoly_t *p)
{
float *v;
int i;
#ifdef VITA
glBegin(GL_TRIANGLE_FAN);
#else
glBegin (GL_POLYGON);
#endif
v = p->verts[0];
for (i=0 ; i<p->numverts ; i++, v+= VERTEXSIZE)
{
glTexCoord2f (v[3], v[4]);
glVertex3fv (v);
}
glEnd ();
}
/*
================
DrawGLTriangleFan -- johnfitz -- like DrawGLPoly but for r_showtris
================
*/
void DrawGLTriangleFan (glpoly_t *p)
{
float *v;
int i;
glBegin (GL_TRIANGLE_FAN);
v = p->verts[0];
for (i=0 ; i<p->numverts ; i++, v+= VERTEXSIZE)
{
glVertex3fv (v);
}
glEnd ();
}
/*
=============================================================
BRUSH MODELS
=============================================================
*/
#if 0
/*
================
R_DrawSequentialPoly -- johnfitz -- rewritten
================
*/
void R_DrawSequentialPoly (msurface_t *s)
{
glpoly_t *p;
texture_t *t;
float *v;
float entalpha;
int i;
t = R_TextureAnimation (s->texinfo->texture, currententity->frame);
entalpha = ENTALPHA_DECODE(currententity->alpha);
// drawflat
if (r_drawflat_cheatsafe)
{
if ((s->flags & SURF_DRAWTURB) && r_oldwater.value)
{
for (p = s->polys->next; p; p = p->next)
{
srand((unsigned int) (uintptr_t) p);
glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0);
DrawGLPoly (p);
rs_brushpasses++;
}
return;
}
srand((unsigned int) (uintptr_t) s->polys);
glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0);
DrawGLPoly (s->polys);
rs_brushpasses++;
return;
}
// fullbright
if ((r_fullbright_cheatsafe) && !(s->flags & SURF_DRAWTILED))
{
if (entalpha < 1)
{
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1, 1, 1, entalpha);
}
if (s->flags & SURF_DRAWFENCE)
glEnable(GL_ALPHA_TEST); // Flip on alpha test
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
rs_brushpasses++;
if (s->flags & SURF_DRAWFENCE)
glDisable (GL_ALPHA_TEST); // Flip alpha test back off
if (entalpha < 1)
{
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glColor3f(1, 1, 1);
}
goto fullbrights;
}
// r_lightmap
if (r_lightmap_cheatsafe)
{
if (s->flags & SURF_DRAWTILED)
{
glDisable (GL_TEXTURE_2D);
DrawGLPoly (s->polys);
glEnable (GL_TEXTURE_2D);
rs_brushpasses++;
return;
}
R_RenderDynamicLightmaps (s);
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
if (!gl_overbright.value)
{
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor3f(0.5, 0.5, 0.5);
}
#ifdef VITA
glBegin(GL_TRIANGLE_FAN);
#else
glBegin (GL_POLYGON);
#endif
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
glTexCoord2f (v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
if (!gl_overbright.value)
{
glColor3f(1,1,1);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}
rs_brushpasses++;
return;
}
// sky poly -- skip it, already handled in gl_sky.c
if (s->flags & SURF_DRAWSKY)
return;
// water poly
if (s->flags & SURF_DRAWTURB)
{
if (currententity->alpha == ENTALPHA_DEFAULT)
entalpha = CLAMP(0.0, GL_WaterAlphaForSurface(s), 1.0);
if (entalpha < 1)
{
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1, 1, 1, entalpha);
}
if (r_oldwater.value)
{
GL_Bind (s->texinfo->texture->gltexture);
for (p = s->polys->next; p; p = p->next)
{
DrawWaterPoly (p);
rs_brushpasses++;
}
rs_brushpasses++;
}
else
{
GL_Bind (s->texinfo->texture->warpimage);
s->texinfo->texture->update_warp = true; // FIXME: one frame too late!
DrawGLPoly (s->polys);
rs_brushpasses++;
}
if (entalpha < 1)
{
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glColor3f(1, 1, 1);
}
return;
}
// missing texture
if (s->flags & SURF_NOTEXTURE)
{
if (entalpha < 1)
{
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1, 1, 1, entalpha);
}
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
rs_brushpasses++;
if (entalpha < 1)
{
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glColor3f(1, 1, 1);
}
return;
}
// lightmapped poly
if (entalpha < 1)
{
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1, 1, 1, entalpha);
}
else
glColor3f(1, 1, 1);
if (s->flags & SURF_DRAWFENCE)
glEnable(GL_ALPHA_TEST); // Flip on alpha test
if (gl_overbright.value)
{
if (gl_texture_env_combine && gl_mtexable) //case 1: texture and lightmap in one pass, overbright using texture combiners
{
GL_DisableMultitexture(); // selects TEXTURE0
GL_Bind (t->gltexture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
GL_EnableMultitexture(); // selects TEXTURE1
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
R_RenderDynamicLightmaps (s);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f);
#ifdef VITA
glBegin(GL_TRIANGLE_FAN);
#else
glBegin(GL_POLYGON);
#endif
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, v[3], v[4]);
GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
GL_DisableMultitexture ();
rs_brushpasses++;
}
else if (entalpha < 1 || (s->flags & SURF_DRAWFENCE)) //case 2: can't do multipass if entity has alpha, so just draw the texture
{
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
rs_brushpasses++;
}
else //case 3: texture in one pass, lightmap in second pass using 2x modulation blend func, fog in third pass
{
//first pass -- texture with no fog
Fog_DisableGFog ();
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
Fog_EnableGFog ();
rs_brushpasses++;
//second pass -- lightmap with black fog, modulate blended
R_RenderDynamicLightmaps (s);
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
glDepthMask (GL_FALSE);
glEnable (GL_BLEND);
glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); //2x modulate
Fog_StartAdditive ();
#ifdef VITA
glBegin(GL_TRIANGLE_FAN);
#else
glBegin (GL_POLYGON);
#endif
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
glTexCoord2f (v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
Fog_StopAdditive ();
rs_brushpasses++;
//third pass -- black geo with normal fog, additive blended
if (Fog_GetDensity() > 0)
{
glBlendFunc(GL_ONE, GL_ONE); //add
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor3f(0,0,0);
DrawGLPoly (s->polys);
glColor3f(1,1,1);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
rs_brushpasses++;
}
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable (GL_BLEND);
glDepthMask (GL_TRUE);
}
}
else
{
if (gl_mtexable) //case 4: texture and lightmap in one pass, regular modulation
{
GL_DisableMultitexture(); // selects TEXTURE0
GL_Bind (t->gltexture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
GL_EnableMultitexture(); // selects TEXTURE1
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
R_RenderDynamicLightmaps (s);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#ifdef VITA
glBegin(GL_TRIANGLE_FAN);
#else
glBegin(GL_POLYGON);
#endif
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, v[3], v[4]);
GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
GL_DisableMultitexture ();
rs_brushpasses++;
}
else if (entalpha < 1 || (s->flags & SURF_DRAWFENCE)) //case 5: can't do multipass if entity has alpha, so just draw the texture
{
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
rs_brushpasses++;
}
else //case 6: texture in one pass, lightmap in a second pass, fog in third pass
{
//first pass -- texture with no fog
Fog_DisableGFog ();
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
Fog_EnableGFog ();
rs_brushpasses++;
//second pass -- lightmap with black fog, modulate blended
R_RenderDynamicLightmaps (s);
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
glDepthMask (GL_FALSE);
glEnable (GL_BLEND);
glBlendFunc (GL_ZERO, GL_SRC_COLOR); //modulate
Fog_StartAdditive ();
#ifdef VITA
glBegin(GL_TRIANGLE_FAN);
#else
glBegin (GL_POLYGON);
#endif
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
glTexCoord2f (v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
Fog_StopAdditive ();
rs_brushpasses++;
//third pass -- black geo with normal fog, additive blended
if (Fog_GetDensity() > 0)
{
glBlendFunc(GL_ONE, GL_ONE); //add
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor3f(0,0,0);
DrawGLPoly (s->polys);
glColor3f(1,1,1);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
rs_brushpasses++;
}
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable (GL_BLEND);
glDepthMask (GL_TRUE);
}
}
if (entalpha < 1)
{
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glColor3f(1, 1, 1);
}
if (s->flags & SURF_DRAWFENCE)
glDisable (GL_ALPHA_TEST); // Flip alpha test back off
fullbrights:
if (gl_fullbrights.value && t->fullbright)
{
glDepthMask (GL_FALSE);
glEnable (GL_BLEND);
glBlendFunc (GL_ONE, GL_ONE);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor3f (entalpha, entalpha, entalpha);
GL_Bind (t->fullbright);
Fog_StartAdditive ();
DrawGLPoly (s->polys);
Fog_StopAdditive ();
glColor3f(1, 1, 1);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable (GL_BLEND);
glDepthMask (GL_TRUE);
rs_brushpasses++;
}
}
#endif
/*
=================
R_DrawBrushModel
=================
*/
void R_DrawBrushModel (entity_t *e)
{
int i, k;
msurface_t *psurf;
float dot;
mplane_t *pplane;
qmodel_t *clmodel;
if (R_CullModelForEntity(e))
return;
currententity = e;
clmodel = e->model;
VectorSubtract (r_refdef.vieworg, e->origin, modelorg);
if (e->angles[0] || e->angles[1] || e->angles[2])
{
vec3_t temp;
vec3_t forward, right, up;
VectorCopy (modelorg, temp);
AngleVectors (e->angles, forward, right, up);
modelorg[0] = DotProduct (temp, forward);
modelorg[1] = -DotProduct (temp, right);
modelorg[2] = DotProduct (temp, up);
}
psurf = &clmodel->surfaces[clmodel->firstmodelsurface];
// calculate dynamic lighting for bmodel if it's not an
// instanced model
if (clmodel->firstmodelsurface != 0 && !gl_flashblend.value)
{
for (k=0 ; k<MAX_DLIGHTS ; k++)
{
if ((cl_dlights[k].die < cl.time) ||
(!cl_dlights[k].radius))
continue;
R_MarkLights (&cl_dlights[k], k,
clmodel->nodes + clmodel->hulls[0].firstclipnode);
}
}
glPushMatrix ();
e->angles[0] = -e->angles[0]; // stupid quake bug
if (gl_zfix.value)
{
e->origin[0] -= DIST_EPSILON;
e->origin[1] -= DIST_EPSILON;
e->origin[2] -= DIST_EPSILON;
}
R_RotateForEntity (e->origin, e->angles);
if (gl_zfix.value)
{
e->origin[0] += DIST_EPSILON;
e->origin[1] += DIST_EPSILON;
e->origin[2] += DIST_EPSILON;
}
e->angles[0] = -e->angles[0]; // stupid quake bug
R_ClearTextureChains (clmodel, chain_model);
for (i=0 ; i<clmodel->nummodelsurfaces ; i++, psurf++)
{
pplane = psurf->plane;
dot = DotProduct (modelorg, pplane->normal) - pplane->dist;
if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
{
R_ChainSurface (psurf, chain_model);
rs_brushpolys++;
}
}
R_DrawTextureChains (clmodel, e, chain_model);
R_DrawTextureChains_Water (clmodel, e, chain_model);
glPopMatrix ();
}
/*
=================
R_DrawBrushModel_ShowTris -- johnfitz
=================
*/
void R_DrawBrushModel_ShowTris (entity_t *e)
{
int i;
msurface_t *psurf;
float dot;
mplane_t *pplane;
qmodel_t *clmodel;
glpoly_t *p;
if (R_CullModelForEntity(e))
return;
currententity = e;
clmodel = e->model;
VectorSubtract (r_refdef.vieworg, e->origin, modelorg);
if (e->angles[0] || e->angles[1] || e->angles[2])
{
vec3_t temp;
vec3_t forward, right, up;
VectorCopy (modelorg, temp);
AngleVectors (e->angles, forward, right, up);
modelorg[0] = DotProduct (temp, forward);
modelorg[1] = -DotProduct (temp, right);
modelorg[2] = DotProduct (temp, up);
}
psurf = &clmodel->surfaces[clmodel->firstmodelsurface];
glPushMatrix ();
e->angles[0] = -e->angles[0]; // stupid quake bug
R_RotateForEntity (e->origin, e->angles);
e->angles[0] = -e->angles[0]; // stupid quake bug
//
// draw it
//
for (i=0 ; i<clmodel->nummodelsurfaces ; i++, psurf++)
{
pplane = psurf->plane;
dot = DotProduct (modelorg, pplane->normal) - pplane->dist;
if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
{
if ((psurf->flags & SURF_DRAWTURB) && r_oldwater.value)
for (p = psurf->polys->next; p; p = p->next)
DrawGLTriangleFan (p);
else
DrawGLTriangleFan (psurf->polys);
}
}
glPopMatrix ();
}
/*
=============================================================
LIGHTMAPS
=============================================================
*/
/*
================
R_RenderDynamicLightmaps
called during rendering
================
*/
void R_RenderDynamicLightmaps (msurface_t *fa)
{
byte *base;
int maps;
glRect_t *theRect;
int smax, tmax;
if (fa->flags & SURF_DRAWTILED) //johnfitz -- not a lightmapped surface
return;
// add to lightmap chain
fa->polys->chain = lightmap_polys[fa->lightmaptexturenum];
lightmap_polys[fa->lightmaptexturenum] = fa->polys;
// check for lightmap modification
for (maps=0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++)
if (d_lightstylevalue[fa->styles[maps]] != fa->cached_light[maps])
goto dynamic;
if (fa->dlightframe == r_framecount // dynamic this frame
|| fa->cached_dlight) // dynamic previously
{
dynamic:
if (r_dynamic.value)
{
lightmap_modified[fa->lightmaptexturenum] = true;
theRect = &lightmap_rectchange[fa->lightmaptexturenum];
if (fa->light_t < theRect->t) {
if (theRect->h)
theRect->h += theRect->t - fa->light_t;
theRect->t = fa->light_t;
}
if (fa->light_s < theRect->l) {
if (theRect->w)
theRect->w += theRect->l - fa->light_s;
theRect->l = fa->light_s;
}
smax = (fa->extents[0]>>4)+1;
tmax = (fa->extents[1]>>4)+1;
if ((theRect->w + theRect->l) < (fa->light_s + smax))
theRect->w = (fa->light_s-theRect->l)+smax;
if ((theRect->h + theRect->t) < (fa->light_t + tmax))
theRect->h = (fa->light_t-theRect->t)+tmax;
base = lightmaps + fa->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT;
base += fa->light_t * BLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes;
R_BuildLightMap (fa, base, BLOCK_WIDTH*lightmap_bytes);
}
}
}
/*
========================
AllocBlock -- returns a texture number and the position inside it
========================
*/
int AllocBlock (int w, int h, int *x, int *y)
{
int i, j;
int best, best2;
int texnum;
// ericw -- rather than searching starting at lightmap 0 every time,
// start at the last lightmap we allocated a surface in.
// This makes AllocBlock much faster on large levels (can shave off 3+ seconds
// of load time on a level with 180 lightmaps), at a cost of not quite packing
// lightmaps as tightly vs. not doing this (uses ~5% more lightmaps)
for (texnum=last_lightmap_allocated ; texnum<MAX_LIGHTMAPS ; texnum++, last_lightmap_allocated++)
{
best = BLOCK_HEIGHT;
for (i=0 ; i<BLOCK_WIDTH-w ; i++)
{
best2 = 0;
for (j=0 ; j<w ; j++)
{
if (allocated[texnum][i+j] >= best)
break;
if (allocated[texnum][i+j] > best2)
best2 = allocated[texnum][i+j];
}
if (j == w)
{ // this is a valid spot
*x = i;
*y = best = best2;
}
}
if (best + h > BLOCK_HEIGHT)
continue;
for (i=0 ; i<w ; i++)
allocated[texnum][*x + i] = best + h;
return texnum;
}
Sys_Error ("AllocBlock: full");
return 0; //johnfitz -- shut up compiler
}
mvertex_t *r_pcurrentvertbase;
qmodel_t *currentmodel;
int nColinElim;
/*
========================
GL_CreateSurfaceLightmap
========================
*/
void GL_CreateSurfaceLightmap (msurface_t *surf)
{
int smax, tmax;
byte *base;
smax = (surf->extents[0]>>4)+1;
tmax = (surf->extents[1]>>4)+1;
surf->lightmaptexturenum = AllocBlock (smax, tmax, &surf->light_s, &surf->light_t);
base = lightmaps + surf->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT;
base += (surf->light_t * BLOCK_WIDTH + surf->light_s) * lightmap_bytes;
R_BuildLightMap (surf, base, BLOCK_WIDTH*lightmap_bytes);
}
/*
================
BuildSurfaceDisplayList -- called at level load time
================
*/
void BuildSurfaceDisplayList (msurface_t *fa)
{
int i, lindex, lnumverts;
medge_t *pedges, *r_pedge;
float *vec;
float s, t;
glpoly_t *poly;
// reconstruct the polygon
pedges = currentmodel->edges;
lnumverts = fa->numedges;
//
// draw texture
//
poly = (glpoly_t *) Hunk_Alloc (sizeof(glpoly_t) + (lnumverts-4) * VERTEXSIZE*sizeof(float));
poly->next = fa->polys;
fa->polys = poly;
poly->numverts = lnumverts;
for (i=0 ; i<lnumverts ; i++)
{
lindex = currentmodel->surfedges[fa->firstedge + i];
if (lindex > 0)
{
r_pedge = &pedges[lindex];
vec = r_pcurrentvertbase[r_pedge->v[0]].position;
}
else
{
r_pedge = &pedges[-lindex];
vec = r_pcurrentvertbase[r_pedge->v[1]].position;
}
s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
s /= fa->texinfo->texture->width;
t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
t /= fa->texinfo->texture->height;
VectorCopy (vec, poly->verts[i]);
poly->verts[i][3] = s;
poly->verts[i][4] = t;
//
// lightmap texture coordinates
//
s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
s -= fa->texturemins[0];
s += fa->light_s*16;
s += 8;
s /= BLOCK_WIDTH*16; //fa->texinfo->texture->width;
t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
t -= fa->texturemins[1];
t += fa->light_t*16;
t += 8;
t /= BLOCK_HEIGHT*16; //fa->texinfo->texture->height;
poly->verts[i][5] = s;
poly->verts[i][6] = t;
}
//johnfitz -- removed gl_keeptjunctions code
poly->numverts = lnumverts;
}
/*
==================
GL_BuildLightmaps -- called at level load time
Builds the lightmap texture
with all the surfaces from all brush models
==================
*/
void GL_BuildLightmaps (void)
{
char name[16];
byte *data;
int i, j;
qmodel_t *m;
memset (allocated, 0, sizeof(allocated));
last_lightmap_allocated = 0;
r_framecount = 1; // no dlightcache
//johnfitz -- null out array (the gltexture objects themselves were already freed by Mod_ClearAll)
for (i=0; i < MAX_LIGHTMAPS; i++)
lightmap_textures[i] = NULL;
//johnfitz
gl_lightmap_format = GL_RGBA;//FIXME: hardcoded for now!
switch (gl_lightmap_format)
{
case GL_RGBA:
lightmap_bytes = 4;
break;
case GL_BGRA:
lightmap_bytes = 4;
break;
default:
Sys_Error ("GL_BuildLightmaps: bad lightmap format");
}
for (j=1 ; j<MAX_MODELS ; j++)
{
m = cl.model_precache[j];
if (!m)
break;
if (m->name[0] == '*')
continue;
r_pcurrentvertbase = m->vertexes;
currentmodel = m;
for (i=0 ; i<m->numsurfaces ; i++)
{
//johnfitz -- rewritten to use SURF_DRAWTILED instead of the sky/water flags
if (m->surfaces[i].flags & SURF_DRAWTILED)
continue;
GL_CreateSurfaceLightmap (m->surfaces + i);
BuildSurfaceDisplayList (m->surfaces + i);
//johnfitz
}
}
//
// upload all lightmaps that were filled
//
for (i=0; i<MAX_LIGHTMAPS; i++)
{
if (!allocated[i][0])
break; // no more used
lightmap_modified[i] = false;
lightmap_rectchange[i].l = BLOCK_WIDTH;
lightmap_rectchange[i].t = BLOCK_HEIGHT;
lightmap_rectchange[i].w = 0;
lightmap_rectchange[i].h = 0;
//johnfitz -- use texture manager
sprintf(name, "lightmap%03i",i);
data = lightmaps+i*BLOCK_WIDTH*BLOCK_HEIGHT*lightmap_bytes;
lightmap_textures[i] = TexMgr_LoadImage (cl.worldmodel, name, BLOCK_WIDTH, BLOCK_HEIGHT,
SRC_LIGHTMAP, data, "", (src_offset_t)data, TEXPREF_LINEAR | TEXPREF_NOPICMIP);
//johnfitz
}
//johnfitz -- warn about exceeding old limits
if (i >= 64)
Con_DWarning ("%i lightmaps exceeds standard limit of 64 (max = %d).\n", i, MAX_LIGHTMAPS);
//johnfitz
}
/*
=============================================================
VBO support
=============================================================
*/
GLuint gl_bmodel_vbo = 0;
void GL_DeleteBModelVertexBuffer (void)
{
if (!(gl_vbo_able && gl_mtexable && gl_max_texture_units >= 3))
return;
GL_DeleteBuffersFunc (1, &gl_bmodel_vbo);
gl_bmodel_vbo = 0;
GL_ClearBufferBindings ();
}
/*
==================
GL_BuildBModelVertexBuffer
Deletes gl_bmodel_vbo if it already exists, then rebuilds it with all
surfaces from world + all brush models
==================
*/
void GL_BuildBModelVertexBuffer (void)
{
unsigned int numverts, varray_bytes, varray_index;
int i, j;
qmodel_t *m;
float *varray;
if (!(gl_vbo_able && gl_mtexable && gl_max_texture_units >= 3))
return;
// ask GL for a name for our VBO
GL_DeleteBuffersFunc (1, &gl_bmodel_vbo);
GL_GenBuffersFunc (1, &gl_bmodel_vbo);
// count all verts in all models
numverts = 0;
for (j=1 ; j<MAX_MODELS ; j++)
{
m = cl.model_precache[j];
if (!m || m->name[0] == '*' || m->type != mod_brush)
continue;
for (i=0 ; i<m->numsurfaces ; i++)
{
numverts += m->surfaces[i].numedges;
}
}
// build vertex array
varray_bytes = VERTEXSIZE * sizeof(float) * numverts;
varray = (float *) malloc (varray_bytes);
varray_index = 0;
for (j=1 ; j<MAX_MODELS ; j++)
{
m = cl.model_precache[j];
if (!m || m->name[0] == '*' || m->type != mod_brush)
continue;
for (i=0 ; i<m->numsurfaces ; i++)
{
msurface_t *s = &m->surfaces[i];
s->vbo_firstvert = varray_index;
memcpy (&varray[VERTEXSIZE * varray_index], s->polys->verts, VERTEXSIZE * sizeof(float) * s->numedges);
varray_index += s->numedges;
}
}
// upload to GPU
GL_BindBufferFunc (GL_ARRAY_BUFFER, gl_bmodel_vbo);
GL_BufferDataFunc (GL_ARRAY_BUFFER, varray_bytes, varray, GL_STATIC_DRAW);
free (varray);
// invalidate the cached bindings
GL_ClearBufferBindings ();
}
/*
===============
R_AddDynamicLights
===============
*/
void R_AddDynamicLights (msurface_t *surf)
{
int lnum;
int sd, td;
float dist, rad, minlight;
vec3_t impact, local;
int s, t;
int i;
int smax, tmax;
mtexinfo_t *tex;
//johnfitz -- lit support via lordhavoc
float cred, cgreen, cblue, brightness;
unsigned *bl;
//johnfitz
smax = (surf->extents[0]>>4)+1;
tmax = (surf->extents[1]>>4)+1;
tex = surf->texinfo;
for (lnum=0 ; lnum<MAX_DLIGHTS ; lnum++)
{
if (! (surf->dlightbits[lnum >> 5] & (1U << (lnum & 31))))
continue; // not lit by this light
rad = cl_dlights[lnum].radius;
dist = DotProduct (cl_dlights[lnum].origin, surf->plane->normal) -
surf->plane->dist;
rad -= fabs(dist);
minlight = cl_dlights[lnum].minlight;
if (rad < minlight)
continue;
minlight = rad - minlight;
for (i=0 ; i<3 ; i++)
{
impact[i] = cl_dlights[lnum].origin[i] -
surf->plane->normal[i]*dist;
}
local[0] = DotProduct (impact, tex->vecs[0]) + tex->vecs[0][3];
local[1] = DotProduct (impact, tex->vecs[1]) + tex->vecs[1][3];
local[0] -= surf->texturemins[0];
local[1] -= surf->texturemins[1];
//johnfitz -- lit support via lordhavoc
bl = blocklights;
cred = cl_dlights[lnum].color[0] * 256.0f;
cgreen = cl_dlights[lnum].color[1] * 256.0f;
cblue = cl_dlights[lnum].color[2] * 256.0f;
//johnfitz
for (t = 0 ; t<tmax ; t++)
{
td = local[1] - t*16;
if (td < 0)
td = -td;
for (s=0 ; s<smax ; s++)
{
sd = local[0] - s*16;
if (sd < 0)
sd = -sd;
if (sd > td)
dist = sd + (td>>1);
else
dist = td + (sd>>1);
if (dist < minlight)
//johnfitz -- lit support via lordhavoc
{
brightness = rad - dist;
bl[0] += (int) (brightness * cred);
bl[1] += (int) (brightness * cgreen);
bl[2] += (int) (brightness * cblue);
}
bl += 3;
//johnfitz
}
}
}
}
/*
===============
R_BuildLightMap -- johnfitz -- revised for lit support via lordhavoc
Combine and scale multiple lightmaps into the 8.8 format in blocklights
===============
*/
void R_BuildLightMap (msurface_t *surf, byte *dest, int stride)
{
int smax, tmax;
int r,g,b;
int i, j, size;
byte *lightmap;
unsigned scale;
int maps;
unsigned *bl;
surf->cached_dlight = (surf->dlightframe == r_framecount);
smax = (surf->extents[0]>>4)+1;
tmax = (surf->extents[1]>>4)+1;
size = smax*tmax;
lightmap = surf->samples;
if (cl.worldmodel->lightdata)
{
// clear to no light
memset (&blocklights[0], 0, size * 3 * sizeof (unsigned int)); //johnfitz -- lit support via lordhavoc
// add all the lightmaps
if (lightmap)
{
for (maps = 0 ; maps < MAXLIGHTMAPS && surf->styles[maps] != 255 ;
maps++)
{
scale = d_lightstylevalue[surf->styles[maps]];
surf->cached_light[maps] = scale; // 8.8 fraction
//johnfitz -- lit support via lordhavoc
bl = blocklights;
for (i=0 ; i<size ; i++)
{
*bl++ += *lightmap++ * scale;
*bl++ += *lightmap++ * scale;
*bl++ += *lightmap++ * scale;
}
//johnfitz
}
}
// add all the dynamic lights
if (surf->dlightframe == r_framecount)
R_AddDynamicLights (surf);
}
else
{
// set to full bright if no light data
memset (&blocklights[0], 255, size * 3 * sizeof (unsigned int)); //johnfitz -- lit support via lordhavoc
}
// bound, invert, and shift
// store:
switch (gl_lightmap_format)
{
case GL_RGBA:
stride -= smax * 4;
bl = blocklights;
for (i=0 ; i<tmax ; i++, dest += stride)
{
for (j=0 ; j<smax ; j++)
{
if (gl_overbright.value)
{
r = *bl++ >> 8;
g = *bl++ >> 8;
b = *bl++ >> 8;
}
else
{
r = *bl++ >> 7;
g = *bl++ >> 7;
b = *bl++ >> 7;
}
*dest++ = (r > 255)? 255 : r;
*dest++ = (g > 255)? 255 : g;
*dest++ = (b > 255)? 255 : b;
*dest++ = 255;
}
}
break;
case GL_BGRA:
stride -= smax * 4;
bl = blocklights;
for (i=0 ; i<tmax ; i++, dest += stride)
{
for (j=0 ; j<smax ; j++)
{
if (gl_overbright.value)
{
r = *bl++ >> 8;
g = *bl++ >> 8;
b = *bl++ >> 8;
}
else
{
r = *bl++ >> 7;
g = *bl++ >> 7;
b = *bl++ >> 7;
}
*dest++ = (b > 255)? 255 : b;
*dest++ = (g > 255)? 255 : g;
*dest++ = (r > 255)? 255 : r;
*dest++ = 255;
}
}
break;
default:
Sys_Error ("R_BuildLightMap: bad lightmap format");
}
}
/*
===============
R_UploadLightmap -- johnfitz -- uploads the modified lightmap to opengl if necessary
assumes lightmap texture is already bound
===============
*/
static void R_UploadLightmap(int lmap)
{
glRect_t *theRect;
if (!lightmap_modified[lmap])
return;
lightmap_modified[lmap] = false;
theRect = &lightmap_rectchange[lmap];
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, theRect->t, BLOCK_WIDTH, theRect->h, gl_lightmap_format,
GL_UNSIGNED_BYTE, lightmaps+(lmap* BLOCK_HEIGHT + theRect->t) *BLOCK_WIDTH*lightmap_bytes);
theRect->l = BLOCK_WIDTH;
theRect->t = BLOCK_HEIGHT;
theRect->h = 0;
theRect->w = 0;
rs_dynamiclightmaps++;
}
void R_UploadLightmaps (void)
{
int lmap;
for (lmap = 0; lmap < MAX_LIGHTMAPS; lmap++)
{
if (!lightmap_modified[lmap])
continue;
GL_Bind (lightmap_textures[lmap]);
R_UploadLightmap(lmap);
}
}
/*
================
R_RebuildAllLightmaps -- johnfitz -- called when gl_overbright gets toggled
================
*/
void R_RebuildAllLightmaps (void)
{
int i, j;
qmodel_t *mod;
msurface_t *fa;
byte *base;
if (!cl.worldmodel) // is this the correct test?
return;
//for each surface in each model, rebuild lightmap with new scale
for (i=1; i<MAX_MODELS; i++)
{
if (!(mod = cl.model_precache[i]))
continue;
fa = &mod->surfaces[mod->firstmodelsurface];
for (j=0; j<mod->nummodelsurfaces; j++, fa++)
{
if (fa->flags & SURF_DRAWTILED)
continue;
base = lightmaps + fa->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT;
base += fa->light_t * BLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes;
R_BuildLightMap (fa, base, BLOCK_WIDTH*lightmap_bytes);
}
}
//for each lightmap, upload it
for (i=0; i<MAX_LIGHTMAPS; i++)
{
if (!allocated[i][0])
break;
GL_Bind (lightmap_textures[i]);
glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, BLOCK_WIDTH, BLOCK_HEIGHT, gl_lightmap_format,
GL_UNSIGNED_BYTE, lightmaps+i*BLOCK_WIDTH*BLOCK_HEIGHT*lightmap_bytes);
}
}