2721 lines
68 KiB
C++
2721 lines
68 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
|
|
*
|
|
* This product contains software technology licensed from Id
|
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
|
* All Rights Reserved.
|
|
*
|
|
****/
|
|
|
|
// qrad.c
|
|
|
|
#include "qrad.h"
|
|
|
|
/*
|
|
NOTES
|
|
-----
|
|
every surface must be divided into at least two patches each axis
|
|
*/
|
|
|
|
patch_t *g_face_patches[MAX_MAP_FACES];
|
|
entity_t *g_face_entity[MAX_MAP_FACES];
|
|
vec_t *g_skynormalsizes[SKYLEVELMAX+1];
|
|
int g_numskynormals[SKYLEVELMAX+1];
|
|
vec3_t *g_skynormals[SKYLEVELMAX+1];
|
|
patch_t *g_patches;
|
|
uint g_num_patches;
|
|
static vec3_t (*emitlight)[MAXLIGHTMAPS];
|
|
static vec3_t (*addlight)[MAXLIGHTMAPS];
|
|
static byte (*newstyles)[MAXLIGHTMAPS];
|
|
#ifdef HLRAD_DELUXEMAPPING
|
|
static vec3_t (*emitlight_dir)[MAXLIGHTMAPS];
|
|
static vec3_t (*addlight_dir)[MAXLIGHTMAPS];
|
|
#endif
|
|
vec3_t g_face_offset[MAX_MAP_FACES]; // for rotating bmodels
|
|
dplane_t g_backplanes[MAX_MAP_PLANES]; // equal to MAX_MAP_FACES, there is no errors
|
|
int g_overflowed_styles_onface[MAX_THREADS];
|
|
int g_overflowed_styles_onpatch[MAX_THREADS];
|
|
int g_direct_luxels[MAX_THREADS];
|
|
int g_lighted_luxels[MAX_THREADS];
|
|
vec3_t g_reflectivity[MAX_MAP_TEXTURES];
|
|
bool g_texture_init[MAX_MAP_TEXTURES];
|
|
static winding_t *g_windingArray[MAX_SUBDIVIDE];
|
|
static uint g_numwindings = 0;
|
|
static size_t g_transfer_data_bytes;
|
|
size_t g_transfer_data_size[MAX_THREADS];
|
|
uint g_numbounce = DEFAULT_BOUNCE; // originally this was 8
|
|
vec_t g_chop = DEFAULT_CHOP;
|
|
vec_t g_texchop = DEFAULT_TEXCHOP;
|
|
vec_t g_smoothvalue = DEFAULT_SMOOTHVALUE;
|
|
float g_totalarea;
|
|
bool g_fastmode = DEFAULT_FASTMODE;
|
|
vec_t g_direct_scale = DEFAULT_DLIGHT_SCALE;
|
|
vec_t g_indirect_scale = DEFAULT_LIGHT_SCALE;
|
|
vec_t g_blur = DEFAULT_BLUR;
|
|
vec_t g_gamma = DEFAULT_GAMMA;
|
|
bool g_drawsample = false;
|
|
vec3_t g_ambient = { 0, 0, 0 };
|
|
float g_maxlight = DEFAULT_LIGHTCLIP; // originally this was 196
|
|
float g_indirect_sun = DEFAULT_INDIRECT_SUN;
|
|
bool g_dirtmapping = DEFAULT_DIRTMAPPING;
|
|
bool g_lerp_enabled = DEFAULT_LERP_ENABLED;
|
|
bool g_extra = DEFAULT_EXTRAMODE;
|
|
bool g_nomodelshadow = false;
|
|
bool g_lightbalance = false;
|
|
bool g_onlylights = false;
|
|
float g_smoothing_threshold; // cosine of smoothing angle(in radians)
|
|
char source[MAX_PATH] = "";
|
|
uint g_gammamode = DEFAULT_GAMMAMODE;
|
|
|
|
static char global_lights[MAX_PATH] = "";
|
|
static char level_lights[MAX_PATH] = "";
|
|
|
|
// pre-quantized table normals from Quake1
|
|
vec_t g_anorms[NUMVERTEXNORMALS][3] =
|
|
{
|
|
#include "anorms.h"
|
|
};
|
|
|
|
/*
|
|
===================================================================
|
|
|
|
MISC
|
|
|
|
===================================================================
|
|
*/
|
|
const dplane_t *GetPlaneFromFace( const dface_t *face )
|
|
{
|
|
ASSERT( face != NULL );
|
|
|
|
if( face->side )
|
|
return &g_backplanes[face->planenum];
|
|
return &g_dplanes[face->planenum];
|
|
}
|
|
|
|
const dplane_t *GetPlaneFromFace( const uint facenum )
|
|
{
|
|
ASSERT( facenum < MAX_MAP_FACES );
|
|
|
|
dface_t *face = &g_dfaces[facenum];
|
|
|
|
if( face->side )
|
|
return &g_backplanes[face->planenum];
|
|
return &g_dplanes[face->planenum];
|
|
}
|
|
|
|
dleaf_t *PointInLeaf( const vec3_t point )
|
|
{
|
|
int nodenum = 0;
|
|
|
|
while( nodenum >= 0 )
|
|
{
|
|
dnode_t *node = &g_dnodes[nodenum];
|
|
|
|
if( PlaneDiff( point, &g_dplanes[node->planenum] ) > 0 )
|
|
nodenum = node->children[0];
|
|
else nodenum = node->children[1];
|
|
}
|
|
|
|
return &g_dleafs[-nodenum - 1];
|
|
}
|
|
|
|
dleaf_t *PointInLeaf_r( int nodenum, const vec3_t point )
|
|
{
|
|
dplane_t *plane;
|
|
dnode_t *node;
|
|
vec_t dist;
|
|
|
|
while( nodenum >= 0 )
|
|
{
|
|
node = &g_dnodes[nodenum];
|
|
plane = &g_dplanes[node->planenum];
|
|
dist = DotProduct( point, plane->normal ) - plane->dist;
|
|
|
|
if( dist > HUNT_WALL_EPSILON )
|
|
{
|
|
nodenum = node->children[0];
|
|
}
|
|
else if( dist < -HUNT_WALL_EPSILON )
|
|
{
|
|
nodenum = node->children[1];
|
|
}
|
|
else
|
|
{
|
|
dleaf_t *result[2];
|
|
|
|
result[0] = PointInLeaf_r( node->children[0], point );
|
|
result[1] = PointInLeaf_r( node->children[1], point );
|
|
|
|
if( result[0] == g_dleafs || result[0]->contents == CONTENTS_SOLID )
|
|
return result[0];
|
|
|
|
if( result[1] == g_dleafs || result[1]->contents == CONTENTS_SOLID )
|
|
return result[1];
|
|
|
|
if( result[0]->contents == CONTENTS_SKY )
|
|
return result[0];
|
|
|
|
if( result[1]->contents == CONTENTS_SKY )
|
|
return result[1];
|
|
|
|
return result[0];
|
|
}
|
|
}
|
|
|
|
return &g_dleafs[-nodenum - 1];
|
|
}
|
|
|
|
dleaf_t *PointInLeaf2( const vec3_t point )
|
|
{
|
|
return PointInLeaf_r( 0, point );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
GetVisCache
|
|
|
|
decompress visibility string
|
|
==============
|
|
*/
|
|
int GetVisCache( int lastoffset, int offset, byte *pvs )
|
|
{
|
|
// get the PVS for the pos to limit the number of checks
|
|
if( !g_visdatasize )
|
|
{
|
|
if( offset != lastoffset )
|
|
{
|
|
memset( pvs, 255, (g_numvisleafs + 7) / 8 );
|
|
lastoffset = -1;
|
|
}
|
|
}
|
|
else if( offset != lastoffset )
|
|
{
|
|
if( offset == -1 ) memset( pvs, 0, (g_numvisleafs + 7) / 8 );
|
|
else DecompressVis( &g_dvisdata[offset], pvs );
|
|
lastoffset = offset;
|
|
}
|
|
|
|
return lastoffset;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
SetDLightVis
|
|
|
|
Init dlight visibility
|
|
==============
|
|
*/
|
|
void SetDLightVis( directlight_t *dl, int leafnum )
|
|
{
|
|
if( !dl->pvs )
|
|
dl->pvs = (byte *)Mem_Alloc( (g_numvisleafs + 7) / 8 );
|
|
GetVisCache( -2, g_dleafs[leafnum].visofs, dl->pvs );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
MergeDLightVis
|
|
|
|
Merge dlight vis with another leaf
|
|
==============
|
|
*/
|
|
void MergeDLightVis( directlight_t *dl, int leafnum )
|
|
{
|
|
if( !dl->pvs )
|
|
{
|
|
SetDLightVis( dl, leafnum );
|
|
}
|
|
else
|
|
{
|
|
byte pvs[(MAX_MAP_LEAFS + 7) / 8];
|
|
|
|
GetVisCache( -2, g_dleafs[leafnum].visofs, pvs );
|
|
|
|
// merge both vis graphs
|
|
for( int i = 0; i < (g_numvisleafs + 7) / 8; i++ )
|
|
dl->pvs[i] |= pvs[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
PatchPlaneDist
|
|
|
|
Fixes up patch planes for brush models with an origin brush
|
|
==============
|
|
*/
|
|
vec_t PatchPlaneDist( patch_t *patch )
|
|
{
|
|
const dplane_t *plane = GetPlaneFromFace( patch->faceNumber );
|
|
|
|
return DotProduct( g_face_offset[patch->faceNumber], plane->normal ) + plane->dist;
|
|
}
|
|
|
|
bool PvsForOrigin( const vec3_t org, byte *pvs )
|
|
{
|
|
dleaf_t *leaf;
|
|
|
|
if( !g_visdatasize )
|
|
{
|
|
memset( pvs, 255, (g_numvisleafs + 7) / 8 );
|
|
return true;
|
|
}
|
|
|
|
leaf = PointInLeaf( org );
|
|
if( leaf->visofs == -1 )
|
|
COM_FatalError( "leaf->visofs == -1\n" );
|
|
|
|
if( leaf->contents == CONTENTS_SOLID )
|
|
return false;
|
|
|
|
DecompressVis( &g_dvisdata[leaf->visofs], pvs );
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
HuntForWorld
|
|
|
|
never return CONTENTS_SKY or CONTENTS_SOLID leafs
|
|
=============
|
|
*/
|
|
dleaf_t *HuntForWorld( vec3_t point, const vec3_t plane_offset, const dplane_t *plane, int hunt_size, vec_t hunt_scale, vec_t hunt_offset )
|
|
{
|
|
vec3_t current_point;
|
|
vec3_t original_point;
|
|
dleaf_t *best_leaf = NULL;
|
|
vec_t best_dist = 99999999.0;
|
|
dplane_t new_plane = *plane;
|
|
int a, x, y, z;
|
|
vec3_t best_point;
|
|
vec3_t scales;
|
|
dleaf_t *leaf;
|
|
|
|
scales[0] = 0.0;
|
|
scales[1] = -hunt_scale;
|
|
scales[2] = hunt_scale;
|
|
|
|
VectorCopy( point, best_point );
|
|
VectorCopy( point, original_point );
|
|
|
|
new_plane.dist += DotProduct( plane->normal, plane_offset );
|
|
|
|
for( a = 0; a < hunt_size; a++ )
|
|
{
|
|
for( x = 0; x < 3; x++ )
|
|
{
|
|
current_point[0] = original_point[0] + (scales[x % 3] * a);
|
|
|
|
for( y = 0; y < 3; y++ )
|
|
{
|
|
current_point[1] = original_point[1] + (scales[y % 3] * a);
|
|
|
|
for( z = 0; z < 3; z++ )
|
|
{
|
|
if( a == 0 && ( x || y || z ))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
vec3_t delta;
|
|
vec_t dist;
|
|
|
|
current_point[2] = original_point[2] + (scales[z % 3] * a);
|
|
|
|
dist = DotProduct( current_point, new_plane.normal ) - new_plane.dist - hunt_offset;
|
|
VectorMA( current_point, -dist, new_plane.normal, current_point );
|
|
VectorSubtract( current_point, original_point, delta );
|
|
dist = DotProduct( delta, delta );
|
|
|
|
if( dist < best_dist )
|
|
{
|
|
if(( leaf = PointInLeaf2( current_point )) != g_dleafs )
|
|
{
|
|
if(( leaf->contents != CONTENTS_SKY ) && ( leaf->contents != CONTENTS_SOLID ))
|
|
{
|
|
if( x || y || z )
|
|
{
|
|
VectorCopy( current_point, best_point );
|
|
best_dist = dist;
|
|
best_leaf = leaf;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( current_point, point );
|
|
return leaf;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( best_leaf )
|
|
break;
|
|
}
|
|
|
|
VectorCopy( best_point, point );
|
|
return best_leaf;
|
|
}
|
|
|
|
dworldlight_t *AllocWorldLight( word *lightnum )
|
|
{
|
|
if( g_numworldlights == MAX_MAP_WORLDLIGHTS )
|
|
COM_FatalError( "MAX_MAP_WORLDLIGHTS limit exceeded\n" );
|
|
|
|
if( lightnum ) *lightnum = g_numworldlights;
|
|
|
|
return &g_dworldlights[g_numworldlights++];
|
|
}
|
|
|
|
void InitWorldLightFromDlight( directlight_t *dl, int leafnum )
|
|
{
|
|
dworldlight_t *wl;
|
|
|
|
if( dl->type != emit_skylight && leafnum == 0 )
|
|
return; // light that be removed
|
|
|
|
wl = AllocWorldLight( &dl->lightnum );
|
|
|
|
wl->emittype = dl->type;
|
|
wl->style = dl->style;
|
|
VectorCopy( dl->origin, wl->origin );
|
|
VectorCopy( dl->intensity, wl->intensity );
|
|
VectorCopy( dl->normal, wl->normal );
|
|
wl->stopdot = dl->stopdot;
|
|
wl->stopdot2 = dl->stopdot2;
|
|
wl->fade = dl->fade;
|
|
wl->falloff = dl->falloff;
|
|
wl->leafnum = leafnum; // leaf where light is located
|
|
wl->radius = dl->radius;
|
|
wl->facenum = dl->facenum;
|
|
wl->modelnumber = dl->modelnum;
|
|
}
|
|
|
|
void InitWorldLightFromPatch( dworldlight_t *wl, patch_t *p )
|
|
{
|
|
dleaf_t *leaf;
|
|
|
|
leaf = PointInLeaf( p->origin );
|
|
|
|
wl->emittype = emit_surface;
|
|
wl->style = p->emitstyle;
|
|
VectorCopy( p->origin, wl->origin );
|
|
|
|
VectorCopy( p->baselight, wl->intensity );
|
|
VectorScale( wl->intensity, p->area, wl->intensity );
|
|
VectorScale( wl->intensity, p->exposure, wl->intensity );
|
|
|
|
if( !VectorIsNull( p->reflectivity ))
|
|
{
|
|
VectorScale( wl->intensity, 0.5 / M_PI, wl->intensity );
|
|
VectorMultiply( wl->intensity, p->reflectivity, wl->intensity );
|
|
}
|
|
else
|
|
{
|
|
VectorScale( wl->intensity, DIRECT_SCALE, wl->intensity );
|
|
}
|
|
|
|
VectorCopy( GetPlaneFromFace( p->faceNumber )->normal, wl->normal );
|
|
wl->stopdot = wl->stopdot2 = 0.0f; // not used
|
|
wl->fade = p->fade; // constant
|
|
wl->falloff = falloff_valve; // inverse square falloff
|
|
wl->leafnum = leaf - g_dleafs; // leaf where light is located
|
|
wl->radius = VectorLength2( wl->intensity ) * 0.5f;
|
|
wl->facenum = p->faceNumber;
|
|
wl->modelnumber = p->modelnum;
|
|
}
|
|
|
|
// =====================================================================================
|
|
// CalcSightArea
|
|
// =====================================================================================
|
|
vec_t CalcSightArea( const vec3_t receiver_origin, const vec3_t receiver_normal, const winding_t *emitter_winding, int skylevel )
|
|
{
|
|
// maybe there are faster ways in calculating the weighted area, but at least this way is not bad.
|
|
int numedges = emitter_winding->numpoints;
|
|
vec3_t *edges = (vec3_t *)Mem_Alloc( numedges * sizeof( vec3_t ));
|
|
vec3_t *pnormal, *pedge;
|
|
vec_t *psize, dot;
|
|
vec_t area = 0.0;
|
|
int i, j;
|
|
|
|
for( int x = 0; x < numedges; x++ )
|
|
{
|
|
vec3_t v1, v2, normal;
|
|
|
|
VectorSubtract( emitter_winding->p[x], receiver_origin, v1 );
|
|
VectorSubtract( emitter_winding->p[(x + 1) % numedges], receiver_origin, v2 );
|
|
CrossProduct( v1, v2, normal ); // pointing inward
|
|
|
|
if( !VectorNormalize( normal ))
|
|
break;
|
|
VectorCopy( normal, edges[x] );
|
|
}
|
|
|
|
if( x != numedges )
|
|
{
|
|
Mem_Free( edges );
|
|
return area;
|
|
}
|
|
|
|
for( i = 0, pnormal = g_skynormals[skylevel], psize = g_skynormalsizes[skylevel]; i < g_numskynormals[skylevel]; i++, pnormal++, psize++ )
|
|
{
|
|
dot = DotProduct( *pnormal, receiver_normal );
|
|
if( dot <= 0 ) continue;
|
|
|
|
for( j = 0, pedge = edges; j < numedges; j++, pedge++ )
|
|
{
|
|
if( DotProduct( *pnormal, *pedge ) <= 0 )
|
|
break;
|
|
}
|
|
|
|
if( j < numedges )
|
|
continue;
|
|
area += dot * (*psize);
|
|
}
|
|
|
|
area = area * 4.0 * M_PI; // convert to absolute sphere area
|
|
Mem_Free( edges );
|
|
|
|
return area;
|
|
}
|
|
|
|
// =====================================================================================
|
|
// GetAlternateOrigin
|
|
// =====================================================================================
|
|
void GetAlternateOrigin( const vec3_t pos, const vec3_t normal, const patch_t *patch, vec3_t origin )
|
|
{
|
|
const dplane_t *faceplane;
|
|
const vec_t *faceplaneoffset;
|
|
const vec_t *facenormal;
|
|
dplane_t clipplane;
|
|
winding_t *w;
|
|
|
|
faceplane = GetPlaneFromFace( patch->faceNumber );
|
|
faceplaneoffset = g_face_offset[patch->faceNumber];
|
|
facenormal = faceplane->normal;
|
|
VectorCopy( normal, clipplane.normal );
|
|
clipplane.dist = DotProduct( pos, clipplane.normal );
|
|
|
|
if( WindingOnPlaneSide( patch->winding, clipplane.normal, clipplane.dist, ON_EPSILON ) != SIDE_CROSS )
|
|
{
|
|
VectorCopy( patch->origin, origin );
|
|
}
|
|
else
|
|
{
|
|
w = CopyWinding( patch->winding );
|
|
|
|
ChopWindingInPlace( &w, clipplane.normal, clipplane.dist, ON_EPSILON, false );
|
|
|
|
if( w == NULL )
|
|
{
|
|
VectorCopy( patch->origin, origin );
|
|
}
|
|
else
|
|
{
|
|
vec3_t v, point, bestpoint;
|
|
vec_t dist, bestdist = -1.0;
|
|
bool found = false;
|
|
vec3_t center;
|
|
|
|
WindingCenter( w, center );
|
|
|
|
VectorMA( center, DEFAULT_HUNT_OFFSET, facenormal, point );
|
|
|
|
if( HuntForWorld( point, faceplaneoffset, faceplane, 2, 1.0, DEFAULT_HUNT_OFFSET ))
|
|
{
|
|
VectorSubtract( point, center, v );
|
|
dist = VectorLength( v );
|
|
|
|
if( !found || dist < bestdist )
|
|
{
|
|
VectorCopy( point, bestpoint );
|
|
bestdist = dist;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if( !found )
|
|
{
|
|
for( int i = 0; i < (int)w->numpoints; i++ )
|
|
{
|
|
const vec_t *p1 = w->p[i];
|
|
const vec_t *p2 = w->p[(i + 1) % w->numpoints];
|
|
|
|
VectorAdd( p1, p2, point );
|
|
VectorAdd( point, center, point );
|
|
VectorScale( point, 1.0 / 3.0, point );
|
|
VectorMA( point, DEFAULT_HUNT_OFFSET, facenormal, point );
|
|
|
|
if( HuntForWorld( point, faceplaneoffset, faceplane, 1, 0.0, DEFAULT_HUNT_OFFSET ))
|
|
{
|
|
VectorSubtract( point, center, v );
|
|
dist = VectorLength( v );
|
|
|
|
if( !found || dist < bestdist )
|
|
{
|
|
VectorCopy( point, bestpoint );
|
|
bestdist = dist;
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( found ) VectorCopy( bestpoint, origin );
|
|
else VectorCopy( patch->origin, origin );
|
|
|
|
FreeWinding( w );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
MakeBackplanes
|
|
=============
|
|
*/
|
|
void MakeBackplanes( void )
|
|
{
|
|
for( int i = 0; i < g_numplanes; i++ )
|
|
{
|
|
VectorNegate( g_dplanes[i].normal, g_backplanes[i].normal );
|
|
g_backplanes[i].dist = -g_dplanes[i].dist;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================================================================
|
|
|
|
TEXTURE LIGHT VALUES
|
|
|
|
===================================================================
|
|
*/
|
|
static texlight_t g_texlights[MAX_TEXLIGHTS];
|
|
static int g_num_texlights;
|
|
|
|
int ParseInfoTexlights( entity_t *mapent )
|
|
{
|
|
int numtexlights = 0;
|
|
float r, g, b, i;
|
|
int j, values;
|
|
epair_t *ep;
|
|
|
|
if( Q_strcmp( ValueForKey( mapent, "classname" ), "info_texlights" ))
|
|
return 0;
|
|
|
|
MsgDev( D_REPORT, "Reading texlights from info_texlights map entity\n" );
|
|
|
|
for( ep = mapent->epairs; ep; ep = ep->next )
|
|
{
|
|
if( !Q_strcmp( ep->key, "classname" ) || !Q_strcmp( ep->key, "origin" ))
|
|
continue; // we dont care about these keyvalues
|
|
|
|
values = sscanf( ep->value, "%f %f %f %f", &r, &g, &b, &i );
|
|
|
|
if( values == 1 )
|
|
{
|
|
g = b = r;
|
|
}
|
|
else if( values == 4 ) // use brightness value.
|
|
{
|
|
r *= i / 255.0;
|
|
g *= i / 255.0;
|
|
b *= i / 255.0;
|
|
}
|
|
else if( values != 3 )
|
|
{
|
|
MsgDev( D_WARN, "ignoring bad texlight '%s' in info_texlights entity", ep->key );
|
|
continue;
|
|
}
|
|
|
|
for( j = 0; j < g_num_texlights; j++ )
|
|
{
|
|
texlight_t *tl = &g_texlights[j];
|
|
|
|
if( !Q_strcmp( tl->name, ep->key ))
|
|
{
|
|
if( !Q_strcmp( tl->filename, "info_texlights" ))
|
|
{
|
|
MsgDev( D_REPORT, "duplication of '%s' in info_texlights map entity!\n", tl->name );
|
|
}
|
|
else if( tl->value[0] != r || tl->value[1] != g || tl->value[2] != b )
|
|
{
|
|
MsgDev( D_REPORT, "overriding '%s' from '%s' with info_texlights map entity!\n",
|
|
tl->name, tl->filename );
|
|
}
|
|
else
|
|
{
|
|
MsgDev( D_WARN, "redundant '%s' def in '%s' AND info_texlights map entity!\n",
|
|
tl->name, tl->filename );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Q_strncpy( g_texlights[j].name, ep->key, sizeof( g_texlights[0].name ));
|
|
g_texlights[j].filename = "info_texlights";
|
|
VectorSet( g_texlights[j].value, r, g, b );
|
|
g_texlights[j].fade = 1.0f;
|
|
numtexlights++;
|
|
|
|
g_num_texlights = Q_max( g_num_texlights, j + 1 );
|
|
|
|
if( g_num_texlights == MAX_TEXLIGHTS )
|
|
COM_FatalError( "MAX_TEXLIGHTS limit exceeded\n" );
|
|
}
|
|
|
|
return numtexlights;
|
|
}
|
|
|
|
int ParseSurfaceLights( entity_t *mapent )
|
|
{
|
|
char name[64];
|
|
char *pLight = NULL;
|
|
char *pColor = NULL;
|
|
vec3_t intensity;
|
|
int j, argCnt;
|
|
|
|
if( Q_strcmp( ValueForKey( mapent, "classname" ), "light" ))
|
|
return 0;
|
|
|
|
if( !CheckKey( mapent, "_surface" ))
|
|
return 0;
|
|
|
|
Q_strncpy( name, ValueForKey( mapent, "_surface" ), sizeof( name ));
|
|
if( CheckKey( mapent, "_light" ))
|
|
pLight = ValueForKey( mapent, "_light" );
|
|
else pLight = ValueForKey( mapent, "light" );
|
|
argCnt = ParseLightIntensity( pLight, intensity );
|
|
if( name[0] == '*' ) name[0] = '!';
|
|
|
|
// quake light
|
|
if( argCnt <= 1 )
|
|
{
|
|
double r, g, b, scaler;
|
|
|
|
scaler = intensity[0];
|
|
r = g = b = 0;
|
|
|
|
if( CheckKey( mapent, "_color" ))
|
|
pColor = ValueForKey( mapent, "_color" );
|
|
else pColor = ValueForKey( mapent, "color" );
|
|
|
|
if( pColor[0] && sscanf( pColor, "%lf %lf %lf", &r, &g, &b ) == 3 )
|
|
{
|
|
intensity[0] = r * (float)scaler;
|
|
intensity[1] = g * (float)scaler;
|
|
intensity[2] = b * (float)scaler;
|
|
}
|
|
}
|
|
|
|
for( j = 0; j < g_num_texlights; j++ )
|
|
{
|
|
texlight_t *tl = &g_texlights[j];
|
|
|
|
if( !Q_strcmp( tl->name, name ))
|
|
{
|
|
if( !Q_strcmp( tl->filename, "info_texlights" ))
|
|
{
|
|
MsgDev( D_REPORT, "duplication of '%s' in info_texlights map entity!\n", tl->name );
|
|
}
|
|
else if( !VectorCompareEpsilon( tl->value, intensity, ON_EPSILON ))
|
|
{
|
|
MsgDev( D_REPORT, "overriding '%s' from '%s' with info_texlights map entity!\n", tl->name, tl->filename );
|
|
}
|
|
else
|
|
{
|
|
MsgDev( D_WARN, "redundant '%s' def in '%s' AND info_texlights map entity!\n", tl->name, tl->filename );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Q_strncpy( g_texlights[j].name, name, sizeof( g_texlights[0].name ));
|
|
VectorCopy( intensity, g_texlights[j].value );
|
|
g_texlights[j].filename = "light_surface";
|
|
g_texlights[j].fade = FloatForKey( mapent, "wait" );
|
|
|
|
// to prevent division by zero
|
|
if( g_texlights[j].fade <= 0.0 ) g_texlights[j].fade = 1.0;
|
|
|
|
g_num_texlights = Q_max( g_num_texlights, j + 1 );
|
|
if( g_num_texlights == MAX_TEXLIGHTS )
|
|
COM_FatalError( "MAX_TEXLIGHTS limit exceeded\n" );
|
|
|
|
return 1;
|
|
}
|
|
|
|
// =====================================================================================
|
|
// ReadInfoTexlights
|
|
// try and parse texlight info from the info_texlights entity
|
|
// =====================================================================================
|
|
void ReadInfoTexlights( void )
|
|
{
|
|
int numtexlights = 0;
|
|
entity_t *mapent;
|
|
|
|
for( int k = 0; k < g_numentities; k++ )
|
|
{
|
|
mapent = &g_entities[k];
|
|
|
|
// all the vertex cache ID's should be removed now
|
|
RemoveKey( mapent, "_vlight_id" );
|
|
|
|
numtexlights += ParseInfoTexlights( mapent );
|
|
|
|
numtexlights += ParseSurfaceLights( mapent );
|
|
}
|
|
|
|
MsgDev( D_REPORT, "[%i texlights parsed from info_texlights map entity]\n\n", numtexlights );
|
|
}
|
|
|
|
/*
|
|
============
|
|
ReadLightFile
|
|
============
|
|
*/
|
|
void ReadLightFile( const char *filename, bool use_direct_path )
|
|
{
|
|
int file_texlights = 0;
|
|
int result = 0;
|
|
char scan[128];
|
|
int j, argCnt;
|
|
file_t *f;
|
|
|
|
FS_AllowDirectPaths( use_direct_path );
|
|
f = FS_Open( filename, "r", false );
|
|
FS_AllowDirectPaths( false );
|
|
if( !f ) return;
|
|
|
|
while( result != EOF )
|
|
{
|
|
char szTexlight[256];
|
|
vec_t r, g, b, i = 1;
|
|
char *comment = scan;
|
|
|
|
result = FS_Gets( f, (byte *)&scan, sizeof( scan ));
|
|
|
|
// skip the comments
|
|
if( comment[0] == '/' && comment[1] == '/' )
|
|
continue;
|
|
|
|
argCnt = sscanf( scan, "%s %f %f %f %f", szTexlight, &r, &g, &b, &i );
|
|
|
|
if( argCnt == 2 )
|
|
{
|
|
// eith 1+1 args, the R,G,B values are all equal to the first value
|
|
g = b = r;
|
|
}
|
|
else if( argCnt == 5 )
|
|
{
|
|
// With 1 + 4 args, the R,G,B values are "scaled" by the fourth numeric value i;
|
|
r *= i / 255.0;
|
|
g *= i / 255.0;
|
|
b *= i / 255.0;
|
|
}
|
|
else if( argCnt != 4 )
|
|
{
|
|
if( Q_strlen( scan ) > 4 )
|
|
MsgDev( D_WARN, "ignoring bad texlight '%s' in %s", scan, filename );
|
|
continue;
|
|
}
|
|
|
|
for( j = 0; j < g_num_texlights; j++ )
|
|
{
|
|
texlight_t *tl = &g_texlights[j];
|
|
|
|
if( !Q_strcmp( tl->name, szTexlight ))
|
|
{
|
|
if( !Q_strcmp( tl->filename, filename ))
|
|
{
|
|
MsgDev( D_REPORT, "duplication of '%s' in file '%s'!\n", tl->name, tl->filename );
|
|
}
|
|
else if( tl->value[0] != r || tl->value[1] != g || tl->value[2] != b )
|
|
{
|
|
MsgDev( D_REPORT, "overriding '%s' from '%s' with '%s'!\n", tl->name, tl->filename, filename );
|
|
}
|
|
else
|
|
{
|
|
MsgDev( D_WARN, "redundant '%s' def in '%s' AND '%s'!\n", tl->name, tl->filename, filename );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Q_strncpy( g_texlights[j].name, szTexlight, sizeof( g_texlights[0].name ));
|
|
VectorSet( g_texlights[j].value, r, g, b );
|
|
g_texlights[j].filename = filename;
|
|
g_texlights[j].fade = 1.0f;
|
|
file_texlights++;
|
|
|
|
g_num_texlights = Q_max( g_num_texlights, j + 1 );
|
|
|
|
if( g_num_texlights == MAX_TEXLIGHTS )
|
|
COM_FatalError( "MAX_TEXLIGHTS limit exceeded\n" );
|
|
}
|
|
|
|
MsgDev( D_INFO, "[%i texlights parsed from '%s']\n\n", file_texlights, filename );
|
|
FS_Close( f );
|
|
}
|
|
|
|
/*
|
|
============
|
|
LightForTexture
|
|
============
|
|
*/
|
|
vec_t LightForTexture( const char *name, vec3_t result )
|
|
{
|
|
VectorClear( result );
|
|
|
|
for( int i = 0; i < g_num_texlights; i++ )
|
|
{
|
|
if( !Q_stricmp( name, g_texlights[i].name ))
|
|
{
|
|
VectorCopy( g_texlights[i].value, result );
|
|
MsgDev( D_REPORT, "Texture '%s': baselight is (%f,%f,%f).\n", name, result[0], result[1], result[2] );
|
|
return g_texlights[i].fade;
|
|
}
|
|
}
|
|
|
|
return 1.0f;
|
|
}
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
MAKE FACES
|
|
|
|
=======================================================================
|
|
*/
|
|
/*
|
|
=============
|
|
WindingFromFace
|
|
=============
|
|
*/
|
|
winding_t *WindingFromFace( const dface_t *f )
|
|
{
|
|
int i, se, v;
|
|
dvertex_t *dv;
|
|
winding_t *w;
|
|
|
|
w = AllocWinding( f->numedges );
|
|
w->numpoints = f->numedges;
|
|
|
|
for( i = 0; i < f->numedges; i++ )
|
|
{
|
|
se = g_dsurfedges[f->firstedge + i];
|
|
if( se < 0 ) v = g_dedges[-se].v[1];
|
|
else v = g_dedges[se].v[0];
|
|
dv = &g_dvertexes[v];
|
|
VectorCopy( dv->point, w->p[i] );
|
|
}
|
|
|
|
RemoveColinearPointsEpsilon( w, ON_EPSILON );
|
|
|
|
return w;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
BaseLightForFace
|
|
=============
|
|
*/
|
|
vec_t BaseLightForFace( dface_t *f, vec3_t light, vec3_t reflectivity )
|
|
{
|
|
int miptex = g_texinfo[f->texinfo].miptex;
|
|
vec_t fade;
|
|
miptex_t *mt;
|
|
|
|
// check for light emited by texture
|
|
mt = GetTextureByMiptex( miptex );
|
|
if( !mt ) return 1.0f;
|
|
|
|
fade = LightForTexture( mt->name, light );
|
|
VectorClear( reflectivity );
|
|
|
|
#ifdef HLRAD_REFLECTIVITY
|
|
int samples = mt->width * mt->height;
|
|
byte *pal = ((byte *)mt) + mt->offsets[0] + (((mt->width * mt->height) * 85) >> 6);
|
|
byte *buf = ((byte *)mt) + mt->offsets[0];
|
|
vec3_t total;
|
|
|
|
// check for cache
|
|
if( g_texture_init[miptex] )
|
|
{
|
|
VectorCopy( g_reflectivity[miptex], reflectivity );
|
|
return fade;
|
|
}
|
|
|
|
pal += sizeof( short ); // skip colorsize
|
|
VectorClear( total );
|
|
|
|
for( int i = 0; i < samples; i++ )
|
|
{
|
|
vec3_t reflectivity;
|
|
|
|
if( mt->name[0] == '{' && buf[i] == 0xFF )
|
|
{
|
|
VectorClear( reflectivity );
|
|
}
|
|
else
|
|
{
|
|
int texel = buf[i];
|
|
reflectivity[0] = pow( pal[texel*3+0] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA );
|
|
reflectivity[1] = pow( pal[texel*3+1] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA );
|
|
reflectivity[2] = pow( pal[texel*3+2] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA );
|
|
VectorScale( reflectivity, DEFAULT_TEXREFLECTSCALE, reflectivity );
|
|
}
|
|
VectorAdd( total, reflectivity, total );
|
|
}
|
|
|
|
VectorScale( total, 1.0 / (double)(mt->width * mt->height), g_reflectivity[miptex] );
|
|
VectorCopy( g_reflectivity[miptex], reflectivity );
|
|
MsgDev( D_REPORT, "Texture '%s': reflectivity is (%f,%f,%f).\n", mt->name, reflectivity[0], reflectivity[1], reflectivity[2] );
|
|
g_texture_init[miptex] = true;
|
|
#endif
|
|
return fade;
|
|
}
|
|
|
|
static void UpdateEmitterInfo( patch_t *patch )
|
|
{
|
|
const vec_t *origin = patch->origin;
|
|
const winding_t *winding = patch->winding;
|
|
vec_t radius = ON_EPSILON;
|
|
|
|
for( int x = 0; x < winding->numpoints; x++ )
|
|
{
|
|
vec3_t delta;
|
|
vec_t dist;
|
|
VectorSubtract( winding->p[x], origin, delta );
|
|
dist = VectorLength( delta );
|
|
radius = Q_max( dist, radius );
|
|
}
|
|
|
|
int skylevel = ACCURATEBOUNCE_DEFAULT_SKYLEVEL;
|
|
vec_t area = WindingArea( winding );
|
|
vec_t size = 0.8f;
|
|
|
|
if( area < size * radius * radius ) // the shape is too thin
|
|
{
|
|
skylevel++;
|
|
size *= 0.25f;
|
|
|
|
if( area < size * radius * radius )
|
|
{
|
|
skylevel++;
|
|
size *= 0.25f;
|
|
|
|
if( area < size * radius * radius )
|
|
{
|
|
// stop here
|
|
radius = sqrt( area / size );
|
|
// just decrease the range to limit the use of the new method.
|
|
// because when the area is small, the new method becomes randomized and unstable.
|
|
}
|
|
}
|
|
}
|
|
|
|
patch->emitter_range = ACCURATEBOUNCE_THRESHOLD * radius;
|
|
patch->emitter_skylevel = skylevel;
|
|
}
|
|
|
|
// =====================================================================================
|
|
// PlacePatchInside
|
|
// =====================================================================================
|
|
static bool PlacePatchInside( patch_t *patch )
|
|
{
|
|
const vec_t *face_offset = g_face_offset[patch->faceNumber];
|
|
vec_t offset = DEFAULT_HUNT_OFFSET;
|
|
vec3_t v, center, point, bestpoint;
|
|
vec_t pointsfound, pointstested;
|
|
vec_t dist, bestdist = -1.0;
|
|
const dplane_t *plane;
|
|
bool found;
|
|
|
|
plane = GetPlaneFromFace( patch->faceNumber );
|
|
WindingCenter( patch->winding, center );
|
|
pointsfound = pointstested = 0;
|
|
found = false;
|
|
|
|
VectorMA( center, offset, plane->normal, point );
|
|
pointstested++;
|
|
|
|
if( HuntForWorld( point, face_offset, plane, 4, 0.2, offset ) || HuntForWorld( point, face_offset, plane, 4, 0.8, offset ))
|
|
{
|
|
VectorSubtract( point, center, v );
|
|
dist = VectorLength( v );
|
|
pointsfound++;
|
|
|
|
if( !found || dist < bestdist )
|
|
{
|
|
VectorCopy( point, bestpoint );
|
|
bestdist = dist;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
for( int i = 0; i < patch->winding->numpoints; i++ )
|
|
{
|
|
const vec_t *p1 = patch->winding->p[i];
|
|
const vec_t *p2 = patch->winding->p[(i+1) % patch->winding->numpoints];
|
|
|
|
VectorAdd( p1, p2, point );
|
|
VectorAdd( point, center, point );
|
|
VectorScale( point, 1.0 / 3.0, point );
|
|
VectorMA( point, offset, plane->normal, point );
|
|
pointstested++;
|
|
|
|
if( HuntForWorld( point, face_offset, plane, 4, 0.2, offset ) || HuntForWorld( point, face_offset, plane, 4, 0.8, offset ))
|
|
{
|
|
VectorSubtract( point, center, v );
|
|
dist = VectorLength( v );
|
|
pointsfound++;
|
|
|
|
if( !found || dist < bestdist )
|
|
{
|
|
VectorCopy( point, bestpoint );
|
|
bestdist = dist;
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
patch->exposure = pointsfound / pointstested;
|
|
|
|
if( found )
|
|
{
|
|
VectorCopy( bestpoint, patch->origin );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
VectorMA( center, offset, plane->normal, patch->origin );
|
|
SetBits( patch->flags, PATCH_OUTSIDE );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// =====================================================================================
|
|
//
|
|
// SUBDIVIDE PATCHES
|
|
//
|
|
// =====================================================================================
|
|
// =====================================================================================
|
|
// CutWindingWithGrid
|
|
// Caller must free this returned value at some point
|
|
// =====================================================================================
|
|
static void CutWindingWithGrid( patch_t *patch, const dplane_t *plA, const dplane_t *plB )
|
|
{
|
|
// patch->winding->m_NumPoints must > 0
|
|
// plA->dist and plB->dist will not be used
|
|
winding_t *winding = NULL;
|
|
vec_t chop, epsilon;
|
|
const int max_gridsize = 64;
|
|
vec_t gridstartA, minA, maxA;
|
|
vec_t gridstartB, minB, maxB;
|
|
int gridsizeA;
|
|
int gridsizeB;
|
|
vec_t gridchopA;
|
|
vec_t gridchopB;
|
|
int i, numstrips;
|
|
|
|
winding = CopyWinding( patch->winding ); // perform all the operations on the copy
|
|
chop = Q_max( 1.0, patch->chop );
|
|
epsilon = 0.6;
|
|
|
|
// optimize the grid
|
|
minA = minB = BOGUS_RANGE;
|
|
maxA = maxB = -BOGUS_RANGE;
|
|
|
|
for( int x = 0; x < winding->numpoints; x++ )
|
|
{
|
|
vec_t dotA, dotB;
|
|
vec_t *point;
|
|
|
|
point = winding->p[x];
|
|
dotA = DotProduct( point, plA->normal );
|
|
minA = Q_min( minA, dotA );
|
|
maxA = Q_max( maxA, dotA );
|
|
dotB = DotProduct( point, plB->normal );
|
|
minB = Q_min( minB, dotB );
|
|
maxB = Q_max( maxB, dotB );
|
|
}
|
|
|
|
gridchopA = chop;
|
|
gridsizeA = (int)ceil(( maxA - minA - 2 * epsilon ) / gridchopA );
|
|
gridsizeA = Q_max( 1, gridsizeA );
|
|
|
|
if( gridsizeA > max_gridsize )
|
|
{
|
|
gridsizeA = max_gridsize;
|
|
gridchopA = (maxA - minA) / (vec_t)gridsizeA;
|
|
}
|
|
gridstartA = (minA + maxA) / 2.0 - (gridsizeA / 2.0) * gridchopA;
|
|
|
|
gridchopB = chop;
|
|
gridsizeB = (int)ceil(( maxB - minB - 2 * epsilon ) / gridchopB);
|
|
gridsizeB = Q_max( 1, gridsizeB );
|
|
|
|
if( gridsizeB > max_gridsize )
|
|
{
|
|
gridsizeB = max_gridsize;
|
|
gridchopB = (maxB - minB) / (vec_t)gridsizeB;
|
|
}
|
|
gridstartB = (minB + maxB) / 2.0 - (gridsizeB / 2.0) * gridchopB;
|
|
|
|
// cut the winding by the direction of plane A and save into windingArray
|
|
g_numwindings = 0;
|
|
|
|
for( i = 1; i < gridsizeA; i++ )
|
|
{
|
|
winding_t *front = NULL;
|
|
winding_t *back = NULL;
|
|
vec_t dist;
|
|
|
|
dist = gridstartA + i * gridchopA;
|
|
ClipWindingEpsilon( winding, plA->normal, dist, ON_EPSILON, &front, &back );
|
|
|
|
if( !front || WindingOnPlaneSide( front, plA->normal, dist, epsilon ) == SIDE_ON ) // ended
|
|
{
|
|
if( front )
|
|
{
|
|
FreeWinding( front );
|
|
front = NULL;
|
|
}
|
|
if( back )
|
|
{
|
|
FreeWinding( back );
|
|
back = NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if( !back || WindingOnPlaneSide( back, plA->normal, dist, epsilon ) == SIDE_ON ) // didn't begin
|
|
{
|
|
if( front )
|
|
{
|
|
FreeWinding( front );
|
|
front = NULL;
|
|
}
|
|
if( back )
|
|
{
|
|
FreeWinding( back );
|
|
back = NULL;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
FreeWinding( winding );
|
|
winding = NULL;
|
|
|
|
g_windingArray[g_numwindings] = back;
|
|
g_numwindings++;
|
|
back = NULL;
|
|
|
|
winding = front;
|
|
front = NULL;
|
|
}
|
|
|
|
g_windingArray[g_numwindings] = winding;
|
|
g_numwindings++;
|
|
winding = NULL;
|
|
|
|
// cut by the direction of plane B
|
|
numstrips = g_numwindings;
|
|
|
|
for( i = 0; i < numstrips; i++ )
|
|
{
|
|
winding_t *strip = g_windingArray[i];
|
|
|
|
g_windingArray[i] = NULL;
|
|
|
|
for( int j = 1; j < gridsizeB; j++ )
|
|
{
|
|
winding_t *front = NULL;
|
|
winding_t *back = NULL;
|
|
vec_t dist;
|
|
|
|
dist = gridstartB + j * gridchopB;
|
|
ClipWindingEpsilon( strip, plB->normal, dist, ON_EPSILON, &front, &back );
|
|
|
|
if( !front || WindingOnPlaneSide( front, plB->normal, dist, epsilon ) == SIDE_ON ) // ended
|
|
{
|
|
if( front )
|
|
{
|
|
FreeWinding( front );
|
|
front = NULL;
|
|
}
|
|
if( back )
|
|
{
|
|
FreeWinding( back );
|
|
back = NULL;
|
|
}
|
|
break;
|
|
}
|
|
if( !back || WindingOnPlaneSide( back, plB->normal, dist, epsilon ) == SIDE_ON ) // didn't begin
|
|
{
|
|
if( front )
|
|
{
|
|
FreeWinding( front );
|
|
front = NULL;
|
|
}
|
|
if( back )
|
|
{
|
|
FreeWinding( back );
|
|
back = NULL;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
FreeWinding( strip );
|
|
strip = NULL;
|
|
|
|
g_windingArray[g_numwindings] = back;
|
|
g_numwindings++;
|
|
back = NULL;
|
|
|
|
strip = front;
|
|
front = NULL;
|
|
}
|
|
|
|
g_windingArray[g_numwindings] = strip;
|
|
g_numwindings++;
|
|
strip = NULL;
|
|
}
|
|
|
|
FreeWinding( patch->winding );
|
|
patch->winding = NULL;
|
|
}
|
|
|
|
// =====================================================================================
|
|
// GetGridPlanes
|
|
// From patch, determine perpindicular grid planes to subdivide with (returned in planeA and planeB)
|
|
// assume S and T is perpindicular (they SHOULD be in worldcraft 3.3 but aren't always...)
|
|
// =====================================================================================
|
|
static void GetGridPlanes( const patch_t *p, dplane_t *pl )
|
|
{
|
|
const dface_t *f = g_dfaces + p->faceNumber;
|
|
dtexinfo_t *tx = &g_texinfo[f->texinfo];
|
|
const dplane_t *faceplane = GetPlaneFromFace( p->faceNumber );
|
|
dplane_t *plane = pl;
|
|
float lmvecs[2][4];
|
|
|
|
LightMatrixFromTexMatrix( tx, lmvecs );
|
|
|
|
for( int x = 0; x < 2; x++, plane++ )
|
|
{
|
|
// cut the patch along texel grid planes
|
|
vec_t val = DotProduct( faceplane->normal, lmvecs[!x] );
|
|
VectorMA( lmvecs[!x], -val, faceplane->normal, plane->normal );
|
|
VectorNormalize( plane->normal );
|
|
plane->dist = DotProduct( plane->normal, p->origin );
|
|
}
|
|
}
|
|
|
|
// =====================================================================================
|
|
// SubdividePatch
|
|
// =====================================================================================
|
|
static void SubdividePatch( patch_t *patch )
|
|
{
|
|
dplane_t planes[2];
|
|
dplane_t *plA = &planes[0];
|
|
dplane_t *plB = &planes[1];
|
|
patch_t *new_patch;
|
|
winding_t **winding;
|
|
int x;
|
|
|
|
memset( g_windingArray, 0, sizeof( g_windingArray ));
|
|
g_numwindings = 0;
|
|
|
|
GetGridPlanes( patch, planes );
|
|
CutWindingWithGrid( patch, plA, plB );
|
|
|
|
x = 0;
|
|
patch->next = NULL;
|
|
winding = g_windingArray;
|
|
|
|
while( *winding == NULL )
|
|
{
|
|
winding++;
|
|
x++;
|
|
}
|
|
|
|
patch->winding = *winding;
|
|
winding++;
|
|
x++;
|
|
|
|
patch->area = WindingAreaAndBalancePoint( patch->winding, patch->origin );
|
|
SetBits( patch->flags, PATCH_SOURCE );
|
|
PlacePatchInside( patch );
|
|
UpdateEmitterInfo( patch );
|
|
|
|
new_patch = &g_patches[g_num_patches];
|
|
|
|
for( ; x < g_numwindings; x++, winding++ )
|
|
{
|
|
if( *winding )
|
|
{
|
|
memcpy( new_patch, patch, sizeof( patch_t ));
|
|
ClearBits( new_patch->flags, PATCH_SOURCE );
|
|
|
|
new_patch->winding = *winding;
|
|
new_patch->area = WindingAreaAndBalancePoint( new_patch->winding, new_patch->origin );
|
|
SetBits( new_patch->flags, PATCH_CHILD );
|
|
PlacePatchInside( new_patch );
|
|
UpdateEmitterInfo( new_patch );
|
|
g_num_patches++;
|
|
new_patch++;
|
|
|
|
if( g_num_patches == MAX_PATCHES )
|
|
COM_FatalError( "MAX_PATCHES limit exceeded\n" );
|
|
}
|
|
}
|
|
// ATTENTION: We let SortPatches relink all the ->next correctly! instead of doing it here too which is somewhat complicated
|
|
}
|
|
|
|
/*
|
|
============
|
|
getScale
|
|
|
|
compute scale for patch
|
|
============
|
|
*/
|
|
static vec_t getScale( const patch_t *patch )
|
|
{
|
|
dface_t *f = &g_dfaces[patch->faceNumber];
|
|
dtexinfo_t *tx = &g_texinfo[f->texinfo];
|
|
const dplane_t *faceplane = GetPlaneFromFace( f );
|
|
vec3_t outvecs[2];
|
|
vec_t scale[2];
|
|
vec_t dot;
|
|
|
|
// snap texture "vecs" to faceplane without affecting texture alignment
|
|
for( int x = 0; x < 2; x++ )
|
|
{
|
|
dot = DotProduct( faceplane->normal, tx->vecs[x] );
|
|
VectorMA( tx->vecs[x], -dot, faceplane->normal, outvecs[x] );
|
|
}
|
|
|
|
scale[0] = 1.0 / Q_max( NORMAL_EPSILON, VectorLength( outvecs[0] ));
|
|
scale[1] = 1.0 / Q_max( NORMAL_EPSILON, VectorLength( outvecs[1] ));
|
|
|
|
// don't care about the angle between vecs[0] and vecs[1]
|
|
// (given the length of "vecs", smaller angle = larger texel area),
|
|
// because gridplanes will have the same angle (also smaller angle = larger patch area)
|
|
return sqrt( scale[0] * scale[1] );
|
|
}
|
|
|
|
/*
|
|
============
|
|
getEmitMode
|
|
|
|
some pathches are emit_surfaces
|
|
============
|
|
*/
|
|
static bool getEmitMode( const patch_t *patch )
|
|
{
|
|
bool emitmode = false;
|
|
vec_t value;
|
|
|
|
if( !VectorIsNull( patch->reflectivity ))
|
|
value = DotProduct( patch->baselight, patch->reflectivity ) / 3.0;
|
|
else value = VectorAvg( patch->baselight );
|
|
|
|
// this patch is emitting surface
|
|
if( value >= DLIGHT_THRESHOLD )
|
|
emitmode = true;
|
|
|
|
return emitmode;
|
|
}
|
|
|
|
/*
|
|
============
|
|
getChop
|
|
|
|
compute chop value for patch
|
|
============
|
|
*/
|
|
static vec_t getChop( const patch_t *patch )
|
|
{
|
|
vec_t rval;
|
|
|
|
if( FBitSet( patch->flags, PATCH_EMITLIGHT ))
|
|
rval = g_texchop * getScale( patch );
|
|
else rval = g_chop * getScale( patch );
|
|
|
|
return rval;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
IsSpecial
|
|
=============
|
|
*/
|
|
static bool IsSpecial( const dface_t *f )
|
|
{
|
|
return FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
MakePatchForFace
|
|
=============
|
|
*/
|
|
bool MakePatchForFace( int modelnum, int fn, winding_t *w, int style )
|
|
{
|
|
dface_t *f = g_dfaces + fn;
|
|
vec3_t centroid = { 0, 0, 0 };
|
|
vec3_t mins, maxs, delta;
|
|
patch_t *patch;
|
|
vec_t fade;
|
|
dworldlight_t *wl;
|
|
int i;
|
|
|
|
// No patches at all for the sky!
|
|
if( IsSpecial( f )) return false;
|
|
|
|
if( w->numpoints < 3 ) // g-cont. how this possible?
|
|
return false;
|
|
|
|
patch = &g_patches[g_num_patches];
|
|
if( g_num_patches == MAX_PATCHES )
|
|
COM_FatalError( "MAX_PATCHES limit exceeded\n" );
|
|
memset( patch, 0, sizeof( patch_t ));
|
|
|
|
patch->area = WindingAreaAndBalancePoint( w, patch->origin );
|
|
patch->modelnum = modelnum;
|
|
patch->faceNumber = fn;
|
|
patch->winding = w;
|
|
|
|
// update debug info
|
|
g_totalarea += patch->area;
|
|
|
|
fade = BaseLightForFace( f, patch->baselight, patch->reflectivity );
|
|
|
|
for( i = 0; i < MAXLIGHTMAPS; i++ )
|
|
patch->totalstyle[i] = 255;
|
|
|
|
if( getEmitMode( patch ))
|
|
{
|
|
SetBits( patch->flags, PATCH_EMITLIGHT );
|
|
|
|
if( style )
|
|
{
|
|
patch->totalstyle[0] = style;
|
|
patch->emitstyle = style;
|
|
}
|
|
}
|
|
|
|
SetBits( patch->flags, PATCH_SOURCE );
|
|
patch->scale = getScale( patch );
|
|
patch->chop = getChop( patch );
|
|
PlacePatchInside( patch );
|
|
UpdateEmitterInfo( patch );
|
|
patch->fade = fade;
|
|
|
|
// NOTE: we alloc worldlights only once per face.
|
|
// all child patches after subdivision has the same lightnum
|
|
if( FBitSet( patch->flags, PATCH_EMITLIGHT ))
|
|
{
|
|
wl = AllocWorldLight( &patch->lightnum );
|
|
InitWorldLightFromPatch( wl, patch );
|
|
}
|
|
|
|
g_face_patches[fn] = patch;
|
|
g_num_patches++;
|
|
|
|
// per-face data
|
|
for( i = 0; i < f->numedges; i++ )
|
|
{
|
|
int edge = g_dsurfedges[f->firstedge + i];
|
|
|
|
if( edge > 0 )
|
|
{
|
|
VectorAdd( g_dvertexes[g_dedges[edge].v[0]].point, centroid, centroid );
|
|
VectorAdd( g_dvertexes[g_dedges[edge].v[1]].point, centroid, centroid );
|
|
}
|
|
else
|
|
{
|
|
VectorAdd( g_dvertexes[g_dedges[-edge].v[1]].point, centroid, centroid );
|
|
VectorAdd( g_dvertexes[g_dedges[-edge].v[0]].point, centroid, centroid );
|
|
}
|
|
}
|
|
|
|
VectorScale( centroid, 1.0 / (f->numedges * 2), centroid );
|
|
VectorAdd( centroid, g_face_offset[fn], g_face_centroids[fn] ); // save them for generating the patch normals later.
|
|
|
|
WindingBounds( patch->winding, mins, maxs );
|
|
VectorSubtract( maxs, mins, delta );
|
|
|
|
if( VectorLength( delta ) > patch->chop )
|
|
{
|
|
if( patch->area < 1.0 )
|
|
MsgDev( D_WARN, "patch at face %d tiny area (%4.3f) not subdividing\n", patch->faceNumber, patch->area );
|
|
else SubdividePatch( patch );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
MakePatches
|
|
=============
|
|
*/
|
|
void MakePatches( void )
|
|
{
|
|
int fn, style = 0;
|
|
vec3_t light_origin;
|
|
vec3_t model_center;
|
|
bool b_light_origin;
|
|
bool b_model_center;
|
|
int i, j, k;
|
|
vec3_t origin;
|
|
entity_t *ent;
|
|
dmodel_t *mod;
|
|
dface_t *f;
|
|
winding_t *w;
|
|
char *s;
|
|
|
|
g_patches = (patch_t *)Mem_Alloc( sizeof( patch_t ) * MAX_PATCHES );
|
|
g_numworldlights = 0;
|
|
|
|
for( i = 0; i < g_nummodels; i++ )
|
|
{
|
|
mod = &g_dmodels[i];
|
|
ent = EntityForModel( i );
|
|
VectorClear( origin );
|
|
b_light_origin = false;
|
|
b_model_center = false;
|
|
|
|
// bmodels with origin brushes need to be offset
|
|
// into their in-use position
|
|
if( *( s = ValueForKey( ent, "origin" )))
|
|
{
|
|
double v1, v2, v3;
|
|
|
|
if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 )
|
|
VectorSet( origin, v1, v2, v3 );
|
|
}
|
|
|
|
// allow models to be lit in an alternate location (pt1)
|
|
if( *( s = ValueForKey( ent, "light_origin" )))
|
|
{
|
|
entity_t *e = FindTargetEntity( s );
|
|
|
|
if( e )
|
|
{
|
|
if( *( s = ValueForKey( e, "origin" )))
|
|
{
|
|
double v1, v2, v3;
|
|
|
|
if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 )
|
|
{
|
|
VectorSet( light_origin, v1, v2, v3 );
|
|
b_light_origin = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// allow models to be lit in an alternate location (pt2)
|
|
if( *( s = ValueForKey( ent, "model_center" )))
|
|
{
|
|
double v1, v2, v3;
|
|
|
|
if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 )
|
|
{
|
|
VectorSet( model_center, v1, v2, v3 );
|
|
b_model_center = true;
|
|
}
|
|
}
|
|
#ifndef HLRAD_PARANOIA_BUMP
|
|
if( CheckKey( ent, "style" ))
|
|
style = abs( IntForKey( ent, "style" ));
|
|
else style = 0;
|
|
#endif
|
|
// allow models to be lit in an alternate location (pt3)
|
|
if( b_light_origin && b_model_center )
|
|
{
|
|
VectorSubtract( light_origin, model_center, origin );
|
|
}
|
|
|
|
for( j = 0; j < mod->numfaces; j++ )
|
|
{
|
|
fn = mod->firstface + j;
|
|
g_face_entity[fn] = ent;
|
|
VectorCopy( origin, g_face_offset[fn] );
|
|
f = &g_dfaces[fn];
|
|
w = WindingFromFace( f );
|
|
|
|
// adjust model origins
|
|
for( k = 0; k < w->numpoints; k++ )
|
|
VectorAdd( w->p[k], origin, w->p[k] );
|
|
|
|
// some faces won't create patch on them
|
|
if( !MakePatchForFace( i, fn, w, style ))
|
|
FreeWinding( w );
|
|
}
|
|
}
|
|
|
|
MsgDev( D_REPORT, "%i square feet [%.2f square inches]\n", (int)(g_totalarea / 144), g_totalarea );
|
|
MsgDev( D_INFO, "%i base patches, required %s\n", g_num_patches, Q_memprint( sizeof( patch_t ) * g_num_patches ));
|
|
MsgDev( D_NOTE, "patch_t = %s\n", Q_memprint( sizeof( patch_t )));
|
|
MsgDev( D_NOTE, "sample_t = %s\n", Q_memprint( sizeof( sample_t )));
|
|
}
|
|
|
|
// =====================================================================================
|
|
// patch_sorter
|
|
// =====================================================================================
|
|
static int CDECL patch_sorter( const void *p1, const void *p2 )
|
|
{
|
|
patch_t *patch1 = (patch_t *)p1;
|
|
patch_t *patch2 = (patch_t *)p2;
|
|
|
|
if( patch1->faceNumber < patch2->faceNumber )
|
|
return -1;
|
|
else if( patch1->faceNumber > patch2->faceNumber )
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
SortPatches
|
|
=============
|
|
*/
|
|
static void SortPatches( void )
|
|
{
|
|
patch_t *old_patches = g_patches;
|
|
g_patches = (patch_t *)Mem_Alloc( sizeof( patch_t ) * ( g_num_patches + 1 ));
|
|
memcpy( g_patches, old_patches, g_num_patches * sizeof( patch_t ));
|
|
Mem_Free( old_patches );
|
|
|
|
qsort((void *)g_patches, (size_t)g_num_patches, sizeof( patch_t ), patch_sorter );
|
|
|
|
// fixup g_face_patches & fFixup patch->next
|
|
memset( g_face_patches, 0, sizeof( g_face_patches ));
|
|
|
|
patch_t *patch = g_patches + 1;
|
|
patch_t *prev = g_patches;
|
|
int x;
|
|
|
|
g_face_patches[prev->faceNumber] = prev;
|
|
|
|
for( x = 1; x < g_num_patches; x++, patch++ )
|
|
{
|
|
if( patch->faceNumber != prev->faceNumber )
|
|
{
|
|
prev->next = NULL;
|
|
g_face_patches[patch->faceNumber] = patch;
|
|
}
|
|
else
|
|
{
|
|
prev->next = patch;
|
|
}
|
|
prev = patch;
|
|
}
|
|
|
|
for( x = 0; x < g_num_patches; x++ )
|
|
{
|
|
patch_t *patch = &g_patches[x];
|
|
patch->leafnum = PointInLeaf( patch->origin ) - g_dleafs;
|
|
}
|
|
|
|
// validate chains (debug thing)
|
|
for( int facenum = 0; facenum < g_numfaces; facenum++ )
|
|
{
|
|
for( patch = g_face_patches[facenum]; patch != NULL; patch = patch->next )
|
|
{
|
|
ASSERT( patch->faceNumber == facenum );
|
|
}
|
|
}
|
|
|
|
// if we haven't patches we don't need bounces
|
|
if( g_num_patches <= 0 ) g_numbounce = 0;
|
|
if( g_numbounce <= 0 ) g_indirect_sun = 0.0f;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
FreePatches
|
|
=============
|
|
*/
|
|
static void FreePatches( void )
|
|
{
|
|
patch_t *patch = g_patches;
|
|
|
|
for( int i = 0; i < g_num_patches; i++, patch++ )
|
|
FreeWinding( patch->winding );
|
|
|
|
memset( g_patches, 0, sizeof( patch_t ) * g_num_patches );
|
|
Mem_Free( g_patches );
|
|
g_patches = NULL;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
FreeTransfers
|
|
=============
|
|
*/
|
|
static void FreeTransfers( void )
|
|
{
|
|
patch_t *patch = g_patches;
|
|
|
|
for( int i = 0; i < g_num_patches; i++, patch++ )
|
|
{
|
|
if( patch->tData )
|
|
{
|
|
Mem_Free( patch->tData );
|
|
patch->tData = NULL;
|
|
}
|
|
|
|
if( patch->tIndex )
|
|
{
|
|
Mem_Free( patch->tIndex );
|
|
patch->tIndex = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=====================================================================
|
|
#ifdef HLRAD_COMPRESS_TRANSFERS
|
|
static uint GetLengthOfRun( const uint *raw, const uint *end )
|
|
{
|
|
uint run_size = 0;
|
|
|
|
while( raw < end )
|
|
{
|
|
if((( *raw ) + 1 ) == (*( raw + 1 )))
|
|
{
|
|
run_size++;
|
|
raw++;
|
|
|
|
if( run_size >= MAX_COMPRESSED_TRANSFER_INDEX )
|
|
return run_size;
|
|
}
|
|
else
|
|
{
|
|
return run_size;
|
|
}
|
|
}
|
|
|
|
return run_size;
|
|
}
|
|
|
|
static transfer_index_t *CompressTransferIndicies( const uint *tRaw, const uint rawSize, uint *iSize, int threadnum )
|
|
{
|
|
uint *end = (uint *)tRaw + rawSize - 1;
|
|
uint compressed_count_1 = 0;
|
|
uint compressed_count = 0;
|
|
uint *raw = (uint *)tRaw;
|
|
uint x, size = rawSize;
|
|
|
|
if( !size ) return NULL;
|
|
|
|
for( x = 0; x < rawSize; x++ )
|
|
{
|
|
x += GetLengthOfRun( tRaw + x, end );
|
|
compressed_count_1++;
|
|
}
|
|
|
|
if( !compressed_count_1 )
|
|
return NULL;
|
|
|
|
transfer_index_t *CompressedArray = (transfer_index_t *)Mem_Alloc( sizeof( transfer_index_t ) * compressed_count_1 );
|
|
transfer_index_t *compressed = CompressedArray;
|
|
|
|
for( x = 0; x < size; x++, raw++, compressed++ )
|
|
{
|
|
compressed->index = (*raw);
|
|
// zero based (count 0 still implies 1 item in the list, so 256 max entries result)
|
|
compressed->size = GetLengthOfRun( raw, end );
|
|
raw += compressed->size;
|
|
x += compressed->size;
|
|
compressed_count++; // number of entries in compressed table
|
|
}
|
|
|
|
*iSize = compressed_count;
|
|
|
|
if( compressed_count != compressed_count_1 )
|
|
COM_FatalError( "CompressTransferIndicies: internal error\n" );
|
|
|
|
g_transfer_data_size[threadnum] += sizeof( transfer_index_t ) * compressed_count;
|
|
|
|
return CompressedArray;
|
|
}
|
|
#else
|
|
static transfer_index_t *CompressTransferIndicies( const uint *tRaw, const uint rawSize, uint *iSize, int threadnum )
|
|
{
|
|
uint *end = (uint *)tRaw + rawSize;
|
|
uint compressed_count = 0;
|
|
uint *raw = (uint *)tRaw;
|
|
uint x, size = rawSize;
|
|
|
|
if( !size ) return NULL;
|
|
|
|
transfer_index_t *CompressedArray = (transfer_index_t *)Mem_Alloc( sizeof( transfer_index_t ) * size );
|
|
transfer_index_t *compressed = CompressedArray;
|
|
|
|
for( x = 0; x < size; x++, raw++, compressed++ )
|
|
{
|
|
compressed->index = (*raw);
|
|
compressed->size = 0;
|
|
compressed_count++; // number of entries in compressed table
|
|
}
|
|
|
|
*iSize = compressed_count;
|
|
|
|
g_transfer_data_size[threadnum] += sizeof( transfer_index_t ) * compressed_count;
|
|
|
|
return CompressedArray;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
===========
|
|
MakeTransfers
|
|
|
|
This is run by multiple threads
|
|
===========
|
|
*/
|
|
static void MakeTransfers( int threadnum )
|
|
{
|
|
transfer_data_t *tData_All = (transfer_data_t *)Mem_Alloc( sizeof( transfer_data_t ) * ( g_num_patches + 1 ));
|
|
uint *tIndex_All = (uint *)Mem_Alloc( sizeof( transfer_index_t ) * ( g_num_patches + 1 ));
|
|
patch_t **vispatches = (patch_t **)Mem_Alloc(( g_num_patches + 1 ) * sizeof( patch_t* ));
|
|
byte pvs[(MAX_MAP_LEAFS+7)/8];
|
|
const vec_t *normal1, *normal2;
|
|
float trans, total, dist;
|
|
bool check_vis = true;
|
|
patch_t *patch1, *patch2;
|
|
int i, j, lastoffset;
|
|
int count = 0;
|
|
uint *tIndex;
|
|
transfer_data_t *tData;
|
|
vec3_t delta;
|
|
|
|
while( 1 )
|
|
{
|
|
if(( i = GetThreadWork( )) == -1 )
|
|
break;
|
|
|
|
patch1 = g_patches + i;
|
|
|
|
// calculate visibility for the patch
|
|
if( !g_visdatasize )
|
|
{
|
|
if( check_vis )
|
|
{
|
|
memset( pvs, 255, (g_numvisleafs + 7) / 8 );
|
|
check_vis = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dleaf_t *leaf = &g_dleafs[patch1->leafnum];
|
|
int thisoffset = leaf->visofs;
|
|
|
|
if( check_vis || thisoffset != lastoffset )
|
|
{
|
|
if( thisoffset == -1 )
|
|
{
|
|
memset( pvs, 0, (g_numvisleafs + 7) / 8 );
|
|
}
|
|
else
|
|
{
|
|
DecompressVis( &g_dvisdata[leaf->visofs], pvs );
|
|
}
|
|
check_vis = false;
|
|
}
|
|
lastoffset = thisoffset;
|
|
}
|
|
|
|
const dplane_t *plane1 = GetPlaneFromFace( patch1->faceNumber );
|
|
count = 0;
|
|
|
|
// find out which patch2's will collect light from patch
|
|
for( j = 0, patch2 = g_patches; j < g_num_patches; j++, patch2++ )
|
|
{
|
|
if( i == j ) continue;
|
|
|
|
if( !patch2->leafnum || !CHECKVISBIT( pvs, patch2->leafnum - 1 ))
|
|
continue;
|
|
|
|
const dplane_t *plane2 = GetPlaneFromFace( patch2->faceNumber );
|
|
|
|
if( DotProduct( patch1->origin, plane2->normal ) <= PatchPlaneDist( patch2 ) + ON_EPSILON - patch1->emitter_range )
|
|
continue;
|
|
|
|
// we need to do a real test
|
|
vec3_t origin1, origin2, delta;
|
|
vec_t dist;
|
|
|
|
VectorSubtract( patch1->origin, patch2->origin, delta );
|
|
dist = VectorLength( delta );
|
|
|
|
if( dist < patch2->emitter_range - ON_EPSILON )
|
|
GetAlternateOrigin( patch1->origin, plane1->normal, patch2, origin2 );
|
|
else VectorCopy( patch2->origin, origin2 );
|
|
|
|
if( DotProduct( origin2, plane1->normal ) <= PatchPlaneDist( patch1 ) + MINIMUM_PATCH_DISTANCE )
|
|
continue;
|
|
|
|
if( dist < patch1->emitter_range - ON_EPSILON )
|
|
GetAlternateOrigin( patch2->origin, plane2->normal, patch1, origin1 );
|
|
else VectorCopy( patch1->origin, origin1 );
|
|
|
|
if( DotProduct( origin1, plane2->normal ) <= PatchPlaneDist( patch2 ) + MINIMUM_PATCH_DISTANCE )
|
|
continue;
|
|
|
|
// we ignore models here, brushes only
|
|
if( TestLine( threadnum, origin1, origin2, true ) != CONTENTS_EMPTY )
|
|
continue;
|
|
|
|
vispatches[count] = patch2;
|
|
count++;
|
|
}
|
|
|
|
// compute transfers for this patch
|
|
patch1->iIndex = patch1->iData = 0;
|
|
normal1 = plane1->normal;
|
|
tIndex = tIndex_All;
|
|
tData = tData_All;
|
|
|
|
for( j = 0; j < count; j++ )
|
|
{
|
|
bool light_behind_surface = false;
|
|
vec_t dot1, dot2;
|
|
|
|
patch2 = vispatches[j];
|
|
normal2 = GetPlaneFromFace( patch2->faceNumber )->normal;
|
|
|
|
// calculate transference
|
|
VectorSubtract( patch2->origin, patch1->origin, delta );
|
|
|
|
// move emitter back to its plane
|
|
VectorMA( delta, -DEFAULT_HUNT_OFFSET, normal2, delta );
|
|
|
|
dist = VectorNormalize( delta );
|
|
dot1 = DotProduct( delta, normal1 );
|
|
dot2 = -DotProduct( delta, normal2 );
|
|
|
|
if( dot1 <= NORMAL_EPSILON )
|
|
light_behind_surface = true;
|
|
|
|
if( dot2 * dist <= MINIMUM_PATCH_DISTANCE )
|
|
continue;
|
|
|
|
// inverse square falloff factoring angle between patch normals
|
|
trans = (dot1 * dot2) / (dist * dist);
|
|
|
|
// HLRAD_TRANSWEIRDFIX:
|
|
// we should limit "trans( patch2receive ) * patch1area"
|
|
// instead of "trans( patch2receive ) * patch2area".
|
|
// also raise "0.4f" to "0.8f" ( 0.8 / M_PI = 1 / 4).
|
|
if( trans * patch2->area > 0.8f )
|
|
trans = 0.8f / patch2->area;
|
|
|
|
if( dist < patch2->emitter_range - ON_EPSILON )
|
|
{
|
|
if( light_behind_surface )
|
|
trans = 0.0;
|
|
|
|
vec_t sightarea = CalcSightArea( patch1->origin, normal1, patch2->winding, patch2->emitter_skylevel );
|
|
vec_t frac = dist / patch2->emitter_range;
|
|
|
|
frac = (frac - 0.5f) * 2.0f; // make a smooth transition between the two methods
|
|
frac = bound( 0.0, frac, 1.0 );
|
|
trans = frac * trans + (1 - frac) * (sightarea / patch2->area); // because later we will multiply this back
|
|
}
|
|
else if( light_behind_surface )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
trans *= patch2->exposure;
|
|
|
|
// scale to 16 bit (black magic)
|
|
trans = trans * patch2->area * INVERSE_TRANSFER_SCALE;
|
|
if( trans > TRANSFER_SCALE_MAX )
|
|
trans = TRANSFER_SCALE_MAX;
|
|
if( trans <= 0.0 ) continue;
|
|
|
|
*tData = trans;
|
|
patch1->iData++;
|
|
*tIndex = patch2 - g_patches;
|
|
tIndex++;
|
|
tData++;
|
|
}
|
|
|
|
// copy the transfers out
|
|
if( patch1->iData )
|
|
{
|
|
uint data_size = patch1->iData * sizeof( transfer_data_t );
|
|
transfer_data_t *t1, *t2;
|
|
|
|
patch1->tData = (transfer_data_t *)Mem_Alloc( data_size );
|
|
patch1->tIndex = CompressTransferIndicies( tIndex_All, patch1->iData, &patch1->iIndex, threadnum );
|
|
g_transfer_data_size[threadnum] += data_size;
|
|
|
|
total = 0.5 / M_PI;
|
|
t1 = patch1->tData;
|
|
t2 = tData_All;
|
|
|
|
for( uint x = 0; x < patch1->iData; x++, t1++, t2++ )
|
|
(*t1) = (*t2) * total;
|
|
}
|
|
}
|
|
|
|
Mem_Free( vispatches );
|
|
Mem_Free( tIndex_All );
|
|
Mem_Free( tData_All );
|
|
}
|
|
|
|
/*
|
|
============
|
|
CalcTransferSize
|
|
============
|
|
*/
|
|
void CalcTransferSize( void )
|
|
{
|
|
g_transfer_data_bytes = 0;
|
|
|
|
for( int i = 0; i < MAX_THREADS; i++ )
|
|
g_transfer_data_bytes += g_transfer_data_size[i];
|
|
|
|
MsgDev( D_INFO, "transfer lists: %s\n", Q_memprint( g_transfer_data_bytes ));
|
|
}
|
|
|
|
/*
|
|
==============
|
|
MakeTransfers
|
|
==============
|
|
*/
|
|
void MakeTransfers( void )
|
|
{
|
|
RunThreadsOn( g_num_patches, true, MakeTransfers );
|
|
|
|
// display transfer size
|
|
CalcTransferSize();
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CollectLight
|
|
=============
|
|
*/
|
|
void CollectLight( void )
|
|
{
|
|
patch_t *patch;
|
|
int i, j;
|
|
|
|
for( i = 0, patch = g_patches; i < g_num_patches; i++, patch++ )
|
|
{
|
|
for( j = 0; j < MAXLIGHTMAPS && newstyles[i][j] != 255; j++ )
|
|
{
|
|
VectorAdd( patch->totallight[j], addlight[i][j], patch->totallight[j] );
|
|
VectorScale( addlight[i][j], TRANSFER_SCALE, emitlight[i][j] );
|
|
VectorClear( addlight[i][j] );
|
|
#ifdef HLRAD_DELUXEMAPPING
|
|
VectorAdd( patch->totallight_dir[j], addlight_dir[i][j], patch->totallight_dir[j] );
|
|
VectorCopy( addlight_dir[i][j], emitlight_dir[i][j] );
|
|
VectorClear( addlight_dir[i][j] );
|
|
#endif
|
|
}
|
|
|
|
// store new styles back into patch
|
|
memcpy( g_patches[i].totalstyle, newstyles[i], sizeof( byte[MAXLIGHTMAPS] ));
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
BounceLight
|
|
|
|
Get light from other patches
|
|
Run multi-threaded
|
|
=============
|
|
*/
|
|
void BounceLight( int threadnum )
|
|
{
|
|
int j, k, m;
|
|
patch_t *patch;
|
|
vec3_t v;
|
|
|
|
while( 1 )
|
|
{
|
|
if(( j = GetThreadWork()) == -1 )
|
|
break;
|
|
|
|
patch = &g_patches[j];
|
|
|
|
transfer_data_t *tData = patch->tData;
|
|
transfer_index_t *tIndex = patch->tIndex;
|
|
uint iIndex = patch->iIndex;
|
|
int overflowed_styles = 0;
|
|
|
|
for( m = 0; m < MAXLIGHTMAPS && newstyles[j][m] != 255; m++ )
|
|
VectorClear( addlight[j][m] );
|
|
|
|
for( k = 0; k < iIndex; k++, tIndex++ )
|
|
{
|
|
uint size = (tIndex->size + 1);
|
|
uint patchnum = tIndex->index;
|
|
uint l;
|
|
|
|
for( l = 0; l < size; l++, tData++, patchnum++ )
|
|
{
|
|
if( patchnum < 0 || patchnum >= g_num_patches )
|
|
{
|
|
MsgDev( D_ERROR, "bad patchnum %i, max %i\n", patchnum, g_num_patches );
|
|
continue;
|
|
}
|
|
|
|
patch_t *emitpatch = &g_patches[patchnum];
|
|
#ifdef HLRAD_DELUXEMAPPING
|
|
vec3_t direction;
|
|
VectorSubtract( patch->origin, emitpatch->origin, direction );
|
|
VectorNormalize( direction );
|
|
#endif
|
|
// for each style on the emitting patch
|
|
for( int emitstyle = 0; emitstyle < MAXLIGHTMAPS && emitpatch->totalstyle[emitstyle] != 255; emitstyle++ )
|
|
{
|
|
// find the matching style on this (destination) patch
|
|
for( m = 0; m < MAXLIGHTMAPS && newstyles[j][m] != 255; m++ )
|
|
{
|
|
if( newstyles[j][m] == emitpatch->totalstyle[emitstyle] )
|
|
break;
|
|
}
|
|
|
|
if( m < MAXLIGHTMAPS )
|
|
{
|
|
VectorScale( emitlight[patchnum][emitstyle], (float)(*tData), v );
|
|
if( !VectorIsFinite( v )) continue;
|
|
|
|
if( VectorMaximum( v ) < EQUAL_EPSILON )
|
|
continue;
|
|
|
|
if( newstyles[j][m] == 255 )
|
|
newstyles[j][m] = emitpatch->totalstyle[emitstyle];
|
|
|
|
VectorAdd( addlight[j][m], v, addlight[j][m] );
|
|
#ifdef HLRAD_DELUXEMAPPING
|
|
vec_t brightness = VectorAvg( v );
|
|
VectorMA( addlight_dir[j][m], brightness, direction, addlight_dir[j][m] );
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
overflowed_styles++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
g_overflowed_styles_onpatch[threadnum] += overflowed_styles;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
BounceLight
|
|
=============
|
|
*/
|
|
void BounceLight( void )
|
|
{
|
|
int i, j;
|
|
|
|
for( i = 0; i < g_num_patches; i++ )
|
|
{
|
|
patch_t *patch = &g_patches[i];
|
|
|
|
for( j = 0; j < MAXLIGHTMAPS && g_patches[i].totalstyle[j] != 255; j++ )
|
|
{
|
|
VectorScale( patch->totallight[j], TRANSFER_SCALE, emitlight[i][j] );
|
|
#ifdef HLRAD_DELUXEMAPPING
|
|
VectorCopy( patch->totallight_dir[j], emitlight_dir[i][j] );
|
|
#endif
|
|
}
|
|
memcpy( newstyles[i], g_patches[i].totalstyle, sizeof( byte[MAXLIGHTMAPS] ));
|
|
}
|
|
|
|
for( i = 0; i < g_numbounce; i++ )
|
|
{
|
|
RunThreadsOnIncremental( g_num_patches, true, BounceLight, i + 1 );
|
|
CollectLight();
|
|
}
|
|
}
|
|
|
|
//==============================================================
|
|
/*
|
|
=============
|
|
RadWorld
|
|
=============
|
|
*/
|
|
void RadWorld( void )
|
|
{
|
|
MakeBackplanes();
|
|
|
|
// turn each face into a single patch
|
|
MakePatches ();
|
|
|
|
SortPatches ();
|
|
|
|
PairEdges ();
|
|
|
|
BuildFaceNeighbors();
|
|
|
|
BuildDiffuseNormals();
|
|
|
|
// create directlights out of patches and lights
|
|
CreateDirectLights ();
|
|
|
|
if( g_onlylights )
|
|
{
|
|
DeleteDirectLights ();
|
|
FreeDiffuseNormals ();
|
|
FreeFaceNeighbors();
|
|
FreeSharedEdges();
|
|
FreeFaceLights();
|
|
FreePatches();
|
|
|
|
return;
|
|
}
|
|
|
|
InitWorldTrace();
|
|
|
|
// generate a position map for each face
|
|
RunThreadsOnIndividual( g_numfaces, true, FindFacePositions );
|
|
CalcPositionsSize();
|
|
|
|
// build initial facelights
|
|
RunThreadsOnIndividual( g_numfaces, true, BuildFaceLights );
|
|
CalcSampleSize ();
|
|
|
|
#ifdef HLRAD_LIGHTMAPMODELS
|
|
BuildModelLightmaps();
|
|
CalcModelSampleSize();
|
|
#endif
|
|
|
|
#ifdef HLRAD_VERTEXLIGHTING
|
|
BuildVertexLights();
|
|
#endif
|
|
// free up the direct lights now that we have facelights
|
|
DeleteDirectLights ();
|
|
|
|
FreeFacePositions ();
|
|
|
|
CalcLuxelsCount();
|
|
|
|
if( g_numbounce > 0 )
|
|
{
|
|
// build transfer lists
|
|
MakeTransfers();
|
|
|
|
emitlight = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( vec3_t[MAXLIGHTMAPS] ));
|
|
addlight = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( vec3_t[MAXLIGHTMAPS] ));
|
|
newstyles = (byte (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( byte[MAXLIGHTMAPS] ));
|
|
#ifdef HLRAD_DELUXEMAPPING
|
|
emitlight_dir = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( vec3_t[MAXLIGHTMAPS] ));
|
|
addlight_dir = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( vec3_t[MAXLIGHTMAPS] ));
|
|
#endif
|
|
// spread light around
|
|
BounceLight ();
|
|
|
|
Mem_Free( emitlight );
|
|
Mem_Free( addlight );
|
|
Mem_Free( newstyles );
|
|
emitlight = NULL;
|
|
addlight = NULL;
|
|
newstyles = NULL;
|
|
#ifdef HLRAD_DELUXEMAPPING
|
|
Mem_Free( emitlight_dir );
|
|
Mem_Free( addlight_dir );
|
|
emitlight_dir = NULL;
|
|
addlight_dir = NULL;
|
|
#endif
|
|
// transfers don't need anymore
|
|
FreeTransfers();
|
|
}
|
|
|
|
// remove direct light from patches
|
|
for( int i = 0; i < g_num_patches; i++ )
|
|
{
|
|
patch_t *p = &g_patches[i];
|
|
|
|
for( int j = 0; j < MAXLIGHTMAPS && p->totalstyle[j] != 255; j++ )
|
|
{
|
|
VectorSubtract( p->totallight[j], p->directlight[j], p->totallight[j] );
|
|
#ifdef HLRAD_DELUXEMAPPING
|
|
VectorSubtract( p->totallight_dir[j], p->directlight_dir[j], p->totallight_dir[j] );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if( !g_lightbalance )
|
|
ScaleDirectLights();
|
|
|
|
// because fastmode uses patches instead of samples
|
|
if( g_numbounce > 0 || g_fastmode )
|
|
RunThreadsOnIndividual( g_numfaces, false, CreateTriangulations );
|
|
|
|
// blend bounced light into direct light and save
|
|
PrecompLightmapOffsets();
|
|
|
|
if( g_numbounce > 0 || g_fastmode )
|
|
{
|
|
CreateFacelightDependencyList();
|
|
|
|
RunThreadsOnIndividual( g_numfaces, true, FacePatchLights );
|
|
#ifdef HLRAD_VERTEXLIGHTING
|
|
VertexPatchLights();
|
|
#endif
|
|
FreeFacelightDependencyList();
|
|
}
|
|
|
|
FreeDiffuseNormals ();
|
|
|
|
if( g_numbounce > 0 || g_fastmode )
|
|
FreeTriangulations();
|
|
|
|
if( g_lightbalance )
|
|
ScaleDirectLights();
|
|
|
|
RunThreadsOnIndividual( g_numfaces, true, FinalLightFace );
|
|
#ifdef HLRAD_LIGHTMAPMODELS
|
|
FinalModelLightFace();
|
|
#endif
|
|
// now can be freed
|
|
FreeFaceLights();
|
|
|
|
#ifndef HLRAD_PARANOIA_BUMP
|
|
ReduceLightmap();
|
|
#endif
|
|
#ifdef HLRAD_AMBIENTCUBES
|
|
ComputeLeafAmbientLighting();
|
|
#endif
|
|
#ifdef HLRAD_LIGHTMAPMODELS
|
|
WriteModelLighting();
|
|
#endif
|
|
#ifdef HLRAD_VERTEXLIGHTING
|
|
FinalLightVertex();
|
|
UnparseEntities();
|
|
#endif
|
|
FreeFaceNeighbors();
|
|
|
|
FreeSharedEdges();
|
|
|
|
FreePatches();
|
|
}
|
|
|
|
/*
|
|
============
|
|
PrintRadSettings
|
|
|
|
show compiler settings like ZHLT
|
|
============
|
|
*/
|
|
static void PrintRadSettings( void )
|
|
{
|
|
char buf1[1024];
|
|
char buf2[1024];
|
|
|
|
Msg( "\nCurrent p2rad settings\n" );
|
|
Msg( "Name | Setting | Default\n" );
|
|
Msg( "---------------------|-----------|-------------------------\n" );
|
|
Msg( "developer [ %7d ] [ %7d ]\n", GetDeveloperLevel(), DEFAULT_DEVELOPER );
|
|
Msg( "fast rad [ %7s ] [ %7s ]\n", g_fastmode ? "on" : "off", DEFAULT_FASTMODE ? "on" : "off" );
|
|
Msg( "extra rad [ %7s ] [ %7s ]\n", g_extra ? "on" : "off", DEFAULT_EXTRAMODE ? "on" : "off" );
|
|
Msg( "bounces [ %7d ] [ %7d ]\n", g_numbounce, DEFAULT_BOUNCE );
|
|
Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_smoothvalue );
|
|
Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_SMOOTHVALUE );
|
|
Msg( "smoothing threshold [ %7s ] [ %7s ]\n", buf1, buf2 );
|
|
Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_blur );
|
|
Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_BLUR );
|
|
Msg( "blur size [ %7s ] [ %7s ]\n", buf1, buf2 );
|
|
Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_lightbalance ? 1.0f : g_direct_scale );
|
|
Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_DLIGHT_SCALE );
|
|
Msg( "direct light scale [ %7s ] [ %7s ]\n", buf1, buf2 );
|
|
Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_lightbalance ? g_direct_scale : 1.0f );
|
|
Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_LIGHT_SCALE );
|
|
Msg( "global light scale [ %7s ] [ %7s ]\n", buf1, buf2 );
|
|
Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_gamma );
|
|
Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_GAMMA );
|
|
Msg( "gamma factor [ %7s ] [ %7s ]\n", buf1, buf2 );
|
|
Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_chop );
|
|
Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_CHOP );
|
|
Msg( "chop value [ %7s ] [ %7s ]\n", buf1, buf2 );
|
|
Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_texchop );
|
|
Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_TEXCHOP );
|
|
Msg( "texchop value [ %7s ] [ %7s ]\n", buf1, buf2 );
|
|
Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_indirect_sun );
|
|
Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_INDIRECT_SUN );
|
|
Msg( "global sky diffusion [ %7s ] [ %7s ]\n", buf1, buf2 );
|
|
Msg( "dirtmapping [ %7s ] [ %7s ]\n", g_dirtmapping ? "on" : "off", DEFAULT_DIRTMAPPING ? "on" : "off" );
|
|
#ifdef HLRAD_PARANOIA_BUMP
|
|
Msg( "gamma mode [ %7d ] [ %7d ]\n", g_gammamode, DEFAULT_GAMMAMODE );
|
|
#endif
|
|
Msg( "\n" );
|
|
}
|
|
|
|
/*
|
|
============
|
|
PrintRadUsage
|
|
|
|
show compiler usage like ZHLT
|
|
============
|
|
*/
|
|
static void PrintRadUsage( void )
|
|
{
|
|
Msg( "\n-= p2rad Options =-\n\n" );
|
|
Msg( " -dev # : compile with developer message (1 - 4). default is %d\n", DEFAULT_DEVELOPER );
|
|
Msg( " -threads # : manually specify the number of threads to run\n" );
|
|
Msg( " -extra : improve lighting quality with lightmap filtering\n" );
|
|
Msg( " -bounce # : set number of radiosity bounces\n" );
|
|
Msg( " -ambient r g b : set ambient world light (0.0 to 1.0, r g b)\n" );
|
|
Msg( " -smooth # : set smoothing threshold for blending (in degrees)\n" );
|
|
Msg( " -blur : filtering the lightmap by post-processing\n" );
|
|
Msg( " -dscale : direct light scaling factor\n" );
|
|
Msg( " -gamma : set global gamma value\n" );
|
|
Msg( " -chop # : set radiosity patch size for normal textures\n" );
|
|
Msg( " -texchop # : set radiosity patch size for texture light faces\n" );
|
|
Msg( " -sky # : set ambient sunlight contribution in the shade outside\n" );
|
|
Msg( " -nomodelshadow : ignore shadows from alias and studiomodels\n" );
|
|
Msg( " -balance : -dscale will be interpret as global scaling factor\n" );
|
|
Msg( " -dirty : enable dirtmapping (baked AO)\n" );
|
|
Msg( " -onlylights : update only worldlights lump\n" );
|
|
#ifdef HLRAD_PARANOIA_BUMP
|
|
Msg( " -gammamode # : gamma correction mode (0, 1, 2)\n" );
|
|
#endif
|
|
Msg( " bspfile : The bspfile to compile\n\n" );
|
|
|
|
exit( 1 );
|
|
}
|
|
|
|
/*
|
|
========
|
|
main
|
|
|
|
light modelfile
|
|
========
|
|
*/
|
|
int main( int argc, char **argv )
|
|
{
|
|
double start, end;
|
|
char str[64];
|
|
int i;
|
|
|
|
atexit( Sys_CloseLog );
|
|
source[0] = '\0';
|
|
|
|
g_smoothing_threshold = cos( DEG2RAD( g_smoothvalue )); // Originally zero.
|
|
|
|
for( i = 1; i < argc; i++ )
|
|
{
|
|
if( !Q_strcmp( argv[i], "-dev" ))
|
|
{
|
|
SetDeveloperLevel( atoi( argv[i+1] ));
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-threads" ))
|
|
{
|
|
g_numthreads = atoi( argv[i+1] );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-fast" ))
|
|
{
|
|
g_nomodelshadow = true;
|
|
g_lerp_enabled = false;
|
|
g_dirtmapping = false;
|
|
g_fastmode = true;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-extra" ))
|
|
{
|
|
g_lerp_enabled = true;
|
|
g_extra = true;
|
|
g_blur = 1.5f;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-bounce" ))
|
|
{
|
|
g_numbounce = atoi( argv[i+1] );
|
|
g_numbounce = bound( 0, g_numbounce, 128 );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-ambient" ))
|
|
{
|
|
if( argc > ( i + 3 ))
|
|
{
|
|
g_ambient[0] = (float)atof( argv[i+1] ) * 0.5f;
|
|
g_ambient[1] = (float)atof( argv[i+2] ) * 0.5f;
|
|
g_ambient[2] = (float)atof( argv[i+3] ) * 0.5f;
|
|
i += 3;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-smooth" ))
|
|
{
|
|
g_smoothvalue = atof( argv[i+1] );
|
|
g_smoothing_threshold = (float)cos( DEG2RAD( g_smoothvalue ));
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-blur" ))
|
|
{
|
|
g_blur = atof( argv[i+1] );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-dscale" ))
|
|
{
|
|
g_direct_scale = (float)atof( argv[i+1] );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-gamma" ))
|
|
{
|
|
g_gamma = (float)atof( argv[i+1] );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-chop" ))
|
|
{
|
|
g_chop = (float)atof( argv[i+1] );
|
|
g_chop = bound( 32, g_chop, 128 );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-texchop" ))
|
|
{
|
|
g_texchop = (float)atof( argv[i+1] );
|
|
g_texchop = bound( 32, g_texchop, 128 );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-sky" ))
|
|
{
|
|
g_indirect_sun = (float)atof( argv[i+1] );
|
|
g_indirect_sun = bound( 0.0f, g_indirect_sun, 2.0f );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-nomodelshadow" ))
|
|
{
|
|
g_nomodelshadow = true;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-balance" ))
|
|
{
|
|
g_lightbalance = true;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-onlylights" ))
|
|
{
|
|
g_onlylights = true;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-quake" ))
|
|
{
|
|
// special preset for quake
|
|
g_lightbalance = true;
|
|
g_indirect_scale = 1.0f;
|
|
g_direct_scale = 0.5f;
|
|
g_indirect_sun = 0.0f;
|
|
g_gamma = 1.0f;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-dirty" ))
|
|
{
|
|
g_dirtmapping = !g_fastmode;
|
|
}
|
|
#ifdef HLRAD_PARANOIA_BUMP
|
|
else if( !Q_strcmp( argv[i], "-gammamode" ))
|
|
{
|
|
g_gammamode = (float)atoi( argv[i+1] );
|
|
i++;
|
|
}
|
|
#endif
|
|
else if( argv[i][0] == '-' )
|
|
{
|
|
MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] );
|
|
break;
|
|
}
|
|
else if( !source[0] )
|
|
{
|
|
Q_strncpy( source, COM_ExpandArg( argv[i] ), sizeof( source ));
|
|
COM_StripExtension( source );
|
|
}
|
|
else
|
|
{
|
|
MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( i != argc || !source[0] )
|
|
{
|
|
if( !source[0] )
|
|
Msg( "no mapfile specified\n" );
|
|
PrintRadUsage();
|
|
}
|
|
|
|
start = I_FloatTime ();
|
|
|
|
Sys_InitLogAppend( va( "%s.log", source ));
|
|
|
|
Msg( "\n%s %s (%s)\n", TOOLNAME, VERSIONSTRING, __DATE__ );
|
|
|
|
PrintRadSettings();
|
|
|
|
ThreadSetDefault ();
|
|
|
|
// starting base filesystem
|
|
FS_Init( source );
|
|
|
|
// Set the required global lights filename
|
|
// try looking in the directory we were run from
|
|
GetModuleFileName( NULL, global_lights, sizeof( global_lights ));
|
|
COM_ExtractFilePath( global_lights, global_lights );
|
|
Q_strncat( global_lights, "\\lights.rad", sizeof( global_lights ));
|
|
|
|
// Set the optional level specific lights filename
|
|
COM_FileBase( source, str );
|
|
Q_snprintf( level_lights, sizeof( level_lights ), "maps/%s.rad", str );
|
|
if( !FS_FileExists( level_lights, false )) level_lights[0] = '\0';
|
|
|
|
ReadLightFile( global_lights, true ); // Required
|
|
if( *level_lights ) ReadLightFile( level_lights, false ); // Optional & implied
|
|
|
|
COM_DefaultExtension( source, ".bsp" );
|
|
|
|
LoadBSPFile( source );
|
|
|
|
if( g_nummodels <= 0 )
|
|
COM_FatalError( "map %s without any models\n", source );
|
|
|
|
ParseEntities();
|
|
TEX_LoadTextures();
|
|
ReadInfoTexlights();
|
|
SetupDirt();
|
|
|
|
if( !g_visdatasize && g_numbounce > 0 )
|
|
MsgDev( D_ERROR, "no vis information, compile time may be adversely affected.\n" );
|
|
|
|
// keep it in acceptable range
|
|
g_blur = bound( 1.0, g_blur, 8.0 );
|
|
g_gamma = bound( 0.3, g_gamma, 1.0 );
|
|
|
|
RadWorld ();
|
|
|
|
WriteBSPFile( source );
|
|
TEX_FreeTextures ();
|
|
FreeWorldTrace ();
|
|
FreeEntities ();
|
|
FS_Shutdown();
|
|
|
|
SetDeveloperLevel( D_REPORT );
|
|
Mem_Check();
|
|
|
|
end = I_FloatTime ();
|
|
Q_timestring((int)( end - start ), str );
|
|
Msg( "%s elapsed\n", str );
|
|
|
|
return 0;
|
|
} |