forked from FWGS/Paranoia2
3988 lines
110 KiB
C++
3988 lines
110 KiB
C++
|
/***
|
||
|
*
|
||
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
|
||
|
*
|
||
|
* This product contains software technology licensed from Id
|
||
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
||
|
* All Rights Reserved.
|
||
|
*
|
||
|
****/
|
||
|
|
||
|
#include "qrad.h"
|
||
|
|
||
|
typedef struct facelist_s
|
||
|
{
|
||
|
word facenum;
|
||
|
facelist_s *next;
|
||
|
} facelist_t;
|
||
|
|
||
|
static int g_numnormals;
|
||
|
static dnormal_t g_dnormals[MAX_MAP_NORMS];
|
||
|
static int g_numvertnormals;
|
||
|
static dvertnorm_t g_vertnormals[MAX_MAP_VERTNORMS];
|
||
|
faceneighbor_t g_faceneighbor[MAX_MAP_FACES];
|
||
|
vec3_t g_face_centroids[MAX_MAP_FACES];
|
||
|
static facelist_t *g_dependentfacelights[MAX_MAP_FACES];
|
||
|
static float g_normal_blend = cos( DEG2RAD( 2.0 ));
|
||
|
edgeshare_t *g_edgeshare;
|
||
|
|
||
|
vec_t *GetTotalLight( patch_t *patch, int style )
|
||
|
{
|
||
|
for( int i = 0; i < MAXLIGHTMAPS && patch->totalstyle[i] != 255; i++ )
|
||
|
{
|
||
|
if( patch->totalstyle[i] == style )
|
||
|
return patch->totallight[i];
|
||
|
}
|
||
|
return vec3_origin;
|
||
|
}
|
||
|
|
||
|
vec_t *GetTotalDirection( patch_t *patch, int style )
|
||
|
{
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
for( int i = 0; i < MAXLIGHTMAPS && patch->totalstyle[i] != 255; i++ )
|
||
|
{
|
||
|
if( patch->totalstyle[i] == style )
|
||
|
return patch->totallight_dir[i];
|
||
|
}
|
||
|
#endif
|
||
|
return vec3_origin;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===================
|
||
|
StoreNormal
|
||
|
===================
|
||
|
*/
|
||
|
unsigned short StoreNormal( const vec3_t normal )
|
||
|
{
|
||
|
static bool showerror = false;
|
||
|
|
||
|
// search if already added
|
||
|
for( int i = 1; i < g_numnormals; i++ )
|
||
|
{
|
||
|
dnormal_t *dv = &g_dnormals[i];
|
||
|
|
||
|
if( DotProduct( dv->normal, normal ) > g_normal_blend )
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
// normals limit don't stop the compilation, just show non-fatal error
|
||
|
// we can use unsmoothed normals in this case...
|
||
|
if( g_numnormals == MAX_MAP_NORMS )
|
||
|
{
|
||
|
if( !showerror )
|
||
|
{
|
||
|
MsgDev( D_ERROR, "MAX_MAP_NORMS limit exceeded\n" );
|
||
|
showerror = true;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// put new elem
|
||
|
VectorCopy( normal, g_dnormals[i].normal );
|
||
|
|
||
|
return g_numnormals++;
|
||
|
}
|
||
|
|
||
|
// Must guarantee these faces will form a loop or a chain, otherwise will result in endless loop.
|
||
|
//
|
||
|
// e[end]/enext[endnext]
|
||
|
// *
|
||
|
// |\.
|
||
|
// |a\ fnext
|
||
|
// | \,
|
||
|
// | f \.
|
||
|
// | \.
|
||
|
// e enext
|
||
|
//
|
||
|
int AddFaceForVertexNormal( const int edgeabs, int &edgeabsnext, const int edgeend, int &edgeendnext, const dface_t *f, dface_t *&fnext, vec_t &angle )
|
||
|
{
|
||
|
int vnum = g_dedges[edgeabs].v[edgeend];
|
||
|
int iedge, iedgenext, edge, edgenext;
|
||
|
int i, e, count1, count2;
|
||
|
vec_t dot;
|
||
|
|
||
|
for( count1 = count2 = 0, i = 0; i < f->numedges; i++ )
|
||
|
{
|
||
|
e = g_dsurfedges[f->firstedge + i];
|
||
|
|
||
|
if( g_dedges[abs(e)].v[0] == g_dedges[abs(e)].v[1] )
|
||
|
continue;
|
||
|
|
||
|
if( abs(e) == edgeabs )
|
||
|
{
|
||
|
iedge = i;
|
||
|
edge = e;
|
||
|
count1++;
|
||
|
}
|
||
|
else if( g_dedges[abs(e)].v[0] == vnum || g_dedges[abs(e)].v[1] == vnum )
|
||
|
{
|
||
|
iedgenext = i;
|
||
|
edgenext = e;
|
||
|
count2++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( count1 != 1 || count2 != 1 )
|
||
|
return -1;
|
||
|
|
||
|
int vnum11, vnum12, vnum21, vnum22;
|
||
|
vec3_t vec1, vec2;
|
||
|
|
||
|
vnum11 = g_dedges[abs(edge)].v[edge > 0 ? 0 : 1];
|
||
|
vnum12 = g_dedges[abs(edge)].v[edge > 0 ? 1 : 0];
|
||
|
vnum21 = g_dedges[abs(edgenext)].v[edgenext > 0 ? 0 : 1];
|
||
|
vnum22 = g_dedges[abs(edgenext)].v[edgenext > 0 ? 1 : 0];
|
||
|
|
||
|
if( vnum == vnum12 && vnum == vnum21 && vnum != vnum11 && vnum != vnum22 )
|
||
|
{
|
||
|
VectorSubtract( g_dvertexes[vnum11].point, g_dvertexes[vnum].point, vec1 );
|
||
|
VectorSubtract( g_dvertexes[vnum22].point, g_dvertexes[vnum].point, vec2 );
|
||
|
edgeabsnext = abs( edgenext );
|
||
|
edgeendnext = edgenext > 0 ? 0 : 1;
|
||
|
}
|
||
|
else if( vnum == vnum11 && vnum == vnum22 && vnum != vnum12 && vnum != vnum21 )
|
||
|
{
|
||
|
VectorSubtract( g_dvertexes[vnum12].point, g_dvertexes[vnum].point, vec1 );
|
||
|
VectorSubtract( g_dvertexes[vnum21].point, g_dvertexes[vnum].point, vec2 );
|
||
|
edgeabsnext = abs( edgenext );
|
||
|
edgeendnext = edgenext > 0 ? 1 : 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
VectorNormalize( vec1 );
|
||
|
VectorNormalize( vec2 );
|
||
|
dot = DotProduct( vec1, vec2 );
|
||
|
dot = dot > 1 ? 1 : dot < -1 ? -1 : dot;
|
||
|
angle = acos( dot );
|
||
|
|
||
|
edgeshare_t *es = &g_edgeshare[edgeabsnext];
|
||
|
|
||
|
if(!( es->faces[0] && es->faces[1] ))
|
||
|
return 1;
|
||
|
|
||
|
if( es->faces[0] == f && es->faces[1] != f )
|
||
|
fnext = es->faces[1];
|
||
|
else if( es->faces[1] == f && es->faces[0] != f )
|
||
|
fnext = es->faces[0];
|
||
|
else return -1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void TranslateWorldToTex( int facenum, matrix3x4 out )
|
||
|
{
|
||
|
vec_t lmvecs[2][4];
|
||
|
dtexinfo_t *tex;
|
||
|
const dplane_t *fp;
|
||
|
dface_t *f;
|
||
|
|
||
|
f = &g_dfaces[facenum];
|
||
|
tex = &g_texinfo[f->texinfo];
|
||
|
fp = GetPlaneFromFace( f );
|
||
|
|
||
|
LightMatrixFromTexMatrix( tex, lmvecs );
|
||
|
|
||
|
for( int i = 0; i < 3; i++ )
|
||
|
{
|
||
|
out[i][0] = lmvecs[0][i];
|
||
|
out[i][1] = lmvecs[1][i];
|
||
|
out[i][2] = fp->normal[i];
|
||
|
}
|
||
|
|
||
|
out[3][0] = lmvecs[0][3];
|
||
|
out[3][1] = lmvecs[1][3];
|
||
|
out[3][2] = -fp->dist;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
TranslateTexToTex
|
||
|
|
||
|
This function creates a matrix that can translate texture coords in face1 into texture coords in face2.
|
||
|
It keeps all points in the common edge invariant. For example, if there is a point in the edge,
|
||
|
and in the texture of face1, its (s,t) = (16,0), and in face2, its (s,t) = (128,64), then we must let matrix * (16,0,0) = (128,64,0)
|
||
|
============
|
||
|
*/
|
||
|
static bool TranslateTexToTex( int facenum1, int edgenum, int facenum2, matrix3x4 out1, matrix3x4 out2 )
|
||
|
{
|
||
|
dvertex_t *vert[2];
|
||
|
vec3_t face1_vert[2];
|
||
|
vec3_t face2_vert[2];
|
||
|
vec3_t face1_axis[2];
|
||
|
vec3_t face2_axis[2];
|
||
|
const vec3_t v_up = { 0, 0, 1 };
|
||
|
matrix3x4 edgetotex1, edgetotex2;
|
||
|
matrix3x4 worldtotex1, worldtotex2;
|
||
|
matrix3x4 inv1, inv2;
|
||
|
vec_t len1, len2;
|
||
|
dedge_t *e;
|
||
|
|
||
|
TranslateWorldToTex( facenum1, worldtotex1 );
|
||
|
TranslateWorldToTex( facenum2, worldtotex2 );
|
||
|
|
||
|
e = &g_dedges[edgenum];
|
||
|
|
||
|
for( int i = 0; i < 2; i++ )
|
||
|
{
|
||
|
vert[i] = &g_dvertexes[e->v[i]];
|
||
|
Matrix3x4_VectorTransform( worldtotex1, vert[i]->point, face1_vert[i] );
|
||
|
face1_vert[i][2] = 0.0; // don't need we are in texture space
|
||
|
Matrix3x4_VectorTransform( worldtotex2, vert[i]->point, face2_vert[i] );
|
||
|
face2_vert[i][2] = 0.0; // don't need we are in texture space
|
||
|
}
|
||
|
|
||
|
VectorSubtract( face1_vert[1], face1_vert[0], face1_axis[0] );
|
||
|
len1 = VectorLength( face1_axis[0] );
|
||
|
CrossProduct( v_up, face1_axis[0], face1_axis[1] );
|
||
|
|
||
|
if( Matrix3x4_CalcSign( worldtotex1 ) < 0.0 )
|
||
|
{
|
||
|
// the three vectors s, t, facenormal are in reverse order
|
||
|
VectorNegate( face1_axis[1], face1_axis[1] );
|
||
|
}
|
||
|
|
||
|
VectorSubtract( face2_vert[1], face2_vert[0], face2_axis[0] );
|
||
|
len2 = VectorLength( face2_axis[0] );
|
||
|
CrossProduct( v_up, face2_axis[0], face2_axis[1] );
|
||
|
|
||
|
if( Matrix3x4_CalcSign( worldtotex2 ) < 0.0 )
|
||
|
{
|
||
|
// the three vectors s, t, facenormal are in reverse order
|
||
|
VectorNegate( face2_axis[1], face2_axis[1] );
|
||
|
}
|
||
|
|
||
|
VectorCopy( face1_axis[0], edgetotex1[0] ); // / v[0][0] v[1][0] \ is a rotation (possibly with a reflection by the edge)
|
||
|
VectorCopy( face1_axis[1], edgetotex1[1] ); // \ v[0][1] v[1][1] /
|
||
|
VectorScale( v_up, len1, edgetotex1[2] ); // encode the length into the 3rd value of the matrix
|
||
|
VectorCopy( face1_vert[0], edgetotex1[3] ); // map (0,0) into the origin point
|
||
|
|
||
|
VectorCopy( face2_axis[0], edgetotex2[0] );
|
||
|
VectorCopy( face2_axis[1], edgetotex2[1] );
|
||
|
VectorScale( v_up, len2, edgetotex2[2] );
|
||
|
VectorCopy( face2_vert[0], edgetotex2[3] );
|
||
|
|
||
|
// for some reasons matrix can't be inverted
|
||
|
if( !Matrix3x4_Invert_Full( inv1, edgetotex1 ) || !Matrix3x4_Invert_Full( inv2, edgetotex2 ))
|
||
|
return false;
|
||
|
|
||
|
Matrix3x4_ConcatTransforms( out1, edgetotex2, inv1 );
|
||
|
Matrix3x4_ConcatTransforms( out2, edgetotex1, inv2 );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
BuildFaceNeighbors
|
||
|
============
|
||
|
*/
|
||
|
void BuildFaceNeighbors( void )
|
||
|
{
|
||
|
int m, j, e;
|
||
|
int facenum1;
|
||
|
int facenum2;
|
||
|
int tmpneighbor[64];
|
||
|
int numneighbors;
|
||
|
vec_t lmmins[2], lmmaxs[2];
|
||
|
vec_t mins[2], maxs[2], val;
|
||
|
float lmvecs[2][4];
|
||
|
dtexinfo_t *tex;
|
||
|
const dplane_t *dp1;
|
||
|
const dplane_t *dp2;
|
||
|
const edgeshare_t *es;
|
||
|
const dface_t *f1;
|
||
|
const dface_t *f2;
|
||
|
faceneighbor_t *fn;
|
||
|
dvertex_t *v;
|
||
|
|
||
|
// store a list of every face that uses a particular vertex
|
||
|
for( facenum1 = 0; facenum1 < g_numfaces; facenum1++ )
|
||
|
{
|
||
|
fn = &g_faceneighbor[facenum1];
|
||
|
numneighbors = 0;
|
||
|
|
||
|
f1 = &g_dfaces[facenum1];
|
||
|
dp1 = GetPlaneFromFace( f1 );
|
||
|
tex = &g_texinfo[f1->texinfo];
|
||
|
VectorCopy( dp1->normal, fn->facenormal );
|
||
|
mins[0] = lmmins[0] = mins[1] = lmmins[1] = 999999;
|
||
|
maxs[0] = lmmaxs[0] = maxs[1] = lmmaxs[1] =-999999;
|
||
|
|
||
|
LightMatrixFromTexMatrix( tex, lmvecs );
|
||
|
|
||
|
for( j = 0; j < f1->numedges; j++ )
|
||
|
{
|
||
|
e = g_dsurfedges[f1->firstedge + j];
|
||
|
|
||
|
if( e >= 0 ) v = &g_dvertexes[g_dedges[e].v[0]];
|
||
|
else v = &g_dvertexes[g_dedges[-e].v[1]];
|
||
|
|
||
|
for( m = 0; m < 2; m++ )
|
||
|
{
|
||
|
/* The following calculation is sensitive to floating-point
|
||
|
* precision. It needs to produce the same result that the
|
||
|
* light compiler does, because R_BuildLightMap uses surf->
|
||
|
* extents to know the width/height of a surface's lightmap,
|
||
|
* and incorrect rounding here manifests itself as patches
|
||
|
* of "corrupted" looking lightmaps.
|
||
|
* Most light compilers are win32 executables, so they use
|
||
|
* x87 floating point. This means the multiplies and adds
|
||
|
* are done at 80-bit precision, and the result is rounded
|
||
|
* down to 32-bits and stored in val.
|
||
|
* Adding the casts to double seems to be good enough to fix
|
||
|
* lighting glitches when Quakespasm is compiled as x86_64
|
||
|
* and using SSE2 floating-point. A potential trouble spot
|
||
|
* is the hallway at the beginning of mfxsp17. -- ericw
|
||
|
*/
|
||
|
val = ((double)v->point[0] * (double)tex->vecs[m][0]) +
|
||
|
((double)v->point[1] * (double)tex->vecs[m][1]) +
|
||
|
((double)v->point[2] * (double)tex->vecs[m][2]) +
|
||
|
(double)tex->vecs[m][3];
|
||
|
mins[m] = Q_min( val, mins[m] );
|
||
|
maxs[m] = Q_max( val, maxs[m] );
|
||
|
|
||
|
val = ((double)v->point[0] * (double)lmvecs[m][0]) +
|
||
|
((double)v->point[1] * (double)lmvecs[m][1]) +
|
||
|
((double)v->point[2] * (double)lmvecs[m][2]) +
|
||
|
(double)lmvecs[m][3];
|
||
|
lmmins[m] = Q_min( val, lmmins[m] );
|
||
|
lmmaxs[m] = Q_max( val, lmmaxs[m] );
|
||
|
}
|
||
|
|
||
|
es = &g_edgeshare[abs( e )];
|
||
|
if( !es->smooth ) continue;
|
||
|
|
||
|
f2 = es->faces[e > 0 ? 1 : 0];
|
||
|
facenum2 = f2 - g_dfaces;
|
||
|
dp2 = GetPlaneFromFace( f2 );
|
||
|
|
||
|
if( DotProduct( dp1->normal, dp2->normal ) < -NORMAL_EPSILON )
|
||
|
continue;
|
||
|
|
||
|
// look to see if we've already added this one
|
||
|
for( m = 0; m < numneighbors; m++ )
|
||
|
{
|
||
|
if( tmpneighbor[m] == facenum2 )
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( m >= numneighbors )
|
||
|
{
|
||
|
// add to neighbor list
|
||
|
tmpneighbor[m] = facenum2;
|
||
|
numneighbors++;
|
||
|
|
||
|
if( numneighbors > ARRAYSIZE( tmpneighbor ))
|
||
|
COM_FatalError( "BuildFaceNeighbors: stack overflow\n" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// calc face extents for traceline and lightmap extents for LightForPoint
|
||
|
for( j = 0; j < 2; j++ )
|
||
|
{
|
||
|
mins[j] = floor( mins[j] );
|
||
|
maxs[j] = ceil( maxs[j] );
|
||
|
|
||
|
fn->texmins[j] = mins[j];
|
||
|
fn->extents[j] = (maxs[j] - mins[j]);
|
||
|
|
||
|
if( FBitSet( tex->flags, TEX_WORLD_LUXELS ))
|
||
|
{
|
||
|
lmmins[j] = floor( lmmins[j] );
|
||
|
lmmaxs[j] = ceil( lmmaxs[j] );
|
||
|
|
||
|
fn->lightmapmins[j] = lmmins[j];
|
||
|
fn->lightextents[j] = (lmmaxs[j] - lmmins[j]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// just copy texturemins
|
||
|
fn->lightmapmins[j] = fn->texmins[j];
|
||
|
fn->lightextents[j] = fn->extents[j];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( numneighbors )
|
||
|
{
|
||
|
// copy over neighbor list
|
||
|
fn->neighbor = (int *)Mem_Alloc( numneighbors * sizeof( fn->neighbor[0] ));
|
||
|
fn->numneighbors = numneighbors;
|
||
|
|
||
|
for( m = 0; m < numneighbors; m++ )
|
||
|
{
|
||
|
fn->neighbor[m] = tmpneighbor[m];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FreeFaceNeighbors( void )
|
||
|
{
|
||
|
faceneighbor_t *fn;
|
||
|
|
||
|
for( int i = 0; i < g_numfaces; i++ )
|
||
|
{
|
||
|
fn = &g_faceneighbor[i];
|
||
|
Mem_Free( fn->neighbor );
|
||
|
fn->neighbor = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void GetPhongNormal( int facenum, const vec3_t spot, vec3_t phongnormal )
|
||
|
{
|
||
|
int j, s;
|
||
|
const dface_t *f = g_dfaces + facenum;
|
||
|
const dplane_t *p = GetPlaneFromFace( f );
|
||
|
vec3_t facenormal;
|
||
|
|
||
|
VectorCopy( p->normal, facenormal );
|
||
|
VectorCopy( facenormal, phongnormal );
|
||
|
|
||
|
if( g_smoothing_threshold != 0 )
|
||
|
{
|
||
|
// Calculate modified point normal for surface
|
||
|
// Use the edge normals iff they are defined. Bend the surface towards the edge normal(s)
|
||
|
// Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal.
|
||
|
// Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric)
|
||
|
// Better third attempt: generate the point normals for all vertices and do baricentric triangulation.
|
||
|
|
||
|
for( j = 0; j < f->numedges; j++ )
|
||
|
{
|
||
|
vec3_t p1, p2, v1, v2, vspot;
|
||
|
uint prev_edge, next_edge;
|
||
|
vec_t a1, a2, aa, bb, ab;
|
||
|
edgeshare_t *es0, *es1, *es2;
|
||
|
int e0, e1, e2;
|
||
|
vec3_t n1, n2;
|
||
|
dface_t *f2;
|
||
|
|
||
|
if( j != 0 ) prev_edge = f->firstedge + ((j + f->numedges - 1) % f->numedges);
|
||
|
else prev_edge = f->firstedge + f->numedges - 1;
|
||
|
|
||
|
if(( j + 1 ) != f->numedges ) next_edge = f->firstedge + ((j + 1) % f->numedges);
|
||
|
else next_edge = f->firstedge;
|
||
|
|
||
|
e0 = g_dsurfedges[f->firstedge + j];
|
||
|
e1 = g_dsurfedges[prev_edge];
|
||
|
e2 = g_dsurfedges[next_edge];
|
||
|
|
||
|
es0 = &g_edgeshare[abs(e0)];
|
||
|
es1 = &g_edgeshare[abs(e1)];
|
||
|
es2 = &g_edgeshare[abs(e2)];
|
||
|
|
||
|
if(( !es0->smooth || es0->coplanar ) && ( !es1->smooth || es1->coplanar ) && ( !es2->smooth || es2->coplanar ))
|
||
|
continue;
|
||
|
|
||
|
if( e0 > 0 )
|
||
|
{
|
||
|
f2 = es0->faces[1];
|
||
|
VectorCopy( g_dvertexes[g_dedges[e0].v[0]].point, p1 );
|
||
|
VectorCopy( g_dvertexes[g_dedges[e0].v[1]].point, p2 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f2 = es0->faces[0];
|
||
|
VectorCopy( g_dvertexes[g_dedges[-e0].v[1]].point, p1 );
|
||
|
VectorCopy( g_dvertexes[g_dedges[-e0].v[0]].point, p2 );
|
||
|
}
|
||
|
|
||
|
// adjust for origin-based models
|
||
|
VectorAdd( p1, g_face_offset[facenum], p1 );
|
||
|
VectorAdd( p2, g_face_offset[facenum], p2 );
|
||
|
|
||
|
for( s = 0; s < 2; s++ )
|
||
|
{
|
||
|
vec3_t s1, s2;
|
||
|
|
||
|
if( s == 0 ) VectorCopy( p1, s1 );
|
||
|
else VectorCopy( p2, s1 );
|
||
|
VectorAdd( p1, p2, s2 ); // edge center
|
||
|
VectorScale( s2, 0.5, s2 );
|
||
|
|
||
|
VectorSubtract( s1, g_face_centroids[facenum], v1 );
|
||
|
VectorSubtract( s2, g_face_centroids[facenum], v2 );
|
||
|
|
||
|
VectorSubtract( spot, g_face_centroids[facenum], vspot );
|
||
|
|
||
|
aa = DotProduct( v1, v1 );
|
||
|
bb = DotProduct( v2, v2 );
|
||
|
ab = DotProduct( v1, v2 );
|
||
|
a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab);
|
||
|
a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb;
|
||
|
|
||
|
// test center to sample vector for inclusion between center to vertex vectors (Use dot product of vectors)
|
||
|
if( a1 >= -0.01 && a2 >= -0.01 )
|
||
|
{
|
||
|
// calculate distance from edge to pos
|
||
|
vec3_t temp;
|
||
|
|
||
|
if( es0->smooth )
|
||
|
{
|
||
|
if( s == 0 ) VectorCopy( es0->vertex_normal[e0 > 0 ? 0 : 1], n1 );
|
||
|
else VectorCopy( es0->vertex_normal[e0 > 0 ? 1 : 0], n1 );
|
||
|
}
|
||
|
else if( s == 0 && es1->smooth )
|
||
|
{
|
||
|
VectorCopy( es1->vertex_normal[e1 > 0 ? 1 : 0], n1 );
|
||
|
}
|
||
|
else if( s == 1 && es2->smooth )
|
||
|
{
|
||
|
VectorCopy( es2->vertex_normal[e2 > 0 ? 0 : 1], n1 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
VectorCopy( facenormal, n1 );
|
||
|
}
|
||
|
|
||
|
if( es0->smooth )
|
||
|
VectorCopy( es0->interface_normal, n2 );
|
||
|
else VectorCopy( facenormal, n2 );
|
||
|
|
||
|
// Interpolate between the center and edge normals based on sample position
|
||
|
VectorScale( facenormal, 1.0 - a1 - a2, phongnormal );
|
||
|
VectorScale( n1, a1, temp );
|
||
|
VectorAdd( phongnormal, temp, phongnormal );
|
||
|
VectorScale( n2, a2, temp );
|
||
|
VectorAdd( phongnormal, temp, phongnormal );
|
||
|
VectorNormalize( phongnormal );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
PairEdges
|
||
|
============
|
||
|
*/
|
||
|
void PairEdges( void )
|
||
|
{
|
||
|
int edgeabs, absnext;
|
||
|
int edgeend, endnext;
|
||
|
vec3_t normal, normals, edgenormal;
|
||
|
int i, j, k, d, r, count;
|
||
|
dface_t *f, *fcur, *fnext;
|
||
|
vec_t angle, angles;
|
||
|
edgeshare_t *e;
|
||
|
|
||
|
g_edgeshare = (edgeshare_t *)Mem_Alloc( sizeof( edgeshare_t ) * g_numedges );
|
||
|
|
||
|
for( i = 0, f = g_dfaces; i < g_numfaces; i++, f++ )
|
||
|
{
|
||
|
const dtexinfo_t *tex = &g_texinfo[f->texinfo];
|
||
|
const dplane_t *fp = GetPlaneFromFace( f );
|
||
|
|
||
|
if( FBitSet( tex->flags, TEX_SPECIAL ))
|
||
|
{
|
||
|
// special textures don't have lightmaps
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for( j = 0; j < f->numedges; j++ )
|
||
|
{
|
||
|
k = g_dsurfedges[f->firstedge + j];
|
||
|
|
||
|
if( k < 0 )
|
||
|
{
|
||
|
e = &g_edgeshare[-k];
|
||
|
e->faces[1] = f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
e = &g_edgeshare[k];
|
||
|
e->faces[0] = f;
|
||
|
}
|
||
|
|
||
|
if( e->faces[0] && e->faces[1] )
|
||
|
{
|
||
|
// determine if coplanar
|
||
|
if( e->faces[0]->planenum == e->faces[1]->planenum && e->faces[0]->side == e->faces[1]->side )
|
||
|
{
|
||
|
VectorCopy( GetPlaneFromFace( e->faces[0] )->normal, e->interface_normal );
|
||
|
e->cos_normals_angle = 1.0;
|
||
|
e->coplanar = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// see if they fall into a "smoothing group" based on angle of the normals
|
||
|
vec3_t normals[2];
|
||
|
|
||
|
VectorCopy( GetPlaneFromFace( e->faces[0] )->normal, normals[0] );
|
||
|
VectorCopy( GetPlaneFromFace( e->faces[1] )->normal, normals[1] );
|
||
|
e->cos_normals_angle = DotProduct( normals[0], normals[1] );
|
||
|
|
||
|
if( e->cos_normals_angle > ( 1.0 - NORMAL_EPSILON ))
|
||
|
{
|
||
|
VectorCopy( GetPlaneFromFace( e->faces[0] )->normal, e->interface_normal );
|
||
|
e->cos_normals_angle = 1.0;
|
||
|
e->coplanar = true;
|
||
|
}
|
||
|
else if( g_smoothing_threshold > 0.0 )
|
||
|
{
|
||
|
if( e->cos_normals_angle >= g_smoothing_threshold )
|
||
|
{
|
||
|
VectorAdd( normals[0], normals[1], e->interface_normal );
|
||
|
VectorNormalize( e->interface_normal );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( !VectorIsNull( e->interface_normal ))
|
||
|
e->smooth = true;
|
||
|
|
||
|
if( e->smooth )
|
||
|
{
|
||
|
int facenum1 = e->faces[0] - g_dfaces;
|
||
|
int facenum2 = e->faces[1] - g_dfaces;
|
||
|
|
||
|
// compute the matrix in advance
|
||
|
if( !TranslateTexToTex( facenum1, abs( k ), facenum2, e->textotex[0], e->textotex[1] ))
|
||
|
{
|
||
|
VectorClear( e->interface_normal );
|
||
|
e->coplanar = false;
|
||
|
e->smooth = false;
|
||
|
MsgDev( D_WARN, "TranslateTexToTex failed on face %d and %d\n", facenum1, facenum2 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( edgeabs = 0; edgeabs < g_numedges; edgeabs++ )
|
||
|
{
|
||
|
e = &g_edgeshare[edgeabs];
|
||
|
if( !e->smooth ) continue;
|
||
|
|
||
|
VectorCopy( e->interface_normal, edgenormal );
|
||
|
|
||
|
if( g_dedges[edgeabs].v[0] != g_dedges[edgeabs].v[1] )
|
||
|
{
|
||
|
const dplane_t *p0 = GetPlaneFromFace( e->faces[0] );
|
||
|
const dplane_t *p1 = GetPlaneFromFace( e->faces[1] );
|
||
|
|
||
|
for( edgeend = 0; edgeend < 2; edgeend++ )
|
||
|
{
|
||
|
VectorClear( normals );
|
||
|
angles = 0;
|
||
|
|
||
|
for( d = 0; d < 2; d++ )
|
||
|
{
|
||
|
f = e->faces[d];
|
||
|
absnext = edgeabs;
|
||
|
endnext = edgeend;
|
||
|
count = 0;
|
||
|
fnext = f;
|
||
|
|
||
|
while( 1 )
|
||
|
{
|
||
|
fcur = fnext;
|
||
|
r = AddFaceForVertexNormal( absnext, absnext, endnext, endnext, fcur, fnext, angle );
|
||
|
count++;
|
||
|
|
||
|
if( r == -1 || count > 128 )
|
||
|
{
|
||
|
MsgDev( D_WARN, "PairEdges: face edges mislink\n" );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
VectorCopy( GetPlaneFromFace( fcur )->normal, normal );
|
||
|
|
||
|
if( DotProduct( normal, p0->normal ) <= NORMAL_EPSILON )
|
||
|
break;
|
||
|
|
||
|
if( DotProduct( normal, p1->normal ) <= NORMAL_EPSILON )
|
||
|
break;
|
||
|
|
||
|
if( DotProduct( edgenormal, normal ) + NORMAL_EPSILON < g_smoothing_threshold )
|
||
|
break;
|
||
|
|
||
|
VectorMA( normals, angle, normal, normals );
|
||
|
angles += angle;
|
||
|
|
||
|
if( r != 0 || fnext == f )
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( angles < NORMAL_EPSILON )
|
||
|
{
|
||
|
VectorCopy( edgenormal, e->vertex_normal[edgeend] );
|
||
|
MsgDev( D_WARN, "PairEdges: no valid faces\n" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
VectorNormalize( normals );
|
||
|
VectorCopy( normals, e->vertex_normal[edgeend] );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
MsgDev( D_WARN, "PairEdges: invalid edge\n" );
|
||
|
VectorCopy( edgenormal, e->vertex_normal[0] );
|
||
|
VectorCopy( edgenormal, e->vertex_normal[1] );
|
||
|
}
|
||
|
|
||
|
if( e->coplanar )
|
||
|
{
|
||
|
if( !VectorCompare( e->vertex_normal[0], e->interface_normal ))
|
||
|
e->coplanar = false;
|
||
|
|
||
|
if( !VectorCompare( e->vertex_normal[1], e->interface_normal ))
|
||
|
e->coplanar = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FreeSharedEdges( void )
|
||
|
{
|
||
|
Mem_Free( g_edgeshare );
|
||
|
g_edgeshare = NULL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================================================================
|
||
|
|
||
|
LIGHTMAP SAMPLE GENERATION
|
||
|
|
||
|
=================================================================
|
||
|
*/
|
||
|
/*
|
||
|
================
|
||
|
CalcFaceExtents
|
||
|
|
||
|
Fills in s->texmins[] and s->texsize[]
|
||
|
also sets exactmins[] and exactmaxs[]
|
||
|
================
|
||
|
*/
|
||
|
void CalcFaceExtents( lightinfo_t *l )
|
||
|
{
|
||
|
vec_t mins[2];
|
||
|
vec_t maxs[2];
|
||
|
float lmvecs[2][4];
|
||
|
int i, j, e;
|
||
|
dtexinfo_t *tex;
|
||
|
vec_t val;
|
||
|
dvertex_t *v;
|
||
|
dface_t *s;
|
||
|
|
||
|
s = l->face;
|
||
|
|
||
|
mins[0] = mins[1] = 999999;
|
||
|
maxs[0] = maxs[1] = -999999;
|
||
|
|
||
|
tex = &g_texinfo[s->texinfo];
|
||
|
|
||
|
int max_surface_extent = GetSurfaceExtent( tex );
|
||
|
int texture_step = GetTextureStep( tex );
|
||
|
|
||
|
LightMatrixFromTexMatrix( tex, lmvecs );
|
||
|
|
||
|
for( i = 0; i < s->numedges; i++ )
|
||
|
{
|
||
|
e = g_dsurfedges[s->firstedge+i];
|
||
|
if( e >= 0 ) v = g_dvertexes + g_dedges[e].v[0];
|
||
|
else v = g_dvertexes + g_dedges[-e].v[1];
|
||
|
|
||
|
for( j = 0; j < 2; j++ )
|
||
|
{
|
||
|
/* The following calculation is sensitive to floating-point
|
||
|
* precision. It needs to produce the same result that the
|
||
|
* light compiler does, because R_BuildLightMap uses surf->
|
||
|
* extents to know the width/height of a surface's lightmap,
|
||
|
* and incorrect rounding here manifests itself as patches
|
||
|
* of "corrupted" looking lightmaps.
|
||
|
* Most light compilers are win32 executables, so they use
|
||
|
* x87 floating point. This means the multiplies and adds
|
||
|
* are done at 80-bit precision, and the result is rounded
|
||
|
* down to 32-bits and stored in val.
|
||
|
* Adding the casts to double seems to be good enough to fix
|
||
|
* lighting glitches when Quakespasm is compiled as x86_64
|
||
|
* and using SSE2 floating-point. A potential trouble spot
|
||
|
* is the hallway at the beginning of mfxsp17. -- ericw
|
||
|
*/
|
||
|
val = ((double)v->point[0] * (double)lmvecs[j][0]) +
|
||
|
((double)v->point[1] * (double)lmvecs[j][1]) +
|
||
|
((double)v->point[2] * (double)lmvecs[j][2]) +
|
||
|
(double)lmvecs[j][3];
|
||
|
mins[j] = Q_min( val, mins[j] );
|
||
|
maxs[j] = Q_max( val, maxs[j] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( i = 0; i < 2; i++ )
|
||
|
{
|
||
|
l->exactmins[i] = mins[i];
|
||
|
l->exactmaxs[i] = maxs[i];
|
||
|
|
||
|
mins[i] = floor( mins[i] / texture_step );
|
||
|
maxs[i] = ceil( maxs[i] / texture_step );
|
||
|
|
||
|
l->texmins[i] = mins[i];
|
||
|
l->texsize[i] = (maxs[i] - mins[i]);
|
||
|
}
|
||
|
|
||
|
if( !FBitSet( tex->flags, TEX_SPECIAL ))
|
||
|
{
|
||
|
if( l->texsize[0] * l->texsize[1] > ( MAX_SINGLEMAP / 3 ))
|
||
|
COM_FatalError( "surface to large to map\n" );
|
||
|
|
||
|
if( l->texsize[0] > max_surface_extent )
|
||
|
MsgDev( D_ERROR, "bad surface extents %d > %d\n", l->texsize[0], max_surface_extent );
|
||
|
|
||
|
if( l->texsize[1] > max_surface_extent )
|
||
|
MsgDev( D_ERROR, "bad surface extents %d > %d\n", l->texsize[1], max_surface_extent );
|
||
|
|
||
|
if( l->texsize[0] < 0 || l->texsize[1] < 0 )
|
||
|
COM_FatalError( "negative extents\n" );
|
||
|
}
|
||
|
|
||
|
l->lmcache_density = 1;
|
||
|
l->lmcache_side = (int)ceil(( 0.5 * g_blur * l->lmcache_density - 0.5 ) * ( 1.0 - NORMAL_EPSILON ));
|
||
|
l->lmcache_offset = l->lmcache_side;
|
||
|
l->lmcachewidth = l->texsize[0] * l->lmcache_density + 1 + 2 * l->lmcache_side;
|
||
|
l->lmcacheheight = l->texsize[1] * l->lmcache_density + 1 + 2 * l->lmcache_side;
|
||
|
|
||
|
l->light = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec3_t[MAXLIGHTMAPS] ));
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
l->deluxe = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec3_t[MAXLIGHTMAPS] ));
|
||
|
l->normals = (vec3_t *)Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec3_t ));
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
l->shadow = (vec_t (*)[MAXLIGHTMAPS])Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec_t[MAXLIGHTMAPS] ));
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
CalcFaceVectors
|
||
|
|
||
|
Fills in texorg, worldtotex. and textoworld
|
||
|
================
|
||
|
*/
|
||
|
void CalcFaceVectors( lightinfo_t *l )
|
||
|
{
|
||
|
dtexinfo_t *tex;
|
||
|
vec_t distscale;
|
||
|
vec_t dist, len;
|
||
|
float lmvecs[2][4];
|
||
|
vec3_t texnormal;
|
||
|
int i, j;
|
||
|
|
||
|
tex = &g_texinfo[l->face->texinfo];
|
||
|
|
||
|
LightMatrixFromTexMatrix( tex, lmvecs );
|
||
|
|
||
|
// convert from float to double
|
||
|
for( i = 0; i < 2; i++ )
|
||
|
{
|
||
|
for( j = 0; j < 3; j++ )
|
||
|
{
|
||
|
l->worldtotex[i][j] = lmvecs[i][j];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// calculate a normal to the texture axis. points can be moved along this
|
||
|
// without changing their S/T
|
||
|
CrossProduct( lmvecs[1], lmvecs[0], texnormal );
|
||
|
VectorNormalize( texnormal );
|
||
|
|
||
|
// flip it towards plane normal
|
||
|
distscale = DotProduct( texnormal, l->plane->normal );
|
||
|
if( !distscale )
|
||
|
{
|
||
|
MsgDev( D_WARN, "Texture axis perpendicular to face\n" );
|
||
|
distscale = 1.0;
|
||
|
}
|
||
|
|
||
|
if( distscale < 0 )
|
||
|
{
|
||
|
VectorNegate( texnormal, texnormal );
|
||
|
distscale = -distscale;
|
||
|
}
|
||
|
|
||
|
// distscale is the ratio of the distance along the texture normal to
|
||
|
// the distance along the plane normal
|
||
|
distscale = 1.0 / distscale;
|
||
|
|
||
|
for( i = 0; i < 2; i++ )
|
||
|
{
|
||
|
CrossProduct( l->worldtotex[!i], l->plane->normal, l->textoworld[i] );
|
||
|
len = DotProduct( l->textoworld[i], l->worldtotex[i] );
|
||
|
VectorScale( l->textoworld[i], ( 1.0 / len ), l->textoworld[i] );
|
||
|
}
|
||
|
|
||
|
// calculate texorg on the texture plane
|
||
|
VectorMA( l->texorg, -lmvecs[0][3], l->textoworld[0], l->texorg );
|
||
|
VectorMA( l->texorg, -lmvecs[1][3], l->textoworld[1], l->texorg );
|
||
|
|
||
|
// project back to the face plane
|
||
|
dist = ( DotProduct( l->texorg, l->plane->normal ) - l->plane->dist ) * distscale;
|
||
|
VectorMA( l->texorg, -dist, texnormal, l->texorg );
|
||
|
|
||
|
// compensate for org'd bmodels
|
||
|
VectorAdd( l->texorg, g_face_offset[l->surfnum], l->texorg );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
TexelSpaceToWorld
|
||
|
|
||
|
translate texel coord into world coord
|
||
|
=================
|
||
|
*/
|
||
|
void TexelSpaceToWorld( const lightinfo_t *l, vec3_t world, const vec_t s, const vec_t t )
|
||
|
{
|
||
|
VectorMA( l->texorg, s, l->textoworld[0], world );
|
||
|
VectorMA( world, t, l->textoworld[1], world );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
WorldToTexelSpace
|
||
|
|
||
|
translate world coord into texel coord
|
||
|
=================
|
||
|
*/
|
||
|
void WorldToTexelSpace( const lightinfo_t *l, const vec3_t world, vec_t &s, vec_t &t )
|
||
|
{
|
||
|
vec3_t pos;
|
||
|
|
||
|
VectorSubtract( world, l->texorg, pos );
|
||
|
s = DotProduct( pos, l->worldtotex[0] );
|
||
|
t = DotProduct( pos, l->worldtotex[1] );
|
||
|
}
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
int edgenum; // g_dedges index
|
||
|
int edgeside;
|
||
|
int nextfacenum; // where to grow
|
||
|
bool tried;
|
||
|
|
||
|
vec3_t point1; // start point
|
||
|
vec3_t point2; // end point
|
||
|
vec3_t direction; // normalized; from point1 to point2
|
||
|
|
||
|
bool noseam;
|
||
|
vec_t distance; // distance from origin
|
||
|
vec_t distancereduction;
|
||
|
vec_t flippedangle;
|
||
|
|
||
|
matrix3x4 prevtonext;
|
||
|
matrix3x4 nexttoprev;
|
||
|
vec_t ratio; // if ratio != 1, seam is unavoidable
|
||
|
} samplefragedge_t;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
dplane_t planes[4];
|
||
|
} samplefragrect_t;
|
||
|
|
||
|
typedef struct samplefrag_s
|
||
|
{
|
||
|
samplefrag_s *next; // since this is a node in a list
|
||
|
samplefrag_s *parentfrag; // where it grew from
|
||
|
samplefragedge_t *parentedge;
|
||
|
int facenum; // facenum
|
||
|
|
||
|
vec_t flippedangle; // copied from parent edge
|
||
|
bool noseam; // copied from parent edge
|
||
|
|
||
|
matrix3x4 coordtomycoord; // v[2][2] > 0, v[2][0] = v[2][1] = v[0][2] = v[1][2] = 0.0
|
||
|
matrix3x4 mycoordtocoord;
|
||
|
|
||
|
vec3_t origin; // original s,t
|
||
|
vec3_t myorigin; // relative to the texture coordinate on that face
|
||
|
samplefragrect_t rect; // original rectangle that forms the boundary
|
||
|
samplefragrect_t myrect; // relative to the texture coordinate on that face
|
||
|
|
||
|
winding_t *winding; // a fragment of the original rectangle in the texture coordinate plane;
|
||
|
// windings of different frags should not overlap
|
||
|
dplane_t windingplane; // normal = (0,0,1) or (0,0,-1); if this normal is wrong, PointInWinding() will never return true
|
||
|
winding_t *mywinding; // relative to the texture coordinate on that face
|
||
|
dplane_t mywindingplane;
|
||
|
|
||
|
int numedges; // # of candicates for the next growth
|
||
|
samplefragedge_t *edges; // candicates for the next growth
|
||
|
} samplefrag_t;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
int maxsize;
|
||
|
int size;
|
||
|
samplefrag_t *head;
|
||
|
} samplefraginfo_t;
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
ChopFrag
|
||
|
|
||
|
fill winding, windingplane, mywinding,
|
||
|
mywindingplane, numedges, edges
|
||
|
=================
|
||
|
*/
|
||
|
void ChopFrag( samplefrag_t *frag )
|
||
|
{
|
||
|
// get the shape of the fragment by clipping the face using the boundaries
|
||
|
const vec3_t v_up = { 0, 0, 1 };
|
||
|
winding_t *facewinding;
|
||
|
matrix3x4 worldtotex;
|
||
|
dface_t *f;
|
||
|
int x;
|
||
|
|
||
|
f = &g_dfaces[frag->facenum];
|
||
|
facewinding = WindingFromFace( f );
|
||
|
|
||
|
TranslateWorldToTex( frag->facenum, worldtotex );
|
||
|
frag->mywinding = AllocWinding( facewinding->numpoints );
|
||
|
frag->mywinding->numpoints = facewinding->numpoints;
|
||
|
|
||
|
// transform facewinding into texture space
|
||
|
for( x = 0; x < facewinding->numpoints; x++ )
|
||
|
{
|
||
|
Matrix3x4_VectorTransform( worldtotex, facewinding->p[x], frag->mywinding->p[x] );
|
||
|
frag->mywinding->p[x][2] = 0.0;
|
||
|
}
|
||
|
|
||
|
RemoveColinearPointsEpsilon( frag->mywinding, ON_EPSILON );
|
||
|
VectorCopy( v_up, frag->mywindingplane.normal ); // this is the same as applying the worldtotex matrix to the faceplane
|
||
|
|
||
|
if( Matrix3x4_CalcSign( worldtotex ) < 0.0 )
|
||
|
frag->mywindingplane.normal[2] *= -1.0;
|
||
|
frag->mywindingplane.dist = 0.0;
|
||
|
|
||
|
for( x = 0; x < 4 && frag->mywinding != NULL; x++ )
|
||
|
ChopWindingInPlace( &frag->mywinding, frag->myrect.planes[x].normal, frag->myrect.planes[x].dist, ON_EPSILON, false );
|
||
|
|
||
|
if( !frag->mywinding )
|
||
|
{
|
||
|
FreeWinding( facewinding );
|
||
|
return; // failed to chop
|
||
|
}
|
||
|
|
||
|
ASSERT( frag->mywinding != NULL );
|
||
|
|
||
|
frag->winding = AllocWinding( frag->mywinding->numpoints );
|
||
|
frag->winding->numpoints = frag->mywinding->numpoints;
|
||
|
|
||
|
// translate coords from current surface to another
|
||
|
for( x = 0; x < frag->mywinding->numpoints; x++ )
|
||
|
Matrix3x4_VectorTransform( frag->mycoordtocoord, frag->mywinding->p[x], frag->winding->p[x] );
|
||
|
|
||
|
RemoveColinearPointsEpsilon( frag->winding, ON_EPSILON );
|
||
|
VectorCopy( frag->mywindingplane.normal, frag->windingplane.normal );
|
||
|
|
||
|
if( Matrix3x4_CalcSign( frag->mycoordtocoord ) < 0.0 )
|
||
|
frag->windingplane.normal[2] *= -1.0;
|
||
|
frag->windingplane.dist = 0.0;
|
||
|
|
||
|
FreeWinding( facewinding );
|
||
|
|
||
|
// find the edges where the fragment can grow in the future
|
||
|
frag->edges = (samplefragedge_t *)Mem_Alloc( f->numedges * sizeof( samplefragedge_t ));
|
||
|
frag->numedges = 0;
|
||
|
|
||
|
for( int i = 0; i < f->numedges; i++ )
|
||
|
{
|
||
|
samplefragedge_t *e;
|
||
|
edgeshare_t *es;
|
||
|
dedge_t *de;
|
||
|
dvertex_t *dv1;
|
||
|
dvertex_t *dv2;
|
||
|
matrix3x4 *m1;
|
||
|
matrix3x4 *m2;
|
||
|
vec_t frac1, frac2;
|
||
|
vec_t dot0, dot1, dot2;
|
||
|
vec3_t tmp, v, normal;
|
||
|
vec_t edgelen;
|
||
|
|
||
|
e = &frag->edges[frag->numedges];
|
||
|
|
||
|
// some basic info
|
||
|
e->edgenum = abs( g_dsurfedges[f->firstedge + i] );
|
||
|
e->edgeside = (g_dsurfedges[f->firstedge + i] < 0 ? 1 : 0);
|
||
|
es = &g_edgeshare[e->edgenum];
|
||
|
if( !es->smooth ) continue;
|
||
|
|
||
|
if(( es->faces[e->edgeside] - g_dfaces ) != frag->facenum )
|
||
|
COM_FatalError( "ChopFrag: internal error\n" );
|
||
|
|
||
|
e->nextfacenum = (es->faces[!e->edgeside] - g_dfaces);
|
||
|
m1 = (matrix3x4 *)&es->textotex[e->edgeside];
|
||
|
m2 = (matrix3x4 *)&es->textotex[!e->edgeside];
|
||
|
|
||
|
if( e->nextfacenum == frag->facenum )
|
||
|
continue; // an invalid edge (usually very short)
|
||
|
e->tried = false; // because the frag hasn't been linked into the list yet
|
||
|
|
||
|
// translate the edge points from world to the texture plane of the original frag
|
||
|
// so the distances are able to be compared among edges from different frags
|
||
|
de = &g_dedges[e->edgenum];
|
||
|
dv1 = &g_dvertexes[de->v[e->edgeside]];
|
||
|
dv2 = &g_dvertexes[de->v[!e->edgeside]];
|
||
|
|
||
|
// translate to another frag
|
||
|
Matrix3x4_VectorTransform( worldtotex, dv1->point, tmp );
|
||
|
Matrix3x4_VectorTransform( frag->mycoordtocoord, tmp, e->point1 );
|
||
|
e->point1[2] = 0.0;
|
||
|
|
||
|
Matrix3x4_VectorTransform( worldtotex, dv2->point, tmp );
|
||
|
Matrix3x4_VectorTransform( frag->mycoordtocoord, tmp, e->point2 );
|
||
|
e->point2[2] = 0.0;
|
||
|
|
||
|
VectorSubtract( e->point2, e->point1, e->direction );
|
||
|
edgelen = VectorNormalize( e->direction );
|
||
|
|
||
|
if( edgelen <= ON_EPSILON )
|
||
|
continue;
|
||
|
|
||
|
// clip the edge
|
||
|
frac1 = 0;
|
||
|
frac2 = 1;
|
||
|
|
||
|
for( int x = 0; x < 4; x++ )
|
||
|
{
|
||
|
vec_t dot1 = DotProduct( e->point1, frag->rect.planes[x].normal ) - frag->rect.planes[x].dist;
|
||
|
vec_t dot2 = DotProduct( e->point2, frag->rect.planes[x].normal ) - frag->rect.planes[x].dist;
|
||
|
|
||
|
if( dot1 <= ON_EPSILON && dot2 <= ON_EPSILON )
|
||
|
{
|
||
|
frac1 = 1.0;
|
||
|
frac2 = 0.0;
|
||
|
}
|
||
|
else if( dot1 < 0.0 )
|
||
|
{
|
||
|
frac1 = Q_max( frac1, dot1 / ( dot1 - dot2 ));
|
||
|
}
|
||
|
else if( dot2 < 0.0 )
|
||
|
{
|
||
|
frac2 = Q_min( frac2, dot1 / ( dot1 - dot2 ));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( edgelen * ( frac2 - frac1 ) <= ON_EPSILON )
|
||
|
continue;
|
||
|
|
||
|
VectorMA( e->point1, edgelen * frac2, e->direction, e->point2 );
|
||
|
VectorMA( e->point1, edgelen * frac1, e->direction, e->point1 );
|
||
|
|
||
|
// calculate the distance, etc., which are used to determine its priority
|
||
|
dot0 = DotProduct( frag->origin, e->direction );
|
||
|
dot1 = DotProduct( e->point1, e->direction );
|
||
|
dot2 = DotProduct( e->point2, e->direction );
|
||
|
e->noseam = frag->noseam;
|
||
|
|
||
|
dot0 = Q_max( dot1, Q_min( dot0, dot2 ));
|
||
|
VectorMA( e->point1, dot0 - dot1, e->direction, v );
|
||
|
VectorSubtract( v, frag->origin, v );
|
||
|
e->distance = VectorLength( v );
|
||
|
CrossProduct( e->direction, frag->windingplane.normal, normal );
|
||
|
VectorNormalize( normal ); // points inward
|
||
|
e->distancereduction = DotProduct( v, normal );
|
||
|
e->flippedangle = frag->flippedangle + acos( Q_min( es->cos_normals_angle, 1.0 ));
|
||
|
|
||
|
// calculate the matrix
|
||
|
e->ratio = (*m2)[2][2];
|
||
|
|
||
|
if( e->ratio <= NORMAL_EPSILON || ( 1.0 / e->ratio ) <= NORMAL_EPSILON )
|
||
|
{
|
||
|
MsgDev( D_INFO, "TranslateTexToTex failed on face %d and %d\n", frag->facenum, e->nextfacenum );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
Matrix3x4_Copy( e->prevtonext, *m1 );
|
||
|
Matrix3x4_Copy( e->nexttoprev, *m2 );
|
||
|
|
||
|
if( fabs( e->ratio - 1.0 ) >= 0.005 )
|
||
|
e->noseam = false;
|
||
|
frag->numedges++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static samplefrag_t *GrowSingleFrag( const samplefraginfo_t *info, samplefrag_t *parent, samplefragedge_t *edge )
|
||
|
{
|
||
|
dplane_t clipplanes[MAX_POINTS_ON_WINDING];
|
||
|
int x, numclipplanes;
|
||
|
bool overlap;
|
||
|
samplefrag_t *frag;
|
||
|
|
||
|
frag = (samplefrag_t *)Mem_Alloc( sizeof( samplefrag_t ));
|
||
|
|
||
|
// some basic info
|
||
|
frag->next = NULL;
|
||
|
frag->parentfrag = parent;
|
||
|
frag->parentedge = edge;
|
||
|
frag->facenum = edge->nextfacenum;
|
||
|
frag->flippedangle = edge->flippedangle;
|
||
|
frag->noseam = edge->noseam;
|
||
|
|
||
|
// calculate the matrix
|
||
|
Matrix3x4_ConcatTransforms( frag->coordtomycoord, edge->prevtonext, parent->coordtomycoord );
|
||
|
Matrix3x4_ConcatTransforms( frag->mycoordtocoord, parent->mycoordtocoord, edge->nexttoprev );
|
||
|
|
||
|
// fill in origin
|
||
|
VectorCopy( parent->origin, frag->origin );
|
||
|
Matrix3x4_VectorTransform( frag->coordtomycoord, frag->origin, frag->myorigin );
|
||
|
|
||
|
// fill in boundaries
|
||
|
frag->rect = parent->rect;
|
||
|
|
||
|
for( x = 0; x < 4; x++ )
|
||
|
{
|
||
|
// since a plane's parameters are in the dual coordinate space,
|
||
|
// we translate the original absolute plane into this relative plane by multiplying the inverse matrix
|
||
|
Matrix3x4_TransformStandardPlane( frag->mycoordtocoord, frag->rect.planes[x].normal, frag->rect.planes[x].dist,
|
||
|
frag->myrect.planes[x].normal, &frag->myrect.planes[x].dist );
|
||
|
vec_t len = VectorLength( frag->myrect.planes[x].normal );
|
||
|
|
||
|
if( !len )
|
||
|
{
|
||
|
MsgDev( D_INFO, "couldn't translate sample boundaries on face %d\n", frag->facenum );
|
||
|
Mem_Free( frag );
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
VectorScale( frag->myrect.planes[x].normal, (1.0 / len), frag->myrect.planes[x].normal );
|
||
|
frag->myrect.planes[x].dist /= len;
|
||
|
}
|
||
|
|
||
|
// chop windings and edges
|
||
|
ChopFrag( frag );
|
||
|
|
||
|
if( !frag->winding || !frag->mywinding )
|
||
|
{
|
||
|
// empty
|
||
|
if( frag->mywinding )
|
||
|
FreeWinding( frag->mywinding );
|
||
|
if( frag->winding )
|
||
|
FreeWinding( frag->winding );
|
||
|
Mem_Free( frag->edges );
|
||
|
Mem_Free( frag );
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// do overlap test
|
||
|
numclipplanes = 0;
|
||
|
overlap = false;
|
||
|
|
||
|
for( x = 0; x < frag->winding->numpoints; x++ )
|
||
|
{
|
||
|
vec3_t v;
|
||
|
|
||
|
VectorSubtract( frag->winding->p[(x + 1) % frag->winding->numpoints], frag->winding->p[x], v );
|
||
|
CrossProduct( v, frag->windingplane.normal, clipplanes[numclipplanes].normal );
|
||
|
|
||
|
if( !VectorNormalize( clipplanes[numclipplanes].normal ))
|
||
|
continue;
|
||
|
|
||
|
clipplanes[numclipplanes].dist = DotProduct( frag->winding->p[x], clipplanes[numclipplanes].normal );
|
||
|
numclipplanes++;
|
||
|
}
|
||
|
|
||
|
for( samplefrag_t *f2 = info->head; f2 && !overlap; f2 = f2->next )
|
||
|
{
|
||
|
winding_t *w = CopyWinding( f2->winding );
|
||
|
|
||
|
for( x = 0; x < numclipplanes && w != NULL; x++ )
|
||
|
ChopWindingInPlace( &w, clipplanes[x].normal, clipplanes[x].dist, ON_EPSILON * 4, false );
|
||
|
overlap = (w) ? true : false;
|
||
|
if( w ) FreeWinding( w );
|
||
|
}
|
||
|
|
||
|
if( overlap )
|
||
|
{
|
||
|
// in the original texture plane, this fragment overlaps with some existing fragments
|
||
|
if( frag->mywinding )
|
||
|
FreeWinding( frag->mywinding );
|
||
|
if( frag->winding )
|
||
|
FreeWinding( frag->winding );
|
||
|
Mem_Free( frag->edges );
|
||
|
Mem_Free( frag );
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return frag;
|
||
|
}
|
||
|
|
||
|
static bool FindBestEdge( samplefraginfo_t *info, samplefrag_t *&bestfrag, samplefragedge_t *&bestedge )
|
||
|
{
|
||
|
bool found = false;
|
||
|
bool better;
|
||
|
samplefrag_t *f;
|
||
|
samplefragedge_t *e;
|
||
|
|
||
|
for( f = info->head; f != NULL; f = f->next )
|
||
|
{
|
||
|
for( e = f->edges; e < f->edges + f->numedges; e++ )
|
||
|
{
|
||
|
if( e->tried ) continue;
|
||
|
|
||
|
if( !found )
|
||
|
better = true;
|
||
|
else if(( e->flippedangle < M_PI + NORMAL_EPSILON ) != ( bestedge->flippedangle < M_PI + NORMAL_EPSILON ))
|
||
|
better = (( e->flippedangle < M_PI + NORMAL_EPSILON ) && !( bestedge->flippedangle < M_PI + NORMAL_EPSILON ));
|
||
|
else if( e->noseam != bestedge->noseam )
|
||
|
better = (e->noseam && !bestedge->noseam);
|
||
|
else if( fabs( e->distance - bestedge->distance ) > ON_EPSILON )
|
||
|
better = ( e->distance < bestedge->distance );
|
||
|
else if( fabs( e->distancereduction - bestedge->distancereduction ) > ON_EPSILON )
|
||
|
better = ( e->distancereduction > bestedge->distancereduction );
|
||
|
else better = e->edgenum < bestedge->edgenum;
|
||
|
|
||
|
if( better )
|
||
|
{
|
||
|
found = true;
|
||
|
bestfrag = f;
|
||
|
bestedge = e;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return found;
|
||
|
}
|
||
|
|
||
|
static samplefraginfo_t *CreateSampleFrag( int facenum, vec_t s, vec_t t, const vec_t square[2][2], int maxsize )
|
||
|
{
|
||
|
const vec3_t v_s = { 1, 0, 0 };
|
||
|
const vec3_t v_t = { 0, 1, 0 };
|
||
|
samplefraginfo_t *info;
|
||
|
|
||
|
info = (samplefraginfo_t *)Mem_Alloc( sizeof( samplefraginfo_t ));
|
||
|
info->maxsize = maxsize;
|
||
|
info->size = 1;
|
||
|
info->head = (samplefrag_t *)Mem_Alloc( sizeof( samplefrag_t ));
|
||
|
info->head->next = NULL;
|
||
|
info->head->parentfrag = NULL;
|
||
|
info->head->parentedge = NULL;
|
||
|
info->head->facenum = facenum;
|
||
|
info->head->flippedangle = 0.0;
|
||
|
info->head->noseam = true;
|
||
|
|
||
|
Matrix3x4_LoadIdentity( info->head->coordtomycoord );
|
||
|
Matrix3x4_LoadIdentity( info->head->mycoordtocoord );
|
||
|
|
||
|
VectorSet( info->head->origin, s, t, 0 );
|
||
|
VectorCopy( info->head->origin, info->head->myorigin );
|
||
|
|
||
|
VectorScale( v_s, 1.0, info->head->rect.planes[0].normal );
|
||
|
info->head->rect.planes[0].dist = square[0][0]; // smin
|
||
|
VectorScale( v_s, -1.0, info->head->rect.planes[1].normal );
|
||
|
info->head->rect.planes[1].dist = -square[1][0]; // smax
|
||
|
VectorScale( v_t, 1.0, info->head->rect.planes[2].normal );
|
||
|
info->head->rect.planes[2].dist = square[0][1]; // tmin
|
||
|
VectorScale( v_t, -1.0, info->head->rect.planes[3].normal );
|
||
|
info->head->rect.planes[3].dist = -square[1][1]; // tmax
|
||
|
|
||
|
info->head->myrect = info->head->rect;
|
||
|
ChopFrag( info->head );
|
||
|
|
||
|
if( !info->head->winding || !info->head->mywinding )
|
||
|
{
|
||
|
// empty
|
||
|
if( info->head->mywinding )
|
||
|
FreeWinding( info->head->mywinding );
|
||
|
if( info->head->winding )
|
||
|
FreeWinding( info->head->winding );
|
||
|
Mem_Free( info->head->edges );
|
||
|
Mem_Free( info->head );
|
||
|
info->head = NULL;
|
||
|
info->size = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// prune edges
|
||
|
for( samplefragedge_t *e = info->head->edges; e < info->head->edges + info->head->numedges; e++ )
|
||
|
{
|
||
|
if( e->nextfacenum == info->head->facenum )
|
||
|
e->tried = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while( info->size < info->maxsize )
|
||
|
{
|
||
|
samplefrag_t *bestfrag;
|
||
|
samplefragedge_t *e, *bestedge;
|
||
|
samplefrag_t *f, *newfrag;
|
||
|
|
||
|
if( !FindBestEdge( info, bestfrag, bestedge ))
|
||
|
break;
|
||
|
|
||
|
newfrag = GrowSingleFrag( info, bestfrag, bestedge );
|
||
|
bestedge->tried = true;
|
||
|
|
||
|
if( newfrag )
|
||
|
{
|
||
|
newfrag->next = info->head;
|
||
|
info->head = newfrag;
|
||
|
info->size++;
|
||
|
|
||
|
|
||
|
for( f = info->head; f != NULL; f = f->next )
|
||
|
{
|
||
|
for( e = newfrag->edges; e < newfrag->edges + newfrag->numedges; e++ )
|
||
|
{
|
||
|
if( e->nextfacenum == f->facenum )
|
||
|
e->tried = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( f = info->head; f != NULL; f = f->next )
|
||
|
{
|
||
|
for( e = f->edges; e < f->edges + f->numedges; e++ )
|
||
|
{
|
||
|
if( e->nextfacenum == newfrag->facenum )
|
||
|
e->tried = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return info;
|
||
|
}
|
||
|
|
||
|
static bool IsFragEmpty( samplefraginfo_t *fraginfo )
|
||
|
{
|
||
|
return (fraginfo->size == 0);
|
||
|
}
|
||
|
|
||
|
static void DeleteSampleFrag( samplefraginfo_t *fraginfo )
|
||
|
{
|
||
|
while( fraginfo->head )
|
||
|
{
|
||
|
samplefrag_t *f;
|
||
|
|
||
|
f = fraginfo->head;
|
||
|
fraginfo->head = f->next;
|
||
|
|
||
|
if( f->mywinding ) FreeWinding( f->mywinding );
|
||
|
if( f->winding ) FreeWinding( f->winding );
|
||
|
Mem_Free( f->edges );
|
||
|
Mem_Free( f );
|
||
|
}
|
||
|
Mem_Free( fraginfo );
|
||
|
}
|
||
|
|
||
|
static bool CalcSamplePoint( vec3_t point, vec3_t position, int *surface, const lightinfo_t *l, vec_t orig_s, vec_t orig_t, const vec_t square[2][2] )
|
||
|
{
|
||
|
bool found = false;
|
||
|
vec_t bests, bestt;
|
||
|
const dplane_t *faceplane;
|
||
|
samplefraginfo_t *fraginfo;
|
||
|
samplefrag_t *bestfrag;
|
||
|
vec_t best_dist;
|
||
|
vec3_t bestpos;
|
||
|
int facenum;
|
||
|
bool outside;
|
||
|
dface_t *face;
|
||
|
samplefrag_t *f;
|
||
|
|
||
|
facenum = l->surfnum;
|
||
|
face = l->face;
|
||
|
faceplane = GetPlaneFromFace( face );
|
||
|
fraginfo = CreateSampleFrag( facenum, orig_s, orig_t, square, 100 );
|
||
|
|
||
|
for( f = fraginfo->head; f != NULL; f = f->next )
|
||
|
{
|
||
|
vec_t s, t, dist;
|
||
|
bool better;
|
||
|
vec3_t pos;
|
||
|
|
||
|
if( !FindNearestPosition( f->facenum, f->mywinding, f->myorigin[0], f->myorigin[1], pos, &s, &t, &dist ))
|
||
|
continue;
|
||
|
|
||
|
if( !found )
|
||
|
better = true;
|
||
|
else if( fabs( dist - best_dist ) > ON_EPSILON * 2 )
|
||
|
better = ( dist < best_dist );
|
||
|
else if( f->noseam != bestfrag->noseam )
|
||
|
better = (f->noseam && !bestfrag->noseam);
|
||
|
else better = (f->facenum < bestfrag->facenum);
|
||
|
|
||
|
if( better )
|
||
|
{
|
||
|
VectorCopy( pos, bestpos );
|
||
|
best_dist = dist;
|
||
|
bestfrag = f;
|
||
|
found = true;
|
||
|
bests = s;
|
||
|
bestt = t;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( found )
|
||
|
{
|
||
|
matrix3x4 worldtotex, textoworld;
|
||
|
vec3_t tex;
|
||
|
|
||
|
TranslateWorldToTex( bestfrag->facenum, worldtotex );
|
||
|
|
||
|
if( !Matrix3x4_Invert_Full( textoworld, worldtotex ))
|
||
|
MsgDev( D_WARN, "Texture axis perpendicular to face\n" );
|
||
|
|
||
|
// adjust source point and store valid position
|
||
|
VectorSet( tex, bests, bestt, 0.0 );
|
||
|
Matrix3x4_VectorTransform( textoworld, tex, point );
|
||
|
VectorAdd( point, g_face_offset[bestfrag->facenum], point );
|
||
|
VectorCopy( bestpos, position );
|
||
|
*surface = bestfrag->facenum;
|
||
|
outside = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TexelSpaceToWorld( l, point, orig_s, orig_t );
|
||
|
VectorMA( point, DEFAULT_HUNT_OFFSET, faceplane->normal, position );
|
||
|
*surface = facenum;
|
||
|
outside = true;
|
||
|
}
|
||
|
|
||
|
DeleteSampleFrag( fraginfo );
|
||
|
|
||
|
return outside;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
CalcPoints
|
||
|
|
||
|
For each texture aligned grid point, back project onto the plane
|
||
|
to get the world xyz value of the sample point
|
||
|
=================
|
||
|
*/
|
||
|
void CalcPoints( lightinfo_t *l )
|
||
|
{
|
||
|
vec_t starts, startt, us, ut;
|
||
|
int h = l->texsize[1] + 1;
|
||
|
int w = l->texsize[0] + 1;
|
||
|
int s, t, texture_step;
|
||
|
vec_t square[2][2];
|
||
|
vec_t step;
|
||
|
|
||
|
l->surfpt = (surfpt_t *)Mem_Alloc( w * h * sizeof( surfpt_t ));
|
||
|
texture_step = GetTextureStep( l->face );
|
||
|
starts = (float)l->texmins[0] * texture_step;
|
||
|
startt = (float)l->texmins[1] * texture_step;
|
||
|
step = (float)texture_step;
|
||
|
l->numsurfpt = w * h;
|
||
|
|
||
|
for( t = 0; t < h; t++ )
|
||
|
{
|
||
|
for( s = 0; s < w; s++ )
|
||
|
{
|
||
|
surfpt_t *surf = &l->surfpt[s+w*t];
|
||
|
us = starts + s * step;
|
||
|
ut = startt + t * step;
|
||
|
square[0][0] = us - step;
|
||
|
square[0][1] = ut - step;
|
||
|
square[1][0] = us + step;
|
||
|
square[1][1] = ut + step;
|
||
|
surf->occluded = CalcSamplePoint( surf->point, surf->position, &surf->surface, l, us, ut, square );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//==============================================================
|
||
|
facelight_t g_facelight[MAX_MAP_FACES];
|
||
|
directlight_t *g_directlights;
|
||
|
int g_numdlights;
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
GetLightType
|
||
|
|
||
|
trying to determine light type by settings
|
||
|
=============
|
||
|
*/
|
||
|
emittype_t GetLightType( entity_t *e )
|
||
|
{
|
||
|
const char *name = ValueForKey( e, "classname" );
|
||
|
const char *target = ValueForKey( e, "target" );
|
||
|
int style = IntForKey( e, "style" );
|
||
|
|
||
|
if( Q_strncmp( name, "light", 5 ) && !CheckKey( e, "_sunlight" ))
|
||
|
return emit_ignored; // not a light entity
|
||
|
|
||
|
#ifdef HLRAD_PARANOIA_BUMP
|
||
|
if( style != 0 && style != LS_SKY )
|
||
|
return emit_ignored;
|
||
|
#endif
|
||
|
if( CheckKey( e, "_surface" ))
|
||
|
return emit_ignored; // already handled
|
||
|
|
||
|
// check for skylight or sunlight
|
||
|
if( BoolForKey( e, "_sky" ) || CheckKey( e, "_sunlight" ) || !Q_strcmp( name, "light_environment" ))
|
||
|
return emit_skylight;
|
||
|
|
||
|
// check for spotlight
|
||
|
if( !Q_strcmp( name, "light_spot" ))
|
||
|
return emit_spotlight;
|
||
|
else if( target[0] || CheckKey( e, "mangle" ))
|
||
|
return emit_spotlight;
|
||
|
|
||
|
// otherwise it's pointlight
|
||
|
return emit_point;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
ParseLightIntensity
|
||
|
|
||
|
get light intensity
|
||
|
=============
|
||
|
*/
|
||
|
int ParseLightIntensity( const char *pLight, vec3_t intensity, vec_t multiplier )
|
||
|
{
|
||
|
double r, g, b, scaler;
|
||
|
int argCnt;
|
||
|
|
||
|
// scanf into doubles, then assign, so it is vec_t size independent
|
||
|
r = g = b = scaler = 0;
|
||
|
argCnt = sscanf( pLight, "%lf %lf %lf %lf", &r, &g, &b, &scaler );
|
||
|
intensity[0] = (float)r;
|
||
|
|
||
|
if( argCnt == 1 )
|
||
|
{
|
||
|
// the R, G, B values are all equal.
|
||
|
intensity[1] = intensity[2] = (float)r;
|
||
|
}
|
||
|
else if( argCnt == 3 || argCnt == 4 )
|
||
|
{
|
||
|
// save the other two G,B values.
|
||
|
intensity[1] = (float)g;
|
||
|
intensity[2] = (float)b;
|
||
|
|
||
|
// did we also get an "intensity" scaler value too?
|
||
|
if( argCnt == 4 )
|
||
|
{
|
||
|
// Scale the normalized 0-255 R,G,B values by the intensity scaler
|
||
|
VectorScale( intensity, scaler / multiplier, intensity );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// quake default lightvalue
|
||
|
VectorFill( intensity, 300.0f );
|
||
|
}
|
||
|
|
||
|
return argCnt;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
ParseLightIntensity
|
||
|
|
||
|
get light intensity and color
|
||
|
=============
|
||
|
*/
|
||
|
void ParseLightIntensity( entity_t *e, directlight_t *dl )
|
||
|
{
|
||
|
double r, g, b, scaler;
|
||
|
char *pLight = NULL;
|
||
|
char *pColor = NULL;
|
||
|
int argCnt;
|
||
|
|
||
|
if( CheckKey( e, "_sunlight" ))
|
||
|
pLight = ValueForKey( e, "_sunlight" );
|
||
|
else if( CheckKey( e, "_light" ))
|
||
|
pLight = ValueForKey( e, "_light" );
|
||
|
else pLight = ValueForKey( e, "light" );
|
||
|
|
||
|
argCnt = ParseLightIntensity( pLight, dl->intensity );
|
||
|
|
||
|
switch( argCnt )
|
||
|
{
|
||
|
case 3:
|
||
|
case 4:
|
||
|
dl->falloff = falloff_valve;
|
||
|
break;
|
||
|
default:
|
||
|
dl->falloff = falloff_quake;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// quake light
|
||
|
if( argCnt <= 1 )
|
||
|
{
|
||
|
scaler = dl->intensity[0];
|
||
|
r = g = b = 0;
|
||
|
|
||
|
if( CheckKey( e, "_sunlight_color" ))
|
||
|
{
|
||
|
pColor = ValueForKey( e, "_sunlight_color" );
|
||
|
dl->radius = BOGUS_RANGE; // sunlight has infinite radius
|
||
|
}
|
||
|
else if( CheckKey( e, "_color" ))
|
||
|
{
|
||
|
pColor = ValueForKey( e, "_color" );
|
||
|
dl->radius = scaler;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pColor = ValueForKey( e, "color" );
|
||
|
dl->radius = scaler;
|
||
|
}
|
||
|
|
||
|
if( pColor[0] && sscanf( pColor, "%lf %lf %lf", &r, &g, &b ) == 3 )
|
||
|
{
|
||
|
dl->intensity[0] = r * (float)scaler;
|
||
|
dl->intensity[1] = g * (float)scaler;
|
||
|
dl->intensity[2] = b * (float)scaler;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
ParseLightDirection
|
||
|
|
||
|
determine light direction
|
||
|
=============
|
||
|
*/
|
||
|
void ParseLightDirection( entity_t *e, directlight_t *dl )
|
||
|
{
|
||
|
const char *target = ValueForKey( e, "target" );
|
||
|
vec3_t vAngles;
|
||
|
|
||
|
if( target[0] )
|
||
|
{
|
||
|
// point towards target
|
||
|
entity_t *e2 = FindTargetEntity( target );
|
||
|
vec3_t dest;
|
||
|
|
||
|
if( e2 )
|
||
|
{
|
||
|
GetVectorForKey( e2, "origin", dest );
|
||
|
VectorSubtract( dest, dl->origin, dl->normal );
|
||
|
VectorNormalize( dl->normal );
|
||
|
}
|
||
|
else MsgDev( D_WARN, "%s[%i] has missing target %s\n", ValueForKey( e, "classname" ), e - g_entities, target );
|
||
|
}
|
||
|
|
||
|
// try angles
|
||
|
if( VectorIsNull( dl->normal ))
|
||
|
{
|
||
|
if( CheckKey( e, "mangle" ))
|
||
|
{
|
||
|
// Quake spotlight angles
|
||
|
GetVectorForKey( e, "mangle", vAngles );
|
||
|
|
||
|
dl->normal[0] = cos( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] ));
|
||
|
dl->normal[1] = sin( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] ));
|
||
|
dl->normal[2] = sin( DEG2RAD( vAngles[1] ));
|
||
|
}
|
||
|
else if( CheckKey( e, "_sunlight_mangle" ))
|
||
|
{
|
||
|
// Quake sunlight angles
|
||
|
GetVectorForKey( e, "_sunlight_mangle", vAngles );
|
||
|
|
||
|
dl->normal[0] = cos( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] ));
|
||
|
dl->normal[1] = sin( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] ));
|
||
|
dl->normal[2] = sin( DEG2RAD( vAngles[1] ));
|
||
|
}
|
||
|
else if( CheckKey( e, "_sun_mangle" ))
|
||
|
{
|
||
|
// Quake sunlight angles
|
||
|
GetVectorForKey( e, "_sun_mangle", vAngles );
|
||
|
|
||
|
dl->normal[0] = cos( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] ));
|
||
|
dl->normal[1] = sin( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] ));
|
||
|
dl->normal[2] = sin( DEG2RAD( vAngles[1] ));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vec_t angle = (float)FloatForKey( e, "angle" );
|
||
|
|
||
|
// Half-Life spotlight or sunlight angles
|
||
|
GetVectorForKey( e, "angles", vAngles );
|
||
|
|
||
|
if( angle == ANGLE_UP )
|
||
|
{
|
||
|
VectorSet( dl->normal, 0.0, 0.0, 1.0 );
|
||
|
}
|
||
|
else if( angle == ANGLE_DOWN )
|
||
|
{
|
||
|
VectorSet( dl->normal, 0.0, 0.0, -1.0 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// if we don't have a specific "angle" use the "angles" YAW
|
||
|
if( !angle ) angle = vAngles[1];
|
||
|
dl->normal[0] = (float)cos( DEG2RAD( angle ));
|
||
|
dl->normal[1] = (float)sin( DEG2RAD( angle ));
|
||
|
dl->normal[2] = 0;
|
||
|
}
|
||
|
|
||
|
angle = FloatForKey( e, "pitch" );
|
||
|
// if we don't have a specific "pitch" use the "angles" PITCH
|
||
|
if( !angle ) angle = vAngles[0];
|
||
|
|
||
|
dl->normal[0] *= (float)cos( DEG2RAD( angle ));
|
||
|
dl->normal[1] *= (float)cos( DEG2RAD( angle ));
|
||
|
dl->normal[2] = (float)sin( DEG2RAD( angle ));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
ParseLightAttenuation
|
||
|
|
||
|
get attenuation value
|
||
|
=============
|
||
|
*/
|
||
|
void ParseLightAttenuation( entity_t *e, directlight_t *dl )
|
||
|
{
|
||
|
if( CheckKey( e, "_fade" ))
|
||
|
dl->fade = FloatForKey( e, "_fade" );
|
||
|
else if( CheckKey( e, "wait" ))
|
||
|
dl->fade = FloatForKey( e, "wait" );
|
||
|
|
||
|
// to prevent division by zero
|
||
|
if( dl->fade <= 0.0 ) dl->fade = 1.0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
ParseLightFalloff
|
||
|
|
||
|
get falloff formula
|
||
|
=============
|
||
|
*/
|
||
|
void ParseLightFalloff( entity_t *e, directlight_t *dl )
|
||
|
{
|
||
|
|
||
|
if( CheckKey( e, "_falloff" ))
|
||
|
{
|
||
|
// strange behavior...
|
||
|
dl->falloff = IntForKey( e, "_falloff" );
|
||
|
if( dl->falloff == 1 )
|
||
|
dl->falloff = falloff_inverse;
|
||
|
else dl->falloff = falloff_valve;
|
||
|
}
|
||
|
else if( CheckKey( e, "delay" ))
|
||
|
{
|
||
|
dl->falloff = IntForKey( e, "delay" );
|
||
|
dl->falloff = bound( falloff_quake, dl->falloff, falloff_inverse2a );
|
||
|
dl->lf_scale = LF_SCALE;
|
||
|
}
|
||
|
|
||
|
// oterwise it's already set in ParseLightIntensity
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
ParsePointLight
|
||
|
=============
|
||
|
*/
|
||
|
void ParsePointLight( entity_t *e, directlight_t *dl )
|
||
|
{
|
||
|
ParseLightIntensity( e, dl );
|
||
|
ParseLightAttenuation( e, dl );
|
||
|
ParseLightFalloff( e, dl );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
ParseSpotLight
|
||
|
=============
|
||
|
*/
|
||
|
void ParseSpotLight( entity_t *e, directlight_t *dl )
|
||
|
{
|
||
|
ParseLightIntensity( e, dl );
|
||
|
ParseLightDirection( e, dl );
|
||
|
ParseLightAttenuation( e, dl );
|
||
|
ParseLightFalloff( e, dl );
|
||
|
|
||
|
if( CheckKey( e, "mangle" ))
|
||
|
{
|
||
|
// Quake spotlights
|
||
|
dl->stopdot = FloatForKey( e, "_softangle" );
|
||
|
dl->stopdot2 = FloatForKey( e, "angle" );
|
||
|
if( !dl->stopdot ) dl->stopdot = 40;
|
||
|
if( !dl->stopdot2 ) dl->stopdot2 = dl->stopdot;
|
||
|
dl->stopdot2 = Q_max( dl->stopdot, dl->stopdot2 );
|
||
|
dl->stopdot = (float)cos( DEG2RAD( dl->stopdot * 0.5 ));
|
||
|
dl->stopdot2 = (float)cos( DEG2RAD( dl->stopdot2 * 0.5 ));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Half-Life spotlights
|
||
|
dl->stopdot = FloatForKey( e, "_cone" );
|
||
|
dl->stopdot2 = FloatForKey( e, "_cone2" );
|
||
|
if( !dl->stopdot ) dl->stopdot = 10;
|
||
|
if( !dl->stopdot2 ) dl->stopdot2 = dl->stopdot;
|
||
|
dl->stopdot2 = Q_max( dl->stopdot, dl->stopdot2 );
|
||
|
dl->stopdot = (float)cos( DEG2RAD( dl->stopdot ));
|
||
|
dl->stopdot2 = (float)cos( DEG2RAD( dl->stopdot2 ));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
ParseSkyLight
|
||
|
=============
|
||
|
*/
|
||
|
void ParseSkyLight( entity_t *e, directlight_t *dl )
|
||
|
{
|
||
|
char *pLight = NULL;
|
||
|
|
||
|
ParseLightIntensity( e, dl );
|
||
|
ParseLightDirection( e, dl );
|
||
|
|
||
|
// check for sky diffuse light
|
||
|
if( CheckKey( e, "_diffuse_light" ))
|
||
|
{
|
||
|
ParseLightIntensity( ValueForKey( e, "_diffuse_light" ), dl->diffuse_intensity );
|
||
|
}
|
||
|
else if( CheckKey( e, "_sunlight2" ))
|
||
|
{
|
||
|
if( CheckKey( e, "_sunlight2_color" ))
|
||
|
pLight = va( "%s %s", ValueForKey( e, "_sunlight2_color" ), ValueForKey( e, "_sunlight2" ));
|
||
|
else pLight = ValueForKey( e, "_sunlight2" );
|
||
|
ParseLightIntensity( pLight, dl->diffuse_intensity, 1.0 );
|
||
|
}
|
||
|
|
||
|
// FIXME: get support for diffuse_intensity2 and _sunlight3?
|
||
|
if( CheckKey( e, "_sunlight3" ))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
if( CheckKey( e, "_spread" ))
|
||
|
dl->sunspreadangle = FloatForKey( e, "_spread" );
|
||
|
else if( CheckKey( e, "_sunlight_penumbra" ))
|
||
|
dl->sunspreadangle = FloatForKey( e, "_sunlight_penumbra" );
|
||
|
|
||
|
if( dl->sunspreadangle < 0.0 || dl->sunspreadangle > 180 )
|
||
|
{
|
||
|
MsgDev( D_ERROR, "invalid spread angle '%s'. Defaulting to 0\n", ValueForKey( e, "_spread" ));
|
||
|
dl->sunspreadangle = 0.0;
|
||
|
}
|
||
|
|
||
|
if( dl->sunspreadangle > 0.0 )
|
||
|
{
|
||
|
vec_t testangle = dl->sunspreadangle;
|
||
|
vec_t totalw = 0, testdot;
|
||
|
int i, count;
|
||
|
|
||
|
// we will later centralize all the normals we have collected.
|
||
|
if( dl->sunspreadangle < SUNSPREAD_THRESHOLD )
|
||
|
testangle = SUNSPREAD_THRESHOLD;
|
||
|
|
||
|
testdot = cos( DEG2RAD( testangle ));
|
||
|
|
||
|
for( count = 0, i = 0; i < g_numskynormals[SUNSPREAD_SKYLEVEL]; i++ )
|
||
|
{
|
||
|
vec_t *testnormal = g_skynormals[SUNSPREAD_SKYLEVEL][i];
|
||
|
vec_t dot = DotProduct( dl->normal, testnormal );
|
||
|
|
||
|
if( dot >= testdot - NORMAL_EPSILON )
|
||
|
{
|
||
|
// this is not the right formula when dl->sunspreadangle < SUNSPREAD_THRESHOLD,
|
||
|
// but it gives almost the same result as the right one.
|
||
|
totalw += Q_max( 0, dot - testdot ) * g_skynormalsizes[SUNSPREAD_SKYLEVEL][i];
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( count <= 10 || totalw <= NORMAL_EPSILON )
|
||
|
COM_FatalError( "CreateDirectLights: internal error\n" );
|
||
|
|
||
|
dl->sunnormals = (vec3_t *)Mem_Alloc( count * sizeof( vec3_t ));
|
||
|
dl->sunnormalweights = (vec_t *)Mem_Alloc( count * sizeof( vec_t ));
|
||
|
dl->numsunnormals = count;
|
||
|
|
||
|
for( count = 0, i = 0; i < g_numskynormals[SUNSPREAD_SKYLEVEL]; i++ )
|
||
|
{
|
||
|
vec_t *testnormal = g_skynormals[SUNSPREAD_SKYLEVEL][i];
|
||
|
vec_t dot = DotProduct( dl->normal, testnormal );
|
||
|
|
||
|
if( dot >= testdot - NORMAL_EPSILON )
|
||
|
{
|
||
|
if( count >= dl->numsunnormals )
|
||
|
COM_FatalError( "CreateDirectLights: internal error\n" );
|
||
|
|
||
|
VectorCopy( testnormal, dl->sunnormals[count] );
|
||
|
dl->sunnormalweights[count] = Q_max( 0, dot - testdot );
|
||
|
dl->sunnormalweights[count] *= g_skynormalsizes[SUNSPREAD_SKYLEVEL][i] / totalw;
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( count != dl->numsunnormals )
|
||
|
COM_FatalError( "CreateDirectLights: internal error\n" );
|
||
|
|
||
|
if( dl->sunspreadangle < SUNSPREAD_THRESHOLD )
|
||
|
{
|
||
|
for( i = 0; i < dl->numsunnormals; i++ )
|
||
|
{
|
||
|
vec_t iscale = 1.0 / DotProduct( dl->sunnormals[i], dl->normal );
|
||
|
vec_t angle = dl->sunspreadangle / SUNSPREAD_THRESHOLD;
|
||
|
vec3_t tmp;
|
||
|
|
||
|
VectorScale( dl->sunnormals[i], iscale, tmp );
|
||
|
VectorSubtract( tmp, dl->normal, tmp );
|
||
|
VectorMA( dl->normal, angle, tmp, dl->sunnormals[i] );
|
||
|
VectorNormalize( dl->sunnormals[i] );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dl->sunnormals = (vec3_t *)Mem_Alloc( sizeof( vec3_t ));
|
||
|
dl->sunnormalweights = (vec_t *)Mem_Alloc( sizeof( vec_t ));
|
||
|
VectorCopy( dl->normal, dl->sunnormals[0] );
|
||
|
dl->sunnormalweights[0] = 1.0;
|
||
|
dl->numsunnormals = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
BuildVisForDLight
|
||
|
|
||
|
create visibility cache for dlight
|
||
|
=============
|
||
|
*/
|
||
|
int BuildVisForDLight( directlight_t *dl )
|
||
|
{
|
||
|
int leafnum;
|
||
|
|
||
|
if( dl->type == emit_skylight )
|
||
|
{
|
||
|
// all leafs that contain skyface should be added to sun visibility
|
||
|
for( leafnum = 0; leafnum < g_numvisleafs; leafnum++ )
|
||
|
{
|
||
|
uint firstmarkface = g_dleafs[leafnum + 1].firstmarksurface;
|
||
|
|
||
|
for( int markface = 0; markface < g_dleafs[leafnum + 1].nummarksurfaces; markface++ )
|
||
|
{
|
||
|
dface_t *surf = &g_dfaces[g_dmarksurfaces[firstmarkface + markface]];
|
||
|
dtexinfo_t *tex = g_texinfo + surf->texinfo;
|
||
|
|
||
|
if( FBitSet( tex->flags, TEX_SPECIAL ))
|
||
|
{
|
||
|
if( !Q_strnicmp( GetTextureByTexinfo( surf->texinfo ), "sky", 3 ))
|
||
|
{
|
||
|
MergeDLightVis( dl, leafnum + 1 );
|
||
|
break; // no reason to check all faces, go to next leaf
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// technically light_environment is outside of world
|
||
|
return -1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
leafnum = PointInLeaf( dl->origin ) - g_dleafs;
|
||
|
SetDLightVis( dl, leafnum );
|
||
|
|
||
|
return leafnum;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
ParseLightEntity
|
||
|
=============
|
||
|
*/
|
||
|
bool ParseLightEntity( entity_t *e, directlight_t *dl )
|
||
|
{
|
||
|
int leafnum;
|
||
|
vec_t l1;
|
||
|
|
||
|
// all the lights has the origin
|
||
|
GetVectorForKey( e, "origin", dl->origin );
|
||
|
|
||
|
switch( dl->type )
|
||
|
{
|
||
|
case emit_point:
|
||
|
ParsePointLight( e, dl );
|
||
|
break;
|
||
|
case emit_spotlight:
|
||
|
ParseSpotLight( e, dl );
|
||
|
break;
|
||
|
case emit_skylight:
|
||
|
ParseSkyLight( e, dl );
|
||
|
break;
|
||
|
default:
|
||
|
// unknown or unsupported light type
|
||
|
Mem_Free( dl );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if( VectorMax( dl->intensity ) <= 1 )
|
||
|
{
|
||
|
Mem_Free( dl );
|
||
|
return false; // bad light value?
|
||
|
}
|
||
|
|
||
|
if( dl->type != emit_skylight && dl->falloff == falloff_valve )
|
||
|
{
|
||
|
l1 = VectorMax( dl->intensity );
|
||
|
l1 = l1 * l1 / 10; // Valve weird divider
|
||
|
VectorScale( dl->intensity, l1, dl->intensity );
|
||
|
dl->radius = l1;
|
||
|
}
|
||
|
|
||
|
dl->facenum = -1; // no texinfo for point and spotlights
|
||
|
dl->modelnum = 0; // worldmodel for pointlights
|
||
|
#ifdef HLRAD_PARANOIA_BUMP
|
||
|
dl->flags = IntForKey( e, "spawnflags" ); // buz
|
||
|
#else
|
||
|
dl->style = abs( IntForKey( e, "style" ));
|
||
|
#endif
|
||
|
leafnum = BuildVisForDLight( dl );
|
||
|
dl->topatch = g_fastmode;
|
||
|
dl->next = g_directlights;
|
||
|
g_directlights = dl;
|
||
|
g_numdlights++;
|
||
|
|
||
|
// copy worldlight params and set unique number
|
||
|
InitWorldLightFromDlight( dl, leafnum );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
CreateDirectLights
|
||
|
=============
|
||
|
*/
|
||
|
void CreateDirectLights( void )
|
||
|
{
|
||
|
directlight_t *dl;
|
||
|
patch_t *p;
|
||
|
int i;
|
||
|
|
||
|
g_numdlights = 0;
|
||
|
|
||
|
// surfaces
|
||
|
for( i = 0, p = g_patches; i < g_num_patches; i++, p++ )
|
||
|
{
|
||
|
vec_t value;
|
||
|
|
||
|
if( !VectorIsNull( p->reflectivity ))
|
||
|
value = DotProduct( p->baselight, p->reflectivity ) / 3.0;
|
||
|
else value = VectorAvg( p->baselight );
|
||
|
|
||
|
if( value > 0.0 )
|
||
|
{
|
||
|
dl = (directlight_t *)Mem_Alloc( sizeof( directlight_t ));
|
||
|
g_numdlights++;
|
||
|
|
||
|
VectorCopy( p->origin, dl->origin );
|
||
|
dl->type = emit_surface;
|
||
|
dl->style = p->emitstyle;
|
||
|
dl->lf_scale = 1.0f;
|
||
|
|
||
|
VectorCopy( GetPlaneFromFace( p->faceNumber )->normal, dl->normal );
|
||
|
VectorCopy( p->baselight, dl->intensity );
|
||
|
VectorScale( dl->intensity, p->area, dl->intensity );
|
||
|
VectorScale( dl->intensity, p->exposure, dl->intensity );
|
||
|
dl->falloff = falloff_valve;
|
||
|
dl->radius = p->area * 1.74;
|
||
|
dl->facenum = p->faceNumber;
|
||
|
dl->modelnum = p->modelnum;
|
||
|
dl->lightnum = p->lightnum; // already set
|
||
|
dl->next = g_directlights;
|
||
|
g_directlights = dl;
|
||
|
|
||
|
if( !VectorIsNull( p->reflectivity ))
|
||
|
{
|
||
|
VectorScale( dl->intensity, 0.5 / M_PI, dl->intensity );
|
||
|
VectorMultiply( dl->intensity, p->reflectivity, dl->intensity );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
VectorScale( dl->intensity, DIRECT_SCALE, dl->intensity );
|
||
|
}
|
||
|
|
||
|
if( !FBitSet( p->flags, PATCH_EMITLIGHT ) || g_fastmode )
|
||
|
dl->topatch = true;
|
||
|
dl->patch_emitter_range = p->emitter_range;
|
||
|
dl->patch_area = p->area;
|
||
|
BuildVisForDLight( dl );
|
||
|
dl->fade = p->fade;
|
||
|
dl->patch = p;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// entities
|
||
|
for( i = 0; i < g_numentities; i++ )
|
||
|
{
|
||
|
emittype_t type;
|
||
|
entity_t *e;
|
||
|
|
||
|
e = &g_entities[i];
|
||
|
type = GetLightType( e );
|
||
|
if( type == emit_ignored )
|
||
|
continue; // not a light entity
|
||
|
|
||
|
dl = (directlight_t *)Mem_Alloc( sizeof( directlight_t ));
|
||
|
dl->lf_scale = 1.0f;
|
||
|
dl->type = type;
|
||
|
|
||
|
if( !ParseLightEntity( e, dl ))
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( g_numdlights <= 0 )
|
||
|
COM_FatalError( "map %s without any lights\n", source );
|
||
|
MsgDev( D_INFO, "%i direct lights\n", g_numdlights );
|
||
|
|
||
|
if( g_onlylights ) return;
|
||
|
|
||
|
#ifdef HLRAD_COMPUTE_VISLIGHTMATRIX
|
||
|
// rows: facenum -> visible light bits like a normal vis info
|
||
|
g_vislightdatasize = g_numfaces * ((g_numworldlights + 7) / 8);
|
||
|
if( g_dvislightdata ) Mem_Free( g_dvislightdata );
|
||
|
g_dvislightdata = (byte *)Mem_Alloc( g_vislightdatasize );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
DeleteDirectLights
|
||
|
=============
|
||
|
*/
|
||
|
void DeleteDirectLights( void )
|
||
|
{
|
||
|
directlight_t *dl, *next;
|
||
|
|
||
|
for( dl = g_directlights; dl; dl = next )
|
||
|
{
|
||
|
next = dl->next;
|
||
|
Mem_Free( dl->pvs );
|
||
|
Mem_Free( dl );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
GetLightDenominator
|
||
|
|
||
|
calc fallof denominator
|
||
|
=============
|
||
|
*/
|
||
|
vec_t GetLightDenominator( const directlight_t *dl, vec_t dist )
|
||
|
{
|
||
|
vec_t lf_scale = Q_max( 1.0, dl->lf_scale );
|
||
|
vec_t value = dist * dl->fade;
|
||
|
|
||
|
switch( dl->falloff )
|
||
|
{
|
||
|
case falloff_quake:
|
||
|
// Quake attenuation.
|
||
|
if( dl->radius - value > 0.0f )
|
||
|
return 1.0 / (1.0 - value * ( 1.0 / dl->radius ));
|
||
|
return 0.0f;
|
||
|
case falloff_inverse:
|
||
|
return value / lf_scale;
|
||
|
break;
|
||
|
case falloff_inverse2a:
|
||
|
value += lf_scale;
|
||
|
case falloff_inverse2:
|
||
|
return value * value / (lf_scale * lf_scale);
|
||
|
case falloff_valve:
|
||
|
// Valve attenuation
|
||
|
return value * value;
|
||
|
default:
|
||
|
// No attenuation
|
||
|
return 1.0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
GatherSampleLight
|
||
|
=============
|
||
|
*/
|
||
|
void GatherSampleLight( int threadnum, int fn, const vec3_t pos, int leafnum, const vec3_t n,
|
||
|
vec3_t *s_light, vec3_t *s_dir, vec_t *s_occ, byte *styles, byte *vislight, bool topatch, entity_t *ignoreent )
|
||
|
{
|
||
|
int skylevel = (fn != -1) ? SKYLEVEL_SOFTSKYON : SKYLEVEL_SOFTSKYOFF;
|
||
|
vec3_t add, delta, add_one;
|
||
|
vec3_t testline_origin;
|
||
|
int style_index = 0;
|
||
|
vec3_t add_direction;
|
||
|
float dist, ratio;
|
||
|
float dot, dot2;
|
||
|
vec3_t direction;
|
||
|
|
||
|
for( directlight_t *dl = g_directlights; dl != NULL; dl = dl->next )
|
||
|
{
|
||
|
// check light visibility
|
||
|
if( !leafnum || !dl->pvs || !CHECKVISBIT( dl->pvs, leafnum - 1 ))
|
||
|
continue;
|
||
|
|
||
|
// skylights work fundamentally differently than normal lights
|
||
|
if( dl->type == emit_skylight )
|
||
|
{
|
||
|
VectorClear( add_direction );
|
||
|
VectorClear( add );
|
||
|
|
||
|
// add sun light
|
||
|
if( topatch == dl->topatch )
|
||
|
{
|
||
|
// loop over the normals
|
||
|
for( int i = 0; i < dl->numsunnormals; i++ )
|
||
|
{
|
||
|
// make sure the angle is okay
|
||
|
dot = -DotProduct( n, dl->sunnormals[i] );
|
||
|
if( dot <= NORMAL_EPSILON )
|
||
|
continue;
|
||
|
|
||
|
// search back to see if we can hit a sky brush
|
||
|
VectorScale( dl->sunnormals[i], -BOGUS_RANGE, delta );
|
||
|
VectorAdd( pos, delta, delta );
|
||
|
|
||
|
if( TestLine( threadnum, pos, delta, dl->topatch, ignoreent ) != CONTENTS_SKY )
|
||
|
continue; // occluded
|
||
|
|
||
|
VectorCopy( dl->sunnormals[i], direction );
|
||
|
VectorScale( dl->intensity, dot * dl->sunnormalweights[i], add_one );
|
||
|
// add to the contribution of this light
|
||
|
VectorAdd( add, add_one, add );
|
||
|
vec_t avg = VectorAvg( add_one );
|
||
|
VectorMA( add_direction, avg, direction, add_direction );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( topatch && g_indirect_sun > 0.0 )
|
||
|
{
|
||
|
vec3_t *skynormals = g_skynormals[skylevel];
|
||
|
vec_t *skyweights = g_skynormalsizes[skylevel];
|
||
|
vec3_t sky_intensity;
|
||
|
|
||
|
// loop over the normals
|
||
|
for( int i = 0; i < g_numskynormals[skylevel]; i++ )
|
||
|
{
|
||
|
// make sure the angle is okay
|
||
|
dot = -DotProduct( n, skynormals[i] );
|
||
|
if( dot <= NORMAL_EPSILON )
|
||
|
continue;
|
||
|
|
||
|
// search back to see if we can hit a sky brush
|
||
|
VectorScale( skynormals[i], -BOGUS_RANGE, delta );
|
||
|
VectorAdd( pos, delta, delta );
|
||
|
|
||
|
if( TestLine( threadnum, pos, delta, true, ignoreent ) != CONTENTS_SKY )
|
||
|
continue; // occluded
|
||
|
|
||
|
// how far this piece of sky has deviated from the sun
|
||
|
vec_t factor = (( 1.0 - DotProduct( dl->normal, skynormals[i] )) / 2.0f );
|
||
|
|
||
|
factor = bound( 0.0, factor, 1.0 );
|
||
|
VectorScale( dl->diffuse_intensity, 1.0 - factor, sky_intensity );
|
||
|
VectorMA( sky_intensity, factor, dl->intensity, sky_intensity );
|
||
|
VectorCopy( skynormals[i], direction );
|
||
|
|
||
|
VectorScale( sky_intensity, skyweights[i] * g_indirect_sun * 0.5, sky_intensity );
|
||
|
VectorScale( sky_intensity, dot, add_one );
|
||
|
// add to the contribution of this light
|
||
|
VectorAdd( add, add_one, add );
|
||
|
vec_t avg = VectorAvg( add_one );
|
||
|
VectorMA( add_direction, avg, direction, add_direction );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bool light_behind_surface = false;
|
||
|
vec_t range;
|
||
|
|
||
|
if( topatch != dl->topatch )
|
||
|
continue;
|
||
|
|
||
|
VectorCopy( dl->origin, testline_origin );
|
||
|
VectorSubtract( dl->origin, pos, delta );
|
||
|
|
||
|
if( dl->type == emit_surface ) // move emitter back to its plane
|
||
|
VectorMA( delta, -DEFAULT_HUNT_OFFSET * 0.5, dl->normal, delta );
|
||
|
|
||
|
dist = VectorNormalize( delta );
|
||
|
dot = DotProduct( delta, n );
|
||
|
dist = Q_max( dist, 1.0 );
|
||
|
|
||
|
// save some compile time
|
||
|
if( dl->type == emit_point && dl->falloff == falloff_quake && dist > dl->radius && topatch == false )
|
||
|
continue; // don't bother with light too far away
|
||
|
|
||
|
// variable power falloff (1 = inverse linear, 2 = inverse square)
|
||
|
vec_t denominator = GetLightDenominator( dl, dist );
|
||
|
if( denominator <= 0.0 ) continue;
|
||
|
VectorNegate( delta, direction );
|
||
|
|
||
|
if(( -dot ) > 0 )
|
||
|
{
|
||
|
// reflect the direction back (this is not ideal!)
|
||
|
VectorMA( direction, -(-dot) * 2.0f, n, direction );
|
||
|
}
|
||
|
|
||
|
switch( dl->type )
|
||
|
{
|
||
|
case emit_point:
|
||
|
if( dot <= NORMAL_EPSILON )
|
||
|
continue;
|
||
|
ratio = dot / denominator;
|
||
|
VectorScale( dl->intensity, ratio, add );
|
||
|
break;
|
||
|
case emit_surface:
|
||
|
if( dot <= NORMAL_EPSILON )
|
||
|
light_behind_surface = true;
|
||
|
dot2 = -DotProduct( delta, dl->normal );
|
||
|
if( dot2 * dist <= MINIMUM_PATCH_DISTANCE )
|
||
|
continue;
|
||
|
range = dl->patch_emitter_range;
|
||
|
ratio = dot * dot2 / denominator;
|
||
|
|
||
|
// analogous to the one in MakeScales
|
||
|
// 0.4f is tested to be able to fully eliminate bright spots
|
||
|
if( ratio * dl->patch_area > 0.4f )
|
||
|
ratio = 0.4f / dl->patch_area;
|
||
|
|
||
|
if( dist < range - ON_EPSILON )
|
||
|
{
|
||
|
vec_t sightarea;
|
||
|
vec_t ratio2, frac;
|
||
|
|
||
|
// do things slow
|
||
|
if( light_behind_surface )
|
||
|
{
|
||
|
ratio = 0.0;
|
||
|
dot = 0.0;
|
||
|
}
|
||
|
|
||
|
GetAlternateOrigin( pos, n, dl->patch, testline_origin );
|
||
|
sightarea = CalcSightArea( pos, n, dl->patch->winding, dl->patch->emitter_skylevel );
|
||
|
|
||
|
frac = dist / range;
|
||
|
frac = ( frac - 0.5 ) * 2.0; // make a smooth transition between the two methods
|
||
|
frac = bound( 0.0, frac, 1.0 );
|
||
|
// because dl->patch_area has been multiplied into dl->intensity
|
||
|
ratio2 = (sightarea / dl->patch_area);
|
||
|
ratio = frac * ratio + (1.0 - frac) * ratio2;
|
||
|
}
|
||
|
else if( light_behind_surface )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
VectorScale( dl->intensity, ratio, add );
|
||
|
break;
|
||
|
case emit_spotlight:
|
||
|
if( dot <= NORMAL_EPSILON )
|
||
|
continue;
|
||
|
dot2 = -DotProduct( delta, dl->normal );
|
||
|
if( dot2 <= dl->stopdot2 )
|
||
|
continue; // outside light cone
|
||
|
ratio = dot * dot2 / denominator;
|
||
|
if( dot2 <= dl->stopdot )
|
||
|
ratio *= (dot2 - dl->stopdot2) / (dl->stopdot - dl->stopdot2);
|
||
|
VectorScale( dl->intensity, ratio, add );
|
||
|
break;
|
||
|
default:
|
||
|
COM_FatalError( "bad dl->type\n" );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
VectorCopy( direction, add_direction ); // will scale it later
|
||
|
}
|
||
|
|
||
|
if( topatch == false )
|
||
|
{
|
||
|
vec_t dirtSample = GatherSampleDirt( threadnum, fn, pos, n, ignoreent );
|
||
|
VectorScale( add, dirtSample, add );
|
||
|
}
|
||
|
|
||
|
// g-cont. when lightmap will be turned from float to byte some lightvalue will be unreachable
|
||
|
// (1.0f / 255.0f) ~= 0.003, and EQUAL_EPSILON is = 0.004 * 255 = 1.02, minimal brightness of lightmap
|
||
|
if( VectorMax( add ) > EQUAL_EPSILON )
|
||
|
{
|
||
|
if( dl->type != emit_skylight && TestLine( threadnum, pos, testline_origin, dl->topatch, ignoreent ) != CONTENTS_EMPTY )
|
||
|
continue; // occluded
|
||
|
#ifdef HLRAD_PARANOIA_BUMP
|
||
|
// hardcoded style representation
|
||
|
if( !FBitSet( dl->flags, LIGHTFLAG_NOT_NORMAL ))
|
||
|
{
|
||
|
VectorAdd( s_light[STYLE_ORIGINAL_LIGHT], add, s_light[STYLE_ORIGINAL_LIGHT] );
|
||
|
styles[0] = STYLE_ORIGINAL_LIGHT; // used
|
||
|
}
|
||
|
|
||
|
if( !FBitSet( dl->flags, LIGHTFLAG_NOT_RENDERER ))
|
||
|
{
|
||
|
VectorAdd( s_light[STYLE_BUMPED_LIGHT], add, s_light[STYLE_BUMPED_LIGHT] );
|
||
|
styles[1] = STYLE_BUMPED_LIGHT; // used
|
||
|
}
|
||
|
#else
|
||
|
for( style_index = 0; style_index < MAXLIGHTMAPS; style_index++ )
|
||
|
{
|
||
|
if( styles[style_index] == dl->style || styles[style_index] == 255 )
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( style_index == MAXLIGHTMAPS )
|
||
|
{
|
||
|
if( topatch ) g_overflowed_styles_onpatch[threadnum]++;
|
||
|
else g_overflowed_styles_onface[threadnum]++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// allocate a new one
|
||
|
if( styles[style_index] == 255 )
|
||
|
styles[style_index] = dl->style;
|
||
|
|
||
|
VectorAdd( s_light[style_index], add, s_light[style_index] );
|
||
|
#endif
|
||
|
if( topatch == false )
|
||
|
g_lighted_luxels[threadnum]++;
|
||
|
|
||
|
if( s_dir )
|
||
|
{
|
||
|
#ifdef HLRAD_PARANOIA_BUMP
|
||
|
// buz: add intensity to lightdir vector
|
||
|
// delta must contain direction to light
|
||
|
if( !FBitSet( dl->flags, LIGHTFLAG_NOT_RENDERER ))
|
||
|
{
|
||
|
if( dl->type != emit_skylight )
|
||
|
{
|
||
|
vec_t maxlight = VectorMaximum( add );
|
||
|
VectorScale( add_direction, maxlight, add_direction );
|
||
|
}
|
||
|
VectorAdd( s_dir[STYLE_BUMPED_LIGHT], add_direction, s_dir[STYLE_BUMPED_LIGHT] );
|
||
|
}
|
||
|
#else
|
||
|
if( dl->type != emit_skylight )
|
||
|
{
|
||
|
vec_t avg = VectorAvg( add );
|
||
|
VectorScale( add_direction, avg, add_direction );
|
||
|
}
|
||
|
|
||
|
VectorAdd( s_dir[style_index], add_direction, s_dir[style_index] );
|
||
|
#endif
|
||
|
}
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
if( s_occ ) s_occ[style_index] = 1.0f;
|
||
|
#endif
|
||
|
#ifdef HLRAD_COMPUTE_VISLIGHTMATRIX
|
||
|
// no reason to set it again
|
||
|
if( vislight != NULL && !CHECKVISBIT( vislight, dl->lightnum ))
|
||
|
{
|
||
|
ThreadLock();
|
||
|
SETVISBIT( vislight, dl->lightnum );
|
||
|
ThreadUnlock();
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// =====================================================================================
|
||
|
// AddSampleToPatch
|
||
|
// Take the sample's collected light and add it back into the apropriate patch for the radiosity pass.
|
||
|
// =====================================================================================
|
||
|
static void AddSamplesToPatches( int threadnum, const sample_t *samples, const byte *styles, int facenum, const lightinfo_t *l )
|
||
|
{
|
||
|
int numtexwindings = 0;
|
||
|
winding_t **texwindings;
|
||
|
int texture_step;
|
||
|
int i, j, m, k;
|
||
|
patch_t *p;
|
||
|
|
||
|
for( p = g_face_patches[facenum]; p != NULL; p = p->next )
|
||
|
numtexwindings++;
|
||
|
|
||
|
// create array of pointers
|
||
|
texwindings = (winding_t **)Mem_Alloc( numtexwindings * sizeof( winding_t* ));
|
||
|
texture_step = GetTextureStep( &g_dfaces[facenum] );
|
||
|
|
||
|
// translate world winding into winding in s,t plane
|
||
|
for( j = 0, p = g_face_patches[facenum]; j < numtexwindings && p != NULL; j++, p = p->next )
|
||
|
{
|
||
|
winding_t *w = AllocWinding( p->winding->numpoints );
|
||
|
|
||
|
w->numpoints = p->winding->numpoints;
|
||
|
|
||
|
for( int x = 0; x < w->numpoints; x++ )
|
||
|
{
|
||
|
vec_t s, t;
|
||
|
WorldToTexelSpace( l, p->winding->p[x], s, t );
|
||
|
VectorSet( w->p[x], s, t, 0.0 );
|
||
|
}
|
||
|
|
||
|
RemoveColinearPointsEpsilon( w, ON_EPSILON );
|
||
|
texwindings[j] = w;
|
||
|
}
|
||
|
|
||
|
for( i = 0; i < l->numsurfpt; i++ )
|
||
|
{
|
||
|
dplane_t clipplanes[4];
|
||
|
vec_t s_vec, t_vec;
|
||
|
|
||
|
s_vec = l->texmins[0] * texture_step + ( i % (l->texsize[0] + 1 )) * texture_step;
|
||
|
t_vec = l->texmins[1] * texture_step + ( i / (l->texsize[0] + 1 )) * texture_step;
|
||
|
|
||
|
// prepare clip planes (in 2D)
|
||
|
VectorSet( clipplanes[0].normal, 1.0, 0.0, 0.0 );
|
||
|
clipplanes[0].dist = (s_vec - 0.5 * texture_step);
|
||
|
VectorSet( clipplanes[1].normal,-1.0, 0.0, 0.0 );
|
||
|
clipplanes[1].dist = -(s_vec + 0.5 * texture_step);
|
||
|
VectorSet( clipplanes[2].normal, 0.0, 1.0, 0.0 );
|
||
|
clipplanes[2].dist = (t_vec - 0.5 * texture_step);
|
||
|
VectorSet( clipplanes[3].normal, 0.0,-1.0, 0.0 );
|
||
|
clipplanes[3].dist = -(t_vec + 0.5 * texture_step);
|
||
|
|
||
|
// clip each patch
|
||
|
for( j = 0, p = g_face_patches[facenum]; j < numtexwindings && p != NULL; j++, p = p->next )
|
||
|
{
|
||
|
winding_t *w = CopyWinding( texwindings[j] );
|
||
|
|
||
|
for( k = 0; k < 4 && w != NULL; k++ )
|
||
|
{
|
||
|
ChopWindingInPlace( &w, clipplanes[k].normal, clipplanes[k].dist, ON_EPSILON, false );
|
||
|
}
|
||
|
|
||
|
if( w != NULL )
|
||
|
{
|
||
|
// add sample to patch
|
||
|
vec_t area = WindingArea( w ) / (texture_step * texture_step);
|
||
|
const sample_t *s = &samples[i];
|
||
|
|
||
|
for( m = 0; m < MAXLIGHTMAPS && ( styles[m] != 255 ); m++ )
|
||
|
{
|
||
|
if( VectorMax( s->light[m] ) < EQUAL_EPSILON )
|
||
|
continue;
|
||
|
|
||
|
for( k = 0; k < MAXLIGHTMAPS && p->totalstyle[k] != 255; k++ )
|
||
|
{
|
||
|
if( p->totalstyle[k] == styles[m] )
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( k < MAXLIGHTMAPS )
|
||
|
{
|
||
|
if( p->totalstyle[k] == 255 )
|
||
|
p->totalstyle[k] = styles[m];
|
||
|
|
||
|
// add the sample to the patch
|
||
|
VectorMA( p->samplelight[k], area, s->light[m], p->samplelight[k] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorMA( p->samplelight_dir[k], area, s->deluxe[m], p->samplelight_dir[k] );
|
||
|
#endif
|
||
|
p->samples[k] += area;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_overflowed_styles_onpatch[threadnum]++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FreeWinding( w );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( j = 0; j < numtexwindings; j++ )
|
||
|
FreeWinding( texwindings[j] );
|
||
|
Mem_Free( texwindings );
|
||
|
}
|
||
|
|
||
|
static void CalcLightmap( int thread, lightinfo_t *l, facelight_t *fl )
|
||
|
{
|
||
|
vec_t texture_step = GetTextureStep( &g_dfaces[l->surfnum] );
|
||
|
vec_t density = (vec_t)l->lmcache_density;
|
||
|
int w = l->texsize[0] + 1;
|
||
|
int h = l->texsize[1] + 1;
|
||
|
byte *vislight = NULL;
|
||
|
dface_t *f = l->face;
|
||
|
vec_t square[2][2];
|
||
|
int i, j;
|
||
|
|
||
|
// allocate light samples
|
||
|
fl->samples = (sample_t *)Mem_Alloc( l->numsurfpt * sizeof( sample_t ));
|
||
|
fl->numsamples = l->numsurfpt;
|
||
|
|
||
|
// stats
|
||
|
g_direct_luxels[thread] += fl->numsamples;
|
||
|
|
||
|
#ifdef HLRAD_COMPUTE_VISLIGHTMATRIX
|
||
|
vislight = g_dvislightdata + l->surfnum * ((g_numworldlights + 7) / 8);
|
||
|
#endif
|
||
|
// copy surf points from lightinfo with offset 0,0
|
||
|
for( i = 0; i < fl->numsamples; i++ )
|
||
|
{
|
||
|
VectorCopy( l->surfpt[i].point, fl->samples[i].pos );
|
||
|
fl->samples[i].occluded = l->surfpt[i].occluded;
|
||
|
fl->samples[i].surface = l->surfpt[i].surface;
|
||
|
}
|
||
|
|
||
|
// for each sample whose light we need to calculate
|
||
|
for( i = 0; i < l->lmcachewidth * l->lmcacheheight; i++ )
|
||
|
{
|
||
|
vec_t s, t, s_vec, t_vec;
|
||
|
int nearest_s, nearest_t;
|
||
|
vec3_t spot, surfpt;
|
||
|
vec3_t pointnormal;
|
||
|
int surface;
|
||
|
bool blocked;
|
||
|
|
||
|
// prepare input parameter and output parameter
|
||
|
s = ((i % l->lmcachewidth) - l->lmcache_offset) / (vec_t)l->lmcache_density;
|
||
|
t = ((i / l->lmcachewidth) - l->lmcache_offset) / (vec_t)l->lmcache_density;
|
||
|
s_vec = l->texmins[0] * texture_step + s * texture_step;
|
||
|
t_vec = l->texmins[1] * texture_step + t * texture_step;
|
||
|
nearest_s = Q_max( 0, Q_min((int)floor( s + 0.5 ), l->texsize[0] ));
|
||
|
nearest_t = Q_max( 0, Q_min((int)floor( t + 0.5 ), l->texsize[1] ));
|
||
|
square[0][0] = l->texmins[0] * texture_step + ceil( s - (l->lmcache_side + 0.5) / density) * texture_step - texture_step;
|
||
|
square[0][1] = l->texmins[1] * texture_step + ceil( t - (l->lmcache_side + 0.5) / density) * texture_step - texture_step;
|
||
|
square[1][0] = l->texmins[0] * texture_step + floor( s + (l->lmcache_side + 0.5) / density) * texture_step + texture_step;
|
||
|
square[1][1] = l->texmins[1] * texture_step + floor( t + (l->lmcache_side + 0.5) / density) * texture_step + texture_step;
|
||
|
|
||
|
// find world's position for the sample
|
||
|
blocked = false;
|
||
|
|
||
|
if( CalcSamplePoint( surfpt, spot, &surface, l, s_vec, t_vec, square ))
|
||
|
{
|
||
|
j = nearest_s + w * nearest_t;
|
||
|
|
||
|
if( l->surfpt[j].occluded )
|
||
|
{
|
||
|
blocked = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// the area this light sample has effect on is completely covered by solid, so take whatever valid position.
|
||
|
VectorCopy( l->surfpt[j].point, surfpt );
|
||
|
VectorCopy( l->surfpt[j].position, spot );
|
||
|
surface = l->surfpt[j].surface;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// calculate normal for the sample
|
||
|
GetPhongNormal( surface, surfpt, pointnormal );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorCopy( pointnormal, l->normals[i] );
|
||
|
#endif
|
||
|
if( blocked ) continue;
|
||
|
|
||
|
// calculate visibility for the sample
|
||
|
int leaf = PointInLeaf( spot ) - g_dleafs;
|
||
|
|
||
|
// gather light
|
||
|
#if defined( HLRAD_DELUXEMAPPING ) && defined( HLRAD_SHADOWMAPPING )
|
||
|
GatherSampleLight( thread, l->surfnum, spot, leaf, pointnormal, l->light[i], l->deluxe[i], l->shadow[i], f->styles, vislight, 0 );
|
||
|
#elif defined( HLRAD_DELUXEMAPPING )
|
||
|
GatherSampleLight( thread, l->surfnum, spot, leaf, pointnormal, l->light[i], l->deluxe[i], NULL, f->styles, vislight, 0 );
|
||
|
#else
|
||
|
GatherSampleLight( thread, l->surfnum, spot, leaf, pointnormal, l->light[i], NULL, NULL, f->styles, vislight, 0 );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
for( i = 0; i < fl->numsamples; i++ )
|
||
|
{
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
vec_t weighting_correction;
|
||
|
vec3_t centernormal;
|
||
|
#endif
|
||
|
int s_center, t_center;
|
||
|
vec_t weighting, subsamples;
|
||
|
int s, t, pos;
|
||
|
vec_t sizehalf;
|
||
|
|
||
|
s_center = (i % w) * l->lmcache_density + l->lmcache_offset;
|
||
|
t_center = (i / w) * l->lmcache_density + l->lmcache_offset;
|
||
|
sizehalf = 0.5 * g_blur * l->lmcache_density;
|
||
|
subsamples = 0.0;
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorCopy( l->normals[s_center + l->lmcachewidth * t_center], centernormal );
|
||
|
#endif
|
||
|
for( s = s_center - l->lmcache_side; s <= s_center + l->lmcache_side; s++ )
|
||
|
{
|
||
|
for( t = t_center - l->lmcache_side; t <= t_center + l->lmcache_side; t++ )
|
||
|
{
|
||
|
weighting = (Q_min( 0.5, sizehalf - ( s - s_center )) - Q_max( -0.5, -sizehalf - ( s - s_center )));
|
||
|
weighting *=(Q_min( 0.5, sizehalf - ( t - t_center )) - Q_max( -0.5, -sizehalf - ( t - t_center )));
|
||
|
|
||
|
pos = s + l->lmcachewidth * t;
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
// when blur distance (g_blur) is large, the subsample can be very far from the original lightmap sample
|
||
|
// in some cases such as a thin cylinder, the subsample can even grow into the opposite side
|
||
|
// as a result, when exposed to a directional light, the light on the cylinder may "leak" into
|
||
|
// the opposite dark side this correction limits the effect of blur distance when the normal changes very fast
|
||
|
// this correction will not break the smoothness that HLRAD_GROWSAMPLE ensures
|
||
|
weighting_correction = DotProduct( l->normals[pos], centernormal );
|
||
|
weighting_correction = (weighting_correction > 0) ? weighting_correction * weighting_correction : 0;
|
||
|
weighting = weighting * weighting_correction;
|
||
|
#endif
|
||
|
for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
|
||
|
{
|
||
|
VectorMA( fl->samples[i].light[j], weighting, l->light[pos][j], fl->samples[i].light[j] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorMA( fl->samples[i].deluxe[j], weighting, l->deluxe[pos][j], fl->samples[i].deluxe[j] );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
fl->samples[i].shadow[j] += l->shadow[pos][j] * weighting;
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
subsamples += weighting;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( subsamples > NORMAL_EPSILON )
|
||
|
{
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorCopy( centernormal, fl->samples[i].normal );
|
||
|
#endif
|
||
|
for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
|
||
|
{
|
||
|
VectorScale( fl->samples[i].light[j], (1.0 / subsamples), fl->samples[i].light[j] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorScale( fl->samples[i].deluxe[j], (1.0 / subsamples), fl->samples[i].deluxe[j] );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
fl->samples[i].shadow[j] *= (1.0 / subsamples);
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
// multiply light by shadow to prevent blur artifacts
|
||
|
for( i = 0; i < fl->numsamples; i++ )
|
||
|
{
|
||
|
for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
|
||
|
{
|
||
|
// VectorScale( fl->samples[i].light[j], fl->samples[i].shadow[j], fl->samples[i].light[j] );
|
||
|
// VectorScale( fl->samples[i].deluxe[j], fl->samples[i].shadow[j], fl->samples[i].deluxe[j] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// output occlusion shouldn't be blured
|
||
|
for( i = 0; i < fl->numsamples; i++ )
|
||
|
{
|
||
|
for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
|
||
|
{
|
||
|
int s = (i % w) + l->lmcache_side;
|
||
|
int t = (i / w) + l->lmcache_side;
|
||
|
int pos = s + l->lmcachewidth * t;
|
||
|
fl->samples[i].shadow[j] = l->shadow[pos][j];
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
Mem_Free( l->light );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
Mem_Free( l->normals );
|
||
|
Mem_Free( l->deluxe );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
Mem_Free( l->shadow );
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
InitLightinfo
|
||
|
=============
|
||
|
*/
|
||
|
void InitLightinfo( lightinfo_t *l, int facenum )
|
||
|
{
|
||
|
dface_t *f;
|
||
|
|
||
|
f = &g_dfaces[facenum];
|
||
|
|
||
|
memset( l, 0, sizeof( *l ));
|
||
|
l->plane = GetPlaneFromFace( f );
|
||
|
l->surfnum = facenum;
|
||
|
l->face = f;
|
||
|
|
||
|
CalcFaceVectors( l );
|
||
|
CalcFaceExtents( l );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
BuildFaceLights
|
||
|
=============
|
||
|
*/
|
||
|
void BuildFaceLights( int facenum, int thread )
|
||
|
{
|
||
|
facelight_t *fl = &g_facelight[facenum];
|
||
|
vec3_t normal;
|
||
|
int i, j;
|
||
|
patch_t *p;
|
||
|
dface_t *f;
|
||
|
sample_t *s;
|
||
|
lightinfo_t l;
|
||
|
vec3_t v;
|
||
|
|
||
|
f = &g_dfaces[facenum];
|
||
|
|
||
|
// some surfaces don't need lightmaps
|
||
|
f->lightofs = -1;
|
||
|
for( j = 0; j < MAXLIGHTMAPS; j++ )
|
||
|
f->styles[j] = 255;
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
return; // non-lit texture
|
||
|
|
||
|
if( g_face_patches[facenum] && g_face_patches[facenum]->emitstyle )
|
||
|
f->styles[0] = g_face_patches[facenum]->emitstyle;
|
||
|
|
||
|
InitLightinfo( &l, facenum );
|
||
|
CalcPoints( &l );
|
||
|
CalcLightmap( thread, &l, fl );
|
||
|
VectorCopy( l.plane->normal, normal );
|
||
|
|
||
|
AddSamplesToPatches( thread, fl->samples, f->styles, facenum, &l );
|
||
|
|
||
|
// average up the direct light on each patch for radiosity
|
||
|
for( p = g_face_patches[facenum]; p != NULL; p = p->next )
|
||
|
{
|
||
|
ASSERT( p->faceNumber == facenum );
|
||
|
|
||
|
for( int i = 0; i < MAXLIGHTMAPS && p->totalstyle[i] != 255; i++ )
|
||
|
{
|
||
|
// g-cont. when lightmap will be turned from float to byte some lightvalue will be unreachable
|
||
|
// (1.0f / 255.0f) ~= 0.003, and EQUAL_EPSILON is = 0.004 * 255 = 1.02, minimal brightness of lightmap
|
||
|
if( p->samples[i] <= EQUAL_EPSILON )
|
||
|
p->samples[i] = 0.0;
|
||
|
|
||
|
if( p->samples[i] != 0.0 )
|
||
|
{
|
||
|
vec_t iscale = ( 1.0f / p->samples[i] );
|
||
|
|
||
|
VectorScale( p->samplelight[i], iscale, v );
|
||
|
VectorAdd( p->directlight[i], v, p->directlight[i] );
|
||
|
VectorAdd( p->totallight[i], v, p->totallight[i] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorScale( p->samplelight_dir[i], iscale, v );
|
||
|
VectorAdd( p->directlight_dir[i], v, p->directlight_dir[i] );
|
||
|
VectorAdd( p->totallight_dir[i], v, p->totallight_dir[i] );
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( p = g_face_patches[facenum]; p != NULL; p = p->next )
|
||
|
{
|
||
|
int leafnum = p->leafnum;
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
GatherSampleLight( thread, l.surfnum, p->origin, leafnum, normal, p->totallight, p->totallight_dir, NULL, p->totalstyle, NULL, 1 );
|
||
|
#else
|
||
|
GatherSampleLight( thread, l.surfnum, p->origin, leafnum, normal, p->totallight, NULL, NULL, p->totalstyle, NULL, 1 );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
Mem_Free( l.surfpt );
|
||
|
|
||
|
// add an ambient term if desired
|
||
|
if( g_ambient[0] || g_ambient[1] || g_ambient[2] )
|
||
|
{
|
||
|
for( j = 0; j < MAXLIGHTMAPS && f->styles[j] == 255; j++ );
|
||
|
if( j == MAXLIGHTMAPS ) f->styles[0] = 0; // adding style
|
||
|
|
||
|
for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
|
||
|
{
|
||
|
if( f->styles[j] == 0 )
|
||
|
{
|
||
|
s = fl->samples;
|
||
|
for( i = 0; i < fl->numsamples; i++, s++ )
|
||
|
{
|
||
|
VectorAdd( s->light[j], g_ambient, s->light[j] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
vec_t avg = VectorAvg( g_ambient );
|
||
|
VectorMA( s->deluxe[j], -DIFFUSE_DIRECTION_SCALE * avg, s->normal, s->deluxe[j] );
|
||
|
#endif
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// light from dlight_threshold and above is sent out, but the
|
||
|
// texture itself should still be full bright
|
||
|
if( g_face_patches[facenum] )
|
||
|
{
|
||
|
for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ )
|
||
|
{
|
||
|
if( f->styles[j] == g_face_patches[facenum]->emitstyle )
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( j == MAXLIGHTMAPS )
|
||
|
{
|
||
|
g_overflowed_styles_onpatch[thread]++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( f->styles[j] == 255 )
|
||
|
f->styles[j] = g_face_patches[facenum]->emitstyle;
|
||
|
|
||
|
s = fl->samples;
|
||
|
for( i = 0; i < fl->numsamples; i++, s++ )
|
||
|
{
|
||
|
VectorAdd( s->light[j], g_face_patches[facenum]->baselight, s->light[j] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
vec_t avg = VectorAvg( g_face_patches[facenum]->baselight );
|
||
|
VectorMA( s->deluxe[j], -DIFFUSE_DIRECTION_SCALE * avg, s->normal, s->deluxe[j] );
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
PrecompLightmapOffsets
|
||
|
=============
|
||
|
*/
|
||
|
#ifdef HLRAD_PARANOIA_BUMP
|
||
|
void PrecompLightmapOffsets( void )
|
||
|
{
|
||
|
int lightstyles;
|
||
|
facelight_t *fl;
|
||
|
dface_t *f;
|
||
|
|
||
|
g_shadowdatasize = 0; // unused
|
||
|
g_normaldatasize = 0; // unused
|
||
|
g_numvertnormals = 0; // unused
|
||
|
g_lightdatasize = 0;
|
||
|
lightstyles = 4;
|
||
|
|
||
|
for( int facenum = 0; facenum < g_numfaces; facenum++ )
|
||
|
{
|
||
|
f = &g_dfaces[facenum];
|
||
|
fl = &g_facelight[facenum];
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
continue; // non-lit texture
|
||
|
|
||
|
f->lightofs = g_lightdatasize;
|
||
|
g_lightdatasize += fl->numsamples * 3 * lightstyles;
|
||
|
}
|
||
|
|
||
|
g_dlightdata = (byte *)Mem_Realloc( g_dlightdata, g_lightdatasize );
|
||
|
}
|
||
|
#else
|
||
|
void PrecompLightmapOffsets( void )
|
||
|
{
|
||
|
int overflow_styles_onpatch = 0;
|
||
|
int overflow_styles_onface = 0;
|
||
|
int i, e, facenum;
|
||
|
int lightstyles;
|
||
|
patch_t *patch;
|
||
|
dvertex_t *vert;
|
||
|
facelight_t *fl;
|
||
|
dface_t *f;
|
||
|
|
||
|
g_lightdatasize = g_normaldatasize = 0;
|
||
|
g_numvertnormals = g_numsurfedges; // indexes count is always matched surfedge count
|
||
|
g_numnormals = 1; // leave first normal empty as error
|
||
|
|
||
|
for( facenum = 0; facenum < g_numfaces; facenum++ )
|
||
|
{
|
||
|
vec3_t maxlights1[MAXSTYLES];
|
||
|
vec3_t maxlights2[MAXSTYLES];
|
||
|
vec_t maxlights[MAXSTYLES];
|
||
|
vec3_t phongNormal;
|
||
|
int j, k;
|
||
|
|
||
|
f = &g_dfaces[facenum];
|
||
|
fl = &g_facelight[facenum];
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
{
|
||
|
// NOTE: for non-lit faces all the vertex-normals have index0 (empty normal)
|
||
|
continue; // non-lit texture
|
||
|
}
|
||
|
|
||
|
// write smoothed normals for face
|
||
|
for( i = 0; i < f->numedges; i++ )
|
||
|
{
|
||
|
e = g_dsurfedges[f->firstedge+i];
|
||
|
|
||
|
if( e >= 0 ) vert = g_dvertexes + g_dedges[e].v[0];
|
||
|
else vert = g_dvertexes + g_dedges[-e].v[1];
|
||
|
GetPhongNormal( facenum, vert->point, phongNormal );
|
||
|
g_vertnormals[f->firstedge+i] = StoreNormal( phongNormal );
|
||
|
}
|
||
|
|
||
|
for( j = 0; j < MAXSTYLES; j++ )
|
||
|
{
|
||
|
VectorClear( maxlights1[j] );
|
||
|
VectorClear( maxlights2[j] );
|
||
|
}
|
||
|
|
||
|
for( k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++ )
|
||
|
{
|
||
|
for( i = 0; i < fl->numsamples; i++ )
|
||
|
{
|
||
|
VectorCompareMax( maxlights1[f->styles[k]], fl->samples[i].light[k], maxlights1[f->styles[k]] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int numpatches;
|
||
|
const int *patches;
|
||
|
|
||
|
GetTriangulationPatches( facenum, &numpatches, &patches ); // collect patches and their neighbors
|
||
|
|
||
|
for( i = 0; i < numpatches; i++ )
|
||
|
{
|
||
|
patch = &g_patches[patches[i]];
|
||
|
|
||
|
for( k = 0; k < MAXLIGHTMAPS && patch->totalstyle[k] != 255; k++ )
|
||
|
{
|
||
|
VectorCompareMax( maxlights2[patch->totalstyle[k]], patch->totallight[k], maxlights2[patch->totalstyle[k]] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( j = 0; j < MAXSTYLES; j++ )
|
||
|
{
|
||
|
vec3_t v;
|
||
|
|
||
|
VectorAdd( maxlights1[j], maxlights2[j], v );
|
||
|
maxlights[j] = VectorMaximum( v );
|
||
|
|
||
|
if( maxlights[j] <= EQUAL_EPSILON )
|
||
|
maxlights[j] = 0;
|
||
|
}
|
||
|
|
||
|
byte oldstyles[MAXLIGHTMAPS];
|
||
|
sample_t *oldsamples = (sample_t *)Mem_Alloc( sizeof( sample_t ) * fl->numsamples );
|
||
|
|
||
|
for( k = 0; k < MAXLIGHTMAPS; k++ )
|
||
|
oldstyles[k] = f->styles[k];
|
||
|
|
||
|
// make backup and clear the source
|
||
|
for( k = 0; k < fl->numsamples; k++ )
|
||
|
{
|
||
|
for( j = 0; j < MAXLIGHTMAPS; j++ )
|
||
|
{
|
||
|
VectorCopy( fl->samples[k].light[j], oldsamples[k].light[j] );
|
||
|
VectorClear( fl->samples[k].light[j] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorCopy( fl->samples[k].deluxe[j], oldsamples[k].deluxe[j] );
|
||
|
VectorClear( fl->samples[k].deluxe[j] );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
oldsamples[k].shadow[j] = fl->samples[k].shadow[j];
|
||
|
fl->samples[k].shadow[j] = 0.0f;
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( k = 0; k < MAXLIGHTMAPS; k++ )
|
||
|
{
|
||
|
byte beststyle = 255;
|
||
|
|
||
|
if( k == 0 )
|
||
|
{
|
||
|
beststyle = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vec_t bestmaxlight = 0;
|
||
|
|
||
|
for( j = 1; j < MAXSTYLES; j++ )
|
||
|
{
|
||
|
if( maxlights[j] > bestmaxlight + NORMAL_EPSILON )
|
||
|
{
|
||
|
bestmaxlight = maxlights[j];
|
||
|
beststyle = j;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( beststyle != 255 )
|
||
|
{
|
||
|
maxlights[beststyle] = 0;
|
||
|
f->styles[k] = beststyle;
|
||
|
|
||
|
for( i = 0; i < MAXLIGHTMAPS && oldstyles[i] != 255; i++ )
|
||
|
{
|
||
|
if( oldstyles[i] == f->styles[k] )
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( i < MAXLIGHTMAPS && oldstyles[i] != 255 )
|
||
|
{
|
||
|
for( j = 0; j < fl->numsamples; j++ )
|
||
|
{
|
||
|
VectorCopy( oldsamples[j].light[i], fl->samples[j].light[k] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorCopy( oldsamples[j].deluxe[i], fl->samples[j].deluxe[k] );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
fl->samples[j].shadow[k] = oldsamples[j].shadow[i];
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for( j = 0; j < fl->numsamples; j++ )
|
||
|
{
|
||
|
VectorClear( fl->samples[j].light[k] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorClear( fl->samples[j].deluxe[k] );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
fl->samples[j].shadow[k] = 0.0f;
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f->styles[k] = 255;
|
||
|
|
||
|
for( j = 0; j < fl->numsamples; j++ )
|
||
|
{
|
||
|
VectorClear( fl->samples[j].light[k] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorClear( fl->samples[j].deluxe[k] );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
fl->samples[j].shadow[k] = 0.0f;
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Mem_Free( oldsamples );
|
||
|
|
||
|
for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ )
|
||
|
{
|
||
|
if( f->styles[lightstyles] == 255 )
|
||
|
break; // end if styles
|
||
|
}
|
||
|
|
||
|
if( !lightstyles ) continue;
|
||
|
|
||
|
f->lightofs = g_lightdatasize;
|
||
|
g_lightdatasize += fl->numsamples * 3 * lightstyles;
|
||
|
}
|
||
|
#ifdef HLRAD_LIGHTMAPMODELS
|
||
|
PrecompModelLightmapOffsets();
|
||
|
#endif
|
||
|
// now we have lightdata size and alloc the arrays
|
||
|
// g-cont. we use realloc in case we already have valid lighting data and do it again
|
||
|
if( g_found_extradata )
|
||
|
{
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
g_ddeluxdata = (byte *)Mem_Realloc( g_ddeluxdata, g_lightdatasize );
|
||
|
g_deluxdatasize = g_lightdatasize;
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
g_dshadowdata = (byte *)Mem_Realloc( g_dshadowdata, g_lightdatasize / 3 );
|
||
|
g_shadowdatasize = g_lightdatasize / 3;
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
g_dlightdata = (byte *)Mem_Realloc( g_dlightdata, g_lightdatasize );
|
||
|
|
||
|
// calc normal datasize
|
||
|
g_normaldatasize = sizeof( dnormallump_t ) + ( g_numvertnormals * sizeof( dvertnorm_t )) + (g_numnormals * sizeof( dnormal_t ));
|
||
|
g_dnormaldata = (byte *)Mem_Realloc( g_dnormaldata, g_normaldatasize );
|
||
|
|
||
|
// write indexed normals into memory
|
||
|
byte *buffer = g_dnormaldata;
|
||
|
dnormallump_t *normhdr = (dnormallump_t *)buffer;
|
||
|
|
||
|
normhdr->ident = NORMIDENT;
|
||
|
normhdr->numnormals = g_numnormals; // this is trivialy to compute in engine but i'm leave this count for bounds checking
|
||
|
buffer += sizeof( dnormallump_t );
|
||
|
|
||
|
// store normal indexes
|
||
|
memcpy( buffer, g_vertnormals, g_numvertnormals * sizeof( dvertnorm_t ));
|
||
|
buffer += g_numvertnormals * sizeof( dvertnorm_t );
|
||
|
|
||
|
// store unique normals
|
||
|
memcpy( buffer, g_dnormals, g_numnormals * sizeof( dnormal_t ));
|
||
|
buffer += g_numnormals * sizeof( dnormal_t );
|
||
|
|
||
|
if(( buffer - g_dnormaldata ) != g_normaldatasize )
|
||
|
COM_FatalError( "WriteVertexNormals: memory corrupted\n" );
|
||
|
|
||
|
// now count how many styles was overflowed
|
||
|
for( i = 0; i < MAX_THREADS; i++ )
|
||
|
{
|
||
|
overflow_styles_onpatch += g_overflowed_styles_onpatch[i];
|
||
|
overflow_styles_onface += g_overflowed_styles_onface[i];
|
||
|
}
|
||
|
|
||
|
if( overflow_styles_onface > 0 )
|
||
|
Msg( "^3Warning:^7 too many light styles on a face (%i faces overflowed)\n", overflow_styles_onface );
|
||
|
|
||
|
if( overflow_styles_onpatch > 0 )
|
||
|
Msg( "^3Warning:^7 too many light styles on a patch (%i patches overflowed)\n", overflow_styles_onpatch );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
CreateFacelightDependencyList
|
||
|
============
|
||
|
*/
|
||
|
void CreateFacelightDependencyList( void )
|
||
|
{
|
||
|
facelist_t *item;
|
||
|
facelight_t *fl;
|
||
|
dface_t *f;
|
||
|
|
||
|
for( int i = 0; i < MAX_MAP_FACES; i++ )
|
||
|
g_dependentfacelights[i] = NULL;
|
||
|
|
||
|
// for each face
|
||
|
for( int facenum = 0; facenum < g_numfaces; facenum++ )
|
||
|
{
|
||
|
f = &g_dfaces[facenum];
|
||
|
fl = &g_facelight[facenum];
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
continue;
|
||
|
|
||
|
for( int i = 0; i < fl->numsamples; i++ )
|
||
|
{
|
||
|
int surface = fl->samples[i].surface; // that surface contains at least one sample from this face
|
||
|
|
||
|
if( surface >= 0 && surface < g_numfaces )
|
||
|
{
|
||
|
// insert this face into the dependency list of that surface
|
||
|
for( item = g_dependentfacelights[surface]; item != NULL; item = item->next )
|
||
|
{
|
||
|
if( item->facenum == facenum )
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( item ) continue; // already added?
|
||
|
|
||
|
item = (facelist_t *)Mem_Alloc( sizeof( facelist_t ));
|
||
|
|
||
|
item->next = g_dependentfacelights[surface];
|
||
|
g_dependentfacelights[surface] = item;
|
||
|
item->facenum = facenum;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
FreeFacelightDependencyList
|
||
|
============
|
||
|
*/
|
||
|
void FreeFacelightDependencyList( void )
|
||
|
{
|
||
|
facelist_t *item;
|
||
|
|
||
|
for( int i = 0; i < MAX_MAP_FACES; i++ )
|
||
|
{
|
||
|
while( g_dependentfacelights[i] )
|
||
|
{
|
||
|
item = g_dependentfacelights[i];
|
||
|
g_dependentfacelights[i] = item->next;
|
||
|
Mem_Free( item );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
CalcSampleSize
|
||
|
============
|
||
|
*/
|
||
|
void CalcSampleSize( void )
|
||
|
{
|
||
|
facelight_t *fl;
|
||
|
dface_t *f;
|
||
|
size_t samples_total_size = 0;
|
||
|
|
||
|
for( int facenum = 0; facenum < g_numfaces; facenum++ )
|
||
|
{
|
||
|
f = &g_dfaces[facenum];
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
continue;
|
||
|
|
||
|
fl = &g_facelight[facenum];
|
||
|
samples_total_size += fl->numsamples * sizeof( sample_t );
|
||
|
}
|
||
|
|
||
|
Msg( "total facelight data: %s\n", Q_memprint( samples_total_size ));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
CalcLuxelsCount
|
||
|
============
|
||
|
*/
|
||
|
void CalcLuxelsCount( void )
|
||
|
{
|
||
|
size_t total_luxels = 0;
|
||
|
size_t lighted_luxels = 0;
|
||
|
|
||
|
for( int i = 0; i < MAX_THREADS; i++ )
|
||
|
{
|
||
|
total_luxels += g_direct_luxels[i];
|
||
|
lighted_luxels += g_lighted_luxels[i];
|
||
|
}
|
||
|
Msg( "%d luxels affected by direct light\n", total_luxels );
|
||
|
Msg( "%d luxels reached by direct light\n", lighted_luxels );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
ScaleDirectLights
|
||
|
============
|
||
|
*/
|
||
|
void ScaleDirectLights( void )
|
||
|
{
|
||
|
#ifndef HLRAD_PARANOIA_BUMP
|
||
|
sample_t *samp;
|
||
|
facelight_t *fl;
|
||
|
dface_t *f;
|
||
|
|
||
|
for( int facenum = 0; facenum < g_numfaces; facenum++ )
|
||
|
{
|
||
|
f = &g_dfaces[facenum];
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
continue;
|
||
|
|
||
|
fl = &g_facelight[facenum];
|
||
|
|
||
|
for( int k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++ )
|
||
|
{
|
||
|
for( int i = 0; i < fl->numsamples; i++ )
|
||
|
{
|
||
|
samp = &fl->samples[i];
|
||
|
VectorScale( samp->light[k], g_direct_scale, samp->light[k] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorScale( samp->deluxe[k], g_direct_scale, samp->deluxe[k] );
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
#ifdef HLRAD_LIGHTMAPMODELS
|
||
|
ScaleModelDirectLights();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
FacePatchLights
|
||
|
|
||
|
This function is run multithreaded
|
||
|
============
|
||
|
*/
|
||
|
void FacePatchLights( int facenum, int threadnum )
|
||
|
{
|
||
|
dface_t *f_other;
|
||
|
facelight_t *fl_other;
|
||
|
vec3_t v, v_dir;
|
||
|
facelist_t *item;
|
||
|
sample_t *samp;
|
||
|
dface_t *f;
|
||
|
|
||
|
f = &g_dfaces[facenum];
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
return;
|
||
|
|
||
|
for( item = g_dependentfacelights[facenum]; item != NULL; item = item->next )
|
||
|
{
|
||
|
f_other = &g_dfaces[item->facenum];
|
||
|
fl_other = &g_facelight[item->facenum];
|
||
|
|
||
|
for( int k = 0; k < MAXLIGHTMAPS && f_other->styles[k] != 255; k++ )
|
||
|
{
|
||
|
for( int i = 0; i < fl_other->numsamples; i++ )
|
||
|
{
|
||
|
samp = &fl_other->samples[i];
|
||
|
|
||
|
if( samp->surface != facenum )
|
||
|
{
|
||
|
// the sample is not in this surface
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
int style = f_other->styles[k];
|
||
|
InterpolateSampleLight( samp->pos, samp->surface, 1, &style, &v, &v_dir );
|
||
|
#ifdef HLRAD_PARANOIA_BUMP
|
||
|
if( f_other->styles[k] == STYLE_ORIGINAL_LIGHT )
|
||
|
{
|
||
|
VectorAdd( samp->light[STYLE_ORIGINAL_LIGHT], v, samp->light[STYLE_ORIGINAL_LIGHT] );
|
||
|
VectorAdd( samp->light[STYLE_INDIRECT_LIGHT], v, samp->light[STYLE_INDIRECT_LIGHT] );
|
||
|
}
|
||
|
else if( f_other->styles[k] == STYLE_BUMPED_LIGHT )
|
||
|
{
|
||
|
// store indirect light into separate style
|
||
|
VectorAdd( samp->deluxe[STYLE_BUMPED_LIGHT], v_dir, samp->deluxe[STYLE_BUMPED_LIGHT] );
|
||
|
}
|
||
|
// only STYLE_ORIGINAL_LIGHT and STYLE_BUMPED_LIGHT are handled
|
||
|
#else
|
||
|
VectorScale( v, g_indirect_scale, v );
|
||
|
VectorAdd( samp->light[k], v, v );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorScale( v_dir, g_indirect_scale, v_dir );
|
||
|
VectorAdd( samp->deluxe[k], v_dir, v_dir );
|
||
|
#endif
|
||
|
if( VectorMaximum( v ) >= EQUAL_EPSILON )
|
||
|
{
|
||
|
VectorCopy( v, samp->light[k] );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorCopy( v_dir, samp->deluxe[k] );
|
||
|
#endif
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
FinalLightFace
|
||
|
|
||
|
Add the indirect lighting on top of the direct
|
||
|
lighting and save into final map format
|
||
|
=============
|
||
|
*/
|
||
|
#ifdef HLRAD_PARANOIA_BUMP
|
||
|
void ApplyLightGamma( vec3_t lb )
|
||
|
{
|
||
|
// clip from the top
|
||
|
if( lb[0] > g_maxlight || lb[1] > g_maxlight || lb[2] > g_maxlight )
|
||
|
{
|
||
|
// find max value and scale the whole color down;
|
||
|
float max = VectorMax( lb );
|
||
|
|
||
|
for( int i = 0; i < 3; i++ )
|
||
|
lb[i] = ( lb[i] * g_maxlight ) / max;
|
||
|
}
|
||
|
|
||
|
// do gamma adjust
|
||
|
if( g_gammamode == 0 )
|
||
|
{
|
||
|
lb[0] = (float)pow( lb[0] / 256.0f, g_gamma ) * 256.0f;
|
||
|
lb[1] = (float)pow( lb[1] / 256.0f, g_gamma ) * 256.0f;
|
||
|
lb[2] = (float)pow( lb[2] / 256.0f, g_gamma ) * 256.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float avg, avgbefore;
|
||
|
|
||
|
if( g_gammamode == 2 )
|
||
|
avg = VectorMaximum( lb );
|
||
|
else avg = VectorAvg( lb );
|
||
|
avgbefore = avg;
|
||
|
|
||
|
if( avgbefore )
|
||
|
{
|
||
|
avg = (float)pow( avg / 256.0f, g_gamma ) * 256.0f;
|
||
|
avg = avg / avgbefore;
|
||
|
VectorScale( lb, avg, lb );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PackColor( vec3_t lb, int facenum, int style, int sample )
|
||
|
{
|
||
|
dface_t *f = &g_dfaces[facenum];
|
||
|
facelight_t *fl = &g_facelight[facenum];
|
||
|
|
||
|
// check
|
||
|
for( int i = 0; i < 3; i++ )
|
||
|
{
|
||
|
if( lb[i] < 0.0f ) MsgDev( D_WARN, "component %d on style %d is too low: %f\n", i, style, lb[i] );
|
||
|
if( lb[i] > 256.0f ) MsgDev( D_WARN, "component %d on style %d is too high: %f\n", i, style, lb[i] );
|
||
|
}
|
||
|
|
||
|
g_dlightdata[f->lightofs + style * fl->numsamples * 3 + sample * 3 + 0] = (byte)lb[0];
|
||
|
g_dlightdata[f->lightofs + style * fl->numsamples * 3 + sample * 3 + 1] = (byte)lb[1];
|
||
|
g_dlightdata[f->lightofs + style * fl->numsamples * 3 + sample * 3 + 2] = (byte)lb[2];
|
||
|
}
|
||
|
|
||
|
void FinalLightFace( int facenum, int threadnum )
|
||
|
{
|
||
|
vec_t lmvecs[2][4];
|
||
|
sample_t *samp;
|
||
|
dtexinfo_t *tx;
|
||
|
facelight_t *fl;
|
||
|
dface_t *f;
|
||
|
|
||
|
f = &g_dfaces[facenum];
|
||
|
fl = &g_facelight[facenum];
|
||
|
tx = &g_texinfo[f->texinfo];
|
||
|
samp = fl->samples;
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
return; // non-lit texture
|
||
|
|
||
|
f->styles[0] = 0; // everyone gets the style zero map.
|
||
|
f->styles[BUMP_LIGHTVECS_MAP] = BUMP_LIGHTVECS_STYLE;
|
||
|
f->styles[BUMP_BASELIGHT_MAP] = BUMP_BASELIGHT_STYLE;
|
||
|
f->styles[BUMP_ADDLIGHT_MAP] = BUMP_ADDLIGHT_STYLE;
|
||
|
|
||
|
// we assume what all the four styles is always present
|
||
|
LightMatrixFromTexMatrix( tx, lmvecs );
|
||
|
|
||
|
for( int j = 0; j < fl->numsamples; j++, samp++ )
|
||
|
{
|
||
|
const vec3_t &facenormal = GetPlaneFromFace( f )->normal;
|
||
|
vec3_t directionnormals[3];
|
||
|
vec3_t texdirections[2];
|
||
|
vec3_t original_light; // direct + indirect
|
||
|
vec3_t light_deluxe; // deluxemap
|
||
|
vec3_t light_diffuse; // direct light
|
||
|
vec3_t light_indirect; // radiosity
|
||
|
vec3_t direction;
|
||
|
int side;
|
||
|
|
||
|
VectorCopy( samp->normal, directionnormals[2] );
|
||
|
|
||
|
for( side = 0; side < 2; side++ )
|
||
|
{
|
||
|
CrossProduct( facenormal, lmvecs[!side], texdirections[side] );
|
||
|
VectorNormalize( texdirections[side] );
|
||
|
if( DotProduct( texdirections[side], lmvecs[side]) < 0.0f )
|
||
|
VectorNegate( texdirections[side], texdirections[side] );
|
||
|
}
|
||
|
|
||
|
for( side = 0; side < 2; side++ )
|
||
|
{
|
||
|
vec_t dot = DotProduct( texdirections[side], samp->normal );
|
||
|
VectorMA( texdirections[side], -dot, samp->normal, directionnormals[side] );
|
||
|
VectorNormalize( directionnormals[side] );
|
||
|
}
|
||
|
|
||
|
// VectorNegate( directionnormals[1], directionnormals[1] );
|
||
|
|
||
|
// prepare four styles
|
||
|
VectorScale( samp->light[STYLE_ORIGINAL_LIGHT], g_direct_scale, original_light );
|
||
|
VectorScale( samp->light[STYLE_BUMPED_LIGHT], g_direct_scale, light_diffuse );
|
||
|
VectorScale( samp->light[STYLE_INDIRECT_LIGHT], g_direct_scale, light_indirect );
|
||
|
VectorScale( samp->deluxe[STYLE_BUMPED_LIGHT], g_direct_scale, direction );
|
||
|
VectorNegate( direction, direction ); // let the direction point from face sample to light source
|
||
|
VectorNormalize( direction );
|
||
|
|
||
|
ApplyLightGamma( original_light ); // apply standard gamma rules
|
||
|
ApplyLightGamma( light_indirect ); // apply standard gamma rules
|
||
|
ApplyLightGamma( light_diffuse ); // apply standard gamma rules
|
||
|
|
||
|
// buz - divide by two bump styles.
|
||
|
// this decreases precision, but increases range
|
||
|
VectorScale( light_indirect, 0.5f, light_indirect );
|
||
|
VectorScale( light_diffuse, 0.5f, light_diffuse );
|
||
|
|
||
|
// turn to tangent space
|
||
|
for( int x = 0; x < 3; x++ )
|
||
|
{
|
||
|
light_deluxe[x] = DotProduct( direction, directionnormals[x] ) * 127.0f + 128.0f;
|
||
|
light_deluxe[x] = bound( 0, light_deluxe[x], 255.0 );
|
||
|
}
|
||
|
|
||
|
PackColor( original_light, facenum, 0, j );
|
||
|
PackColor( light_indirect, facenum, 1, j );
|
||
|
PackColor( light_deluxe, facenum, 2, j );
|
||
|
PackColor( light_diffuse, facenum, 3, j );
|
||
|
}
|
||
|
}
|
||
|
#else
|
||
|
void FinalLightFace( int facenum, int threadnum )
|
||
|
{
|
||
|
vec_t lmvecs[2][4];
|
||
|
int lightstyles;
|
||
|
float minlight;
|
||
|
int i, j, k;
|
||
|
sample_t *samp;
|
||
|
dtexinfo_t *tx;
|
||
|
facelight_t *fl;
|
||
|
dface_t *f;
|
||
|
vec3_t lb;
|
||
|
|
||
|
f = &g_dfaces[facenum];
|
||
|
fl = &g_facelight[facenum];
|
||
|
tx = &g_texinfo[f->texinfo];
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
return; // non-lit texture
|
||
|
|
||
|
for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ )
|
||
|
{
|
||
|
if( f->styles[lightstyles] == 255 )
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( !lightstyles ) return;
|
||
|
|
||
|
LightMatrixFromTexMatrix( tx, lmvecs );
|
||
|
|
||
|
minlight = FloatForKey( g_face_entity[facenum], "_minlight" );
|
||
|
if( minlight < 1.0 ) minlight *= 128.0f; // GoldSrc
|
||
|
else minlight *= 0.5f; // Quake
|
||
|
|
||
|
if( g_lightbalance )
|
||
|
minlight *= g_direct_scale;
|
||
|
if( g_numbounce > 0 ) minlight = 0.0f; // ignore for radiosity
|
||
|
|
||
|
for( k = 0; k < lightstyles; k++ )
|
||
|
{
|
||
|
samp = fl->samples;
|
||
|
|
||
|
for( j = 0; j < fl->numsamples; j++, samp++ )
|
||
|
{
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
const vec3_t &facenormal = GetPlaneFromFace( f )->normal;
|
||
|
vec3_t directionnormals[3];
|
||
|
vec3_t texdirections[2];
|
||
|
vec3_t direction;
|
||
|
int side;
|
||
|
vec3_t v;
|
||
|
|
||
|
VectorCopy( samp->normal, directionnormals[2] );
|
||
|
|
||
|
for( side = 0; side < 2; side++ )
|
||
|
{
|
||
|
CrossProduct( facenormal, lmvecs[!side], texdirections[side] );
|
||
|
VectorNormalize( texdirections[side] );
|
||
|
if( DotProduct( texdirections[side], lmvecs[side]) < 0.0f )
|
||
|
VectorNegate( texdirections[side], texdirections[side] );
|
||
|
}
|
||
|
|
||
|
for( side = 0; side < 2; side++ )
|
||
|
{
|
||
|
vec_t dot = DotProduct( texdirections[side], samp->normal );
|
||
|
VectorMA( texdirections[side], -dot, samp->normal, directionnormals[side] );
|
||
|
VectorNormalize( directionnormals[side] );
|
||
|
}
|
||
|
VectorNegate( directionnormals[1], directionnormals[1] );
|
||
|
#endif
|
||
|
VectorCopy( samp->light[k], lb );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
VectorCopy( samp->deluxe[k], direction );
|
||
|
vec_t avg = VectorAvg( lb );
|
||
|
VectorScale( direction, 1.0 / Q_max( 1.0, avg ), direction );
|
||
|
#endif
|
||
|
// clip from the bottom first
|
||
|
lb[0] = Q_max( lb[0], minlight );
|
||
|
lb[1] = Q_max( lb[1], minlight );
|
||
|
lb[2] = Q_max( lb[2], minlight );
|
||
|
|
||
|
// clip from the top
|
||
|
if( lb[0] > g_maxlight || lb[1] > g_maxlight || lb[2] > g_maxlight )
|
||
|
{
|
||
|
// find max value and scale the whole color down;
|
||
|
float max = VectorMax( lb );
|
||
|
|
||
|
for( i = 0; i < 3; i++ )
|
||
|
lb[i] = ( lb[i] * g_maxlight ) / max;
|
||
|
}
|
||
|
|
||
|
// do gamma adjust
|
||
|
lb[0] = (float)pow( lb[0] / 256.0f, g_gamma ) * 256.0f;
|
||
|
lb[1] = (float)pow( lb[1] / 256.0f, g_gamma ) * 256.0f;
|
||
|
lb[2] = (float)pow( lb[2] / 256.0f, g_gamma ) * 256.0f;
|
||
|
|
||
|
#ifdef HLRAD_RIGHTROUND // when you go down, when you go down down!
|
||
|
g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = Q_rint( lb[0] );
|
||
|
g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = Q_rint( lb[1] );
|
||
|
g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = Q_rint( lb[2] );
|
||
|
#else
|
||
|
g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = (byte)lb[0];
|
||
|
g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = (byte)lb[1];
|
||
|
g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = (byte)lb[2];
|
||
|
#endif
|
||
|
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
if( g_deluxdatasize )
|
||
|
{
|
||
|
VectorScale( direction, 0.225, v ); // the scale is calculated such that length( v ) < 1
|
||
|
|
||
|
if( DotProduct( v, v ) > ( 1.0 - NORMAL_EPSILON ))
|
||
|
VectorNormalize( v );
|
||
|
|
||
|
VectorNegate( v, v ); // let the direction point from face sample to light source
|
||
|
|
||
|
for( int x = 0; x < 3; x++ )
|
||
|
{
|
||
|
lb[x] = DotProduct( v, directionnormals[x] ) * 127.0f + 128.0f;
|
||
|
lb[x] = bound( 0, lb[x], 255.0 );
|
||
|
}
|
||
|
|
||
|
g_ddeluxdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = (byte)lb[0];
|
||
|
g_ddeluxdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = (byte)lb[1];
|
||
|
g_ddeluxdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = (byte)lb[2];
|
||
|
}
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
if( g_shadowdatasize )
|
||
|
g_dshadowdata[(f->lightofs / 3) + k * fl->numsamples + j] = (byte)samp->shadow[k] * 255;
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
void FreeFaceLights( void )
|
||
|
{
|
||
|
for( int i = 0; i < g_numfaces; i++ )
|
||
|
{
|
||
|
Mem_Free( g_facelight[i].samples );
|
||
|
}
|
||
|
#ifdef HLRAD_LIGHTMAPMODELS
|
||
|
FreeModelFaceLights();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void ReduceLightmap( void )
|
||
|
{
|
||
|
// store compiler settings to help matching with dynamic lighting
|
||
|
SetKeyValue( &g_entities[0], "_lightgamma", va( "%.2f", g_gamma ));
|
||
|
SetKeyValue( &g_entities[0], "_dscale", va( "%.2f", g_direct_scale ));
|
||
|
SetKeyValue( &g_entities[0], "_ambient", va( "%.2f %.2f %.2f", g_ambient[0], g_ambient[1], g_ambient[2] ));
|
||
|
SetKeyValue( &g_entities[0], "_maxlight", va( "%.2f", g_maxlight ));
|
||
|
SetKeyValue( &g_entities[0], "_smooth", va( "%.2f", g_smoothvalue ));
|
||
|
|
||
|
int oldsize = g_lightdatasize;
|
||
|
|
||
|
byte *oldlightdata = (byte *)Mem_Alloc( g_lightdatasize, C_SAFEALLOC );
|
||
|
|
||
|
if( !oldlightdata )
|
||
|
{
|
||
|
MsgDev( D_WARN, "failed to reduce lightmap due to failed to allocate memory for lightdata\n" );
|
||
|
return;
|
||
|
}
|
||
|
memcpy( oldlightdata, g_dlightdata, g_lightdatasize );
|
||
|
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
byte *olddeluxdata = (byte *)Mem_Alloc( g_deluxdatasize, C_SAFEALLOC );
|
||
|
|
||
|
if( !olddeluxdata )
|
||
|
{
|
||
|
MsgDev( D_WARN, "failed to reduce lightmap due to failed to allocate memory for deluxdata\n" );
|
||
|
Mem_Free( oldlightdata, C_SAFEALLOC );
|
||
|
return;
|
||
|
}
|
||
|
memcpy( olddeluxdata, g_ddeluxdata, g_deluxdatasize );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
byte *oldshadowdata = (byte *)Mem_Alloc( g_shadowdatasize, C_SAFEALLOC );
|
||
|
|
||
|
if( !oldshadowdata )
|
||
|
{
|
||
|
MsgDev( D_WARN, "failed to reduce lightmap due to failed to allocate memory for shadowdata\n" );
|
||
|
Mem_Free( oldlightdata, C_SAFEALLOC );
|
||
|
Mem_Free( olddeluxdata, C_SAFEALLOC );
|
||
|
return;
|
||
|
}
|
||
|
memcpy( oldshadowdata, g_dshadowdata, g_shadowdatasize );
|
||
|
#endif
|
||
|
#endif
|
||
|
g_lightdatasize = 0;
|
||
|
g_deluxdatasize = 0;
|
||
|
g_shadowdatasize = 0;
|
||
|
|
||
|
for( int facenum = 0; facenum < g_numfaces; facenum++ )
|
||
|
{
|
||
|
dface_t *f = &g_dfaces[facenum];
|
||
|
facelight_t *fl = &g_facelight[facenum];
|
||
|
|
||
|
if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ))
|
||
|
continue; // non-lit texture
|
||
|
|
||
|
if( f->lightofs == -1 )
|
||
|
continue;
|
||
|
|
||
|
byte oldstyles[MAXLIGHTMAPS];
|
||
|
int numstyles = 0;
|
||
|
int i, k, oldofs;
|
||
|
|
||
|
oldofs = f->lightofs;
|
||
|
f->lightofs = g_lightdatasize;
|
||
|
|
||
|
for( k = 0; k < MAXLIGHTMAPS; k++ )
|
||
|
{
|
||
|
oldstyles[k] = f->styles[k];
|
||
|
f->styles[k] = 255;
|
||
|
}
|
||
|
|
||
|
for( k = 0; k < MAXLIGHTMAPS && oldstyles[k] != 255; k++ )
|
||
|
{
|
||
|
int count = fl->numsamples;
|
||
|
byte maxb = 0;
|
||
|
|
||
|
for( i = 0; i < count; i++ )
|
||
|
{
|
||
|
byte *v = &oldlightdata[oldofs + count * 3 * k + i * 3];
|
||
|
maxb = Q_max( maxb, VectorMaximum( v ));
|
||
|
}
|
||
|
|
||
|
if( maxb <= 0 ) // black
|
||
|
continue;
|
||
|
|
||
|
f->styles[numstyles] = oldstyles[k];
|
||
|
memcpy( &g_dlightdata[f->lightofs + count * 3 * numstyles], &oldlightdata[oldofs + count * 3 * k], count * 3 );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
if( g_ddeluxdata != NULL )
|
||
|
memcpy( &g_ddeluxdata[f->lightofs + count * 3 * numstyles], &olddeluxdata[oldofs + count * 3 * k], count * 3 );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
if( g_dshadowdata != NULL )
|
||
|
memcpy( &g_dshadowdata[(f->lightofs/3) + count * numstyles], &oldshadowdata[(oldofs / 3) + count * k], count );
|
||
|
#endif
|
||
|
#endif
|
||
|
numstyles++;
|
||
|
}
|
||
|
g_lightdatasize += fl->numsamples * 3 * numstyles;
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
if( g_ddeluxdata != NULL )
|
||
|
g_deluxdatasize += fl->numsamples * 3 * numstyles;
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
if( g_dshadowdata != NULL )
|
||
|
g_shadowdatasize += fl->numsamples * numstyles;
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#ifdef HLRAD_LIGHTMAPMODELS
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
ReduceModelLightmap( oldlightdata, olddeluxdata, oldshadowdata );
|
||
|
#else
|
||
|
ReduceModelLightmap( oldlightdata, olddeluxdata, NULL );
|
||
|
#endif
|
||
|
#else
|
||
|
ReduceModelLightmap( oldlightdata, NULL, NULL );
|
||
|
#endif
|
||
|
#endif
|
||
|
Mem_Free( oldlightdata, C_SAFEALLOC );
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
Mem_Free( olddeluxdata, C_SAFEALLOC );
|
||
|
#ifdef HLRAD_SHADOWMAPPING
|
||
|
Mem_Free( oldshadowdata, C_SAFEALLOC );
|
||
|
#endif
|
||
|
#endif
|
||
|
int newsize = g_lightdatasize;
|
||
|
|
||
|
if( oldsize != newsize )
|
||
|
{
|
||
|
#ifdef HLRAD_DELUXEMAPPING
|
||
|
Msg( "Reduce lightmap from %s to %s\n", Q_memprint( oldsize * 2 + ( oldsize / 3 )), Q_memprint( newsize * 2 + ( newsize / 3 )));
|
||
|
#else
|
||
|
Msg( "Reduce lightmap from %s to %s\n", Q_memprint( oldsize + ( oldsize / 3 )), Q_memprint( newsize + ( newsize / 3 )));
|
||
|
#endif
|
||
|
}
|
||
|
}
|