/*** * * 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 } }