Paranoia2/utils/p2rad/vertexlight.cpp
2020-08-31 19:50:41 +03:00

757 lines
19 KiB
C++

/***
*
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
****/
#include "qrad.h"
#include "model_trace.h"
#include "..\..\engine\studio.h"
#ifdef HLRAD_VERTEXLIGHTING
#define MAX_INDIRECT_DIST 1024.0f
typedef struct
{
int modelnum : 10;
int vertexnum : 22;
} vertremap_t;
static entity_t *g_vertexlight[MAX_MAP_MODELS];
static int g_vertexlight_modnum;
static vertremap_t *g_vertexlight_indexes;
static uint g_vertexlight_numindexes;
static vec3_t g_box_directions[6] =
{
{ 1.0f, 0.0f, 0.0f },
{-1.0f, 0.0f, 0.0f },
{ 0.0f, 1.0f, 0.0f },
{ 0.0f, -1.0f, 0.0f },
{ 0.0f, 0.0f, 1.0f },
{ 0.0f, 0.0f, -1.0f },
};
void NudgeVertexPosition( vec3_t position )
{
vec3_t test;
int x;
VectorCopy( position, test );
if( PointInLeaf( test ) != g_dleafs )
return;
for( x = 0; x < 6; x++ )
{
VectorMA( position, 2.0f, g_box_directions[x], test );
if( PointInLeaf( test )->contents != CONTENTS_SOLID )
{
VectorCopy( test, position );
return;
}
}
for( x = 0; x < 6; x++ )
{
VectorMA( position, 3.0f, g_box_directions[x], test );
if( PointInLeaf( test )->contents != CONTENTS_SOLID )
{
VectorCopy( test, position );
return;
}
}
}
void GetStylesFromMesh( byte newstyles[4], const tmesh_t *mesh )
{
ThreadLock();
memcpy( newstyles, mesh->styles, sizeof( newstyles ));
ThreadUnlock();
}
void AddStylesToMesh( tmesh_t *mesh, byte newstyles[4] )
{
int i, j;
// store styles back to the mesh
ThreadLock();
for( i = 0; i < MAXLIGHTMAPS && newstyles[i] != 255; i++ )
{
for( j = 0; j < MAXLIGHTMAPS; j++ )
{
if( mesh->styles[j] == newstyles[i] || mesh->styles[j] == 255 )
break;
}
if( j == MAXLIGHTMAPS )
continue;
// allocate a new one
if( mesh->styles[j] == 255 )
mesh->styles[j] = newstyles[i];
}
ThreadUnlock();
}
void SmoothModelNormals( int modelnum, int threadnum = -1 )
{
entity_t *mapent = g_vertexlight[modelnum];
tmesh_t *mesh;
if( g_smoothing_threshold == 0 )
return;
// sanity check
if( !mapent || !mapent->cache )
return;
mesh = (tmesh_t *)mapent->cache;
if( !mesh->verts || mesh->numverts <= 0 )
return;
// build the smoothed normals
if( FBitSet( mesh->flags, FMESH_DONT_SMOOTH ))
return;
vec3_t *normals = (vec3_t *)Mem_Alloc( mesh->numverts * sizeof( vec3_t ));
for( int hashSize = 1; hashSize < mesh->numverts; hashSize <<= 1 );
hashSize = hashSize >> 2;
// build a map from vertex to a list of triangles that share the vert.
CUtlArray<CIntVector> vertHashMap;
vertHashMap.AddMultipleToTail( hashSize );
for( int vertID = 0; vertID < mesh->numverts; vertID++ )
{
tvert_t *tv = &mesh->verts[vertID];
uint hash = VertexHashKey( tv->point, hashSize );
vertHashMap[hash].AddToTail( vertID );
}
for( int hashID = 0; hashID < hashSize; hashID++ )
{
for( int i = 0; i < vertHashMap[hashID].Size(); i++ )
{
int vertID = vertHashMap[hashID][i];
tvert_t *tv0 = &mesh->verts[vertID];
for( int j = 0; j < vertHashMap[hashID].Size(); j++ )
{
tvert_t *tv1 = &mesh->verts[vertHashMap[hashID][j]];
if( !VectorCompareEpsilon( tv0->point, tv1->point, ON_EPSILON ))
continue;
if( DotProduct( tv0->normal, tv1->normal ) >= g_smoothing_threshold )
VectorAdd( normals[vertID], tv1->normal, normals[vertID] );
}
}
}
// copy smoothed normals back
for( int j = 0; j < mesh->numverts; j++ )
{
VectorCopy( normals[j], mesh->verts[j].normal );
VectorNormalize2( mesh->verts[j].normal );
}
Mem_Free( normals );
}
void BuildVertexLights( int indexnum, int thread = -1 )
{
int modelnum = g_vertexlight_indexes[indexnum].modelnum;
int vertexnum = g_vertexlight_indexes[indexnum].vertexnum;
entity_t *mapent = g_vertexlight[modelnum];
float shadow[MAXLIGHTMAPS];
vec3_t light[MAXLIGHTMAPS];
vec3_t delux[MAXLIGHTMAPS];
entity_t *ignoreent = NULL;
byte *vislight = NULL;
byte styles[4];
vec3_t normal;
tmesh_t *mesh;
int j;
// sanity check
if( !mapent || !mapent->cache )
return;
mesh = (tmesh_t *)mapent->cache;
if( !mesh->verts || mesh->numverts <= 0 )
return;
if( !FBitSet( mesh->flags, FMESH_SELF_SHADOW ))
ignoreent = mapent;
GetStylesFromMesh( styles, mesh );
// stats
g_direct_luxels[thread]++;
#ifdef HLRAD_COMPUTE_VISLIGHTMATRIX
vislight = mesh->vislight;
#endif
// vertexlighting is easy. We don't needs to find valid points etc
tvert_t *tv = &mesh->verts[vertexnum];
vec3_t point;
// not supposed for vertex lighting?
if( !tv->light ) return;
// nudge position from ground
NudgeVertexPosition( tv->light->pos ); // nudged vertexes will be used on indirect lighting too
VectorCopy( tv->normal, normal );
// calculate visibility for the sample
int leaf = PointInLeaf( tv->light->pos ) - g_dleafs;
if( FBitSet( mesh->flags, FMESH_SELF_SHADOW ))
VectorMA( tv->light->pos, DEFAULT_HUNT_OFFSET, tv->normal, point );
else VectorCopy( tv->light->pos, point );
memset( light, 0, sizeof( light ));
memset( delux, 0, sizeof( delux ));
memset( shadow, 0, sizeof( shadow ));
// gather direct lighting for our vertex
GatherSampleLight( thread, -1, point, leaf, normal, light, delux, shadow, styles, vislight, 0, ignoreent );
// add an ambient term if desired
if( g_ambient[0] || g_ambient[1] || g_ambient[2] )
{
for( j = 0; j < MAXLIGHTMAPS && styles[j] == 255; j++ );
if( j == MAXLIGHTMAPS ) styles[0] = 0; // adding style
for( j = 0; j < MAXLIGHTMAPS && styles[j] != 255; j++ )
{
if( styles[j] == 0 )
{
VectorAdd( light[j], g_ambient, light[j] );
#ifdef HLRAD_DELUXEMAPPING
vec_t avg = VectorAvg( g_ambient );
VectorMA( delux[j], -DIFFUSE_DIRECTION_SCALE * avg, normal, delux[j] );
#endif
break;
}
}
}
if( !g_lightbalance )
{
for( int k = 0; k < MAXLIGHTMAPS; k++ )
{
VectorScale( light[k], g_direct_scale, light[k] );
VectorScale( delux[k], g_direct_scale, delux[k] );
}
}
// grab indirect lighting for vertex from light_environment or lighting vertex in -fast mode
GatherSampleLight( thread, -1, point, leaf, normal, light, delux, shadow, styles, NULL, 1, ignoreent );
// store results back into the vertex
for( j = 0; j < MAXLIGHTMAPS && styles[j] != 255; j++ )
{
VectorCopy( light[j], tv->light->light[j] );
VectorCopy( delux[j], tv->light->deluxe[j] );
tv->light->shadow[j] = shadow[j];
}
AddStylesToMesh( mesh, styles );
}
bool AddPatchStyleToMesh( trace_t *trace, tmesh_t *mesh, tvert_t *tv, vec3_t *s_light, vec3_t *s_dir, byte newstyles[4] )
{
vec3_t sampled_light[MAXLIGHTMAPS];
vec3_t sampled_dir[MAXLIGHTMAPS];
int i, lightstyles;
vec_t dot, total;
int numpatches;
const int *patches;
vec3_t v, dir;
if( trace->surface < 0 || trace->surface >= g_numfaces )
return false;
GetTriangulationPatches( trace->surface, &numpatches, &patches ); // collect patches and their neighbors
memset( sampled_light, 0, sizeof( sampled_light ));
memset( sampled_dir, 0, sizeof( sampled_dir ));
total = 0.0f;
for( i = 0; i < numpatches; i++ )
{
patch_t *p = &g_patches[patches[i]];
if( FBitSet( p->flags, PATCH_OUTSIDE ))
continue;
for( int j = 0; j < MAXLIGHTMAPS && p->totalstyle[j] != 255; j++ )
{
if( p->samples[j] <= 0.0 )
continue;
for( lightstyles = 0; lightstyles < MAXLIGHTMAPS && newstyles[lightstyles] != 255; lightstyles++ )
{
if( newstyles[lightstyles] == p->totalstyle[j] )
break;
}
if( lightstyles == MAXLIGHTMAPS )
{
// overflowed
continue;
}
else if( newstyles[lightstyles] == 255 )
{
newstyles[lightstyles] = p->totalstyle[j];
}
const vec_t *b = GetTotalLight( p, p->totalstyle[j] );
VectorScale( b, (1.0), v );
VectorAdd( sampled_light[lightstyles], v, sampled_light[lightstyles] );
const vec_t *d = GetTotalDirection(p, p->totalstyle[j] );
VectorScale( d, (1.0), v );
VectorCopy( v, dir );
VectorNormalize( dir );
dot = DotProduct( dir, tv->normal );
if(( -dot ) > 0 )
{
// reflect the direction back (this is not ideal!)
VectorMA( v, -(-dot) * 2.0f, tv->normal, v );
}
VectorAdd( sampled_dir[lightstyles], v, sampled_dir[lightstyles] );
total++;
}
}
// add light to vertex
for( int k = 0; k < MAXLIGHTMAPS && newstyles[k] != 255; k++ )
{
VectorScale( sampled_light[k], 1.0f / total, sampled_light[k] );
VectorScale( sampled_dir[k], 1.0f / total, sampled_dir[k] );
if( VectorMaximum( sampled_light[k] ) >= EQUAL_EPSILON )
{
if( s_light ) VectorAdd( s_light[k], sampled_light[k], s_light[k] );
if( s_dir ) VectorAdd( s_dir[k], sampled_dir[k], s_dir[k] );
}
}
return true;
}
/*
============
VertexPatchLights
This function is run multithreaded
============
*/
void VertexPatchLights( int indexnum, int threadnum = -1 )
{
int modelnum = g_vertexlight_indexes[indexnum].modelnum;
int vertexnum = g_vertexlight_indexes[indexnum].vertexnum;
vec3_t *skynormals = g_skynormals[SKYLEVEL_SOFTSKYOFF];
entity_t *mapent = g_vertexlight[modelnum];
vec3_t sampled_light[MAXLIGHTMAPS];
vec3_t sampled_dir[MAXLIGHTMAPS];
byte newstyles[4];
trace_t besttrace;
vec_t total;
trace_t trace;
tmesh_t *mesh;
vec3_t delta;
vec_t dot;
// sanity check
if( !mapent || !mapent->cache )
return;
mesh = (tmesh_t *)mapent->cache;
if( !mesh->verts || mesh->numverts <= 0 )
return;
besttrace.surface = -1;
besttrace.fraction = 1.0f;
besttrace.contents = CONTENTS_EMPTY;
GetStylesFromMesh( newstyles, mesh );
tvert_t *tv = &mesh->verts[vertexnum];
// not supposed for vertex lighting?
if( !tv->light ) return;
memset( sampled_light, 0, sizeof( sampled_light ));
memset( sampled_dir, 0, sizeof( sampled_dir ));
total = 0.0f;
// NOTE: we can't using patches directly because they linked to faces
// we should find nearest face with matched normal and just nearest face
// then lerp between them
for( int j = 0; j < g_numskynormals[SKYLEVEL_SOFTSKYOFF]; j++ )
{
dot = -DotProduct( skynormals[j], tv->normal );
// if( dot <= NORMAL_EPSILON ) continue;
VectorScale( skynormals[j], -MAX_INDIRECT_DIST, delta );
VectorAdd( tv->light->pos, delta, delta );
TestLine( threadnum, tv->light->pos, delta, &trace );
if( trace.surface == -1 )
continue;
if( trace.fraction < besttrace.fraction )
besttrace = trace;
AddPatchStyleToMesh( &trace, mesh, tv, sampled_light, sampled_dir, newstyles );
total++;
}
if( total <= 0 ) return;
// add light to vertex
for( int k = 0; k < MAXLIGHTMAPS && mesh->styles[k] != 255; k++ )
{
VectorScale( sampled_light[k], 1.0f / total, sampled_light[k] );
VectorScale( sampled_dir[k], 1.0f / total, sampled_dir[k] );
VectorScale( sampled_light[k], g_indirect_scale, sampled_light[k] );
VectorScale( sampled_dir[k], g_indirect_scale, sampled_dir[k] );
if( VectorMaximum( sampled_light[k] ) >= EQUAL_EPSILON )
{
VectorAdd( tv->light->light[k], sampled_light[k], tv->light->light[k] );
VectorAdd( tv->light->deluxe[k], sampled_dir[k], tv->light->deluxe[k] );
}
}
AddStylesToMesh( mesh, newstyles );
}
void FinalLightVertex( int modelnum, int threadnum = -1 )
{
entity_t *mapent = g_vertexlight[modelnum];
int usedstyles[MAXLIGHTMAPS];
vec3_t lb, v, direction;
int lightstyles;
vec_t minlight;
tmesh_t *mesh;
dmodelvertlight_t *dml;
dvlightlump_t *l;
// sanity check
if( !mapent || !mapent->cache )
return;
mesh = (tmesh_t *)mapent->cache;
if( !mesh->verts || mesh->numverts <= 0 )
return;
for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ )
{
if( mesh->styles[lightstyles] == 255 )
break;
}
// completely black model
if( !lightstyles ) return;
memset( usedstyles, 0, sizeof( usedstyles ));
l = (dvlightlump_t *)g_dvlightdata;
ASSERT( l->dataofs[modelnum] != -1 );
dml = (dmodelvertlight_t *)((byte *)g_dvlightdata + l->dataofs[modelnum]);
ASSERT( dml->numverts == mesh->numverts );
minlight = FloatForKey( mapent, "_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
// vertexlighting is easy. We don't needs to find valid points etc
for( int i = 0; i < mesh->numverts; i++ )
{
tvert_t *tv = &mesh->verts[i];
dvertlight_t *dvl = &dml->verts[i];
int j;
if( !tv->light ) continue;
for( int k = 0; k < lightstyles; k++ )
{
VectorCopy( tv->light->light[k], lb );
VectorCopy( tv->light->deluxe[k], direction );
if( VectorMax( lb ) > EQUAL_EPSILON )
usedstyles[k]++;
if( g_lightbalance )
{
VectorScale( lb, g_direct_scale, lb );
VectorScale( direction, g_direct_scale, direction );
}
vec_t avg = VectorAvg( lb );
VectorScale( direction, 1.0 / Q_max( 1.0, avg ), direction );
// 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( j = 0; j < 3; j++ )
lb[j] = ( lb[j] * 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
dvl->light[k].r = Q_rint( lb[0] );
dvl->light[k].g = Q_rint( lb[1] );
dvl->light[k].b = Q_rint( lb[2] );
#else
dvl->light[k].r = (byte)lb[0];
dvl->light[k].g = (byte)lb[1];
dvl->light[k].b = (byte)lb[2];
#endif
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
// keep deluxe vectors in modelspace for vertex lighting
for( int x = 0; x < 3; x++ )
{
lb[x] = v[x] * 127.0f + 128.0f;
lb[x] = bound( 0, lb[x], 255.0 );
}
dvl->deluxe[k].r = (byte)lb[0];
dvl->deluxe[k].g = (byte)lb[1];
dvl->deluxe[k].b = (byte)lb[2];
// shadows are simple
dvl->shadow[k] = tv->light->shadow[k] * 255;
}
}
for( int k = 0; k < lightstyles; k++ )
{
if( usedstyles[k] == 0 )
mesh->styles[k] = 255;
}
}
static void GenerateLightCacheNumbers( void )
{
char string[32];
tmesh_t *mesh;
int i, j;
for( i = 1; i < g_numentities; i++ )
{
entity_t *mapent = &g_entities[i];
// no cache - no lighting
if( !mapent->cache ) continue;
mesh = (tmesh_t *)mapent->cache;
if( mapent->modtype != mod_alias && mapent->modtype != mod_studio )
continue;
if( !mesh->verts || mesh->numverts <= 0 )
continue;
if( !FBitSet( mesh->flags, FMESH_VERTEX_LIGHTING ))
continue;
short lightid = g_vertexlight_modnum++;
// at this point we have valid target for vertex lighting
Q_snprintf( string, sizeof( string ), "%i", lightid + 1 );
SetKeyValue( mapent, "vlight_cache", string );
g_vertexlight[lightid] = mapent;
}
g_vertexlight_numindexes = 0;
// generate remapping table for more effective CPU utilize
for( i = 0; i < g_vertexlight_modnum; i++ )
{
entity_t *mapent = g_vertexlight[i];
mesh = (tmesh_t *)mapent->cache;
g_vertexlight_numindexes += mesh->numverts;
}
g_vertexlight_indexes = (vertremap_t *)Mem_Alloc( g_vertexlight_numindexes * sizeof( vertremap_t ));
uint curIndex = 0;
for( i = 0; i < g_vertexlight_modnum; i++ )
{
entity_t *mapent = g_vertexlight[i];
mesh = (tmesh_t *)mapent->cache;
// encode model as lowpart and vertexnum as highpart
for( j = 0; j < mesh->numverts; j++ )
{
g_vertexlight_indexes[curIndex].modelnum = i;
g_vertexlight_indexes[curIndex].vertexnum = j;
curIndex++;
}
ASSERT( curIndex <= g_vertexlight_numindexes );
}
}
static int ModelSize( tmesh_t *mesh )
{
if( !mesh ) return 0;
if( !mesh->verts || mesh->numverts <= 0 )
return 0;
for( int lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ )
{
if( mesh->styles[lightstyles] == 255 )
break;
}
// model is valid but completely not lighted by direct
if( !lightstyles ) return 0;
return sizeof( dmodelvertlight_t ) - ( sizeof( dvertlight_t ) * 3 ) + sizeof( dvertlight_t ) * mesh->numverts + ((g_numworldlights + 7) / 8);
}
static int WriteModelLight( tmesh_t *mesh, byte *out )
{
int size = ModelSize( mesh );
dmodelvertlight_t *dml;
if( !size ) return 0;
dml = (dmodelvertlight_t *)out;
out += sizeof( dmodelvertlight_t ) - ( sizeof( dvertlight_t ) * 3 ) + sizeof( dvertlight_t ) * mesh->numverts;
dml->modelCRC = mesh->modelCRC;
dml->numverts = mesh->numverts;
memcpy( dml->styles, mesh->styles, sizeof( dml->styles ));
memcpy( dml->submodels, mesh->vsubmodels, sizeof( dml->submodels ));
memcpy( out, mesh->vislight, ((g_numworldlights + 7) / 8));
return size;
}
static void AllocVertexLighting( void )
{
int totaldatasize = ( sizeof( int ) * 3 ) + ( sizeof( int ) * g_vertexlight_modnum );
int i, len;
byte *data;
dvlightlump_t *l;
for( i = 0; i < g_vertexlight_modnum; i++ )
{
entity_t *mapent = g_vertexlight[i];
// sanity check
if( !mapent ) continue;
len = ModelSize( (tmesh_t *)mapent->cache );
totaldatasize += len;
}
Msg( "total vertexlight data: %s\n", Q_memprint( totaldatasize ));
g_dvlightdata = (byte *)Mem_Realloc( g_dvlightdata, totaldatasize );
// now setup to get the miptex data (or just the headers if using -wadtextures) from the wadfile
l = (dvlightlump_t *)g_dvlightdata;
data = (byte *)&l->dataofs[g_vertexlight_modnum];
l->ident = VLIGHTIDENT;
l->version = VLIGHT_VERSION;
l->nummodels = g_vertexlight_modnum;
for( i = 0; i < g_vertexlight_modnum; i++ )
{
entity_t *mapent = g_vertexlight[i];
l->dataofs[i] = data - (byte *)l;
len = WriteModelLight( (tmesh_t *)mapent->cache, data );
if( !len ) l->dataofs[i] = -1; // completely black model
data += len;
}
g_vlightdatasize = data - g_dvlightdata;
if( totaldatasize != g_vlightdatasize )
COM_FatalError( "WriteVertexLighting: memory corrupted\n" );
// vertex cache acesss
// const char *id = ValueForKey( ent, "vlight_cache" );
// int cacheID = atoi( id ) - 1;
// if( cacheID < 0 || cacheID > num_map_entities ) return; // bad cache num
// if( l->dataofs[cacheID] == -1 ) return; // cache missed
// otherwise it's valid
}
void BuildVertexLights( void )
{
GenerateLightCacheNumbers();
// new code is very fast, so no reason to show progress
RunThreadsOnIndividual( g_vertexlight_modnum, false, SmoothModelNormals );
if( !g_vertexlight_numindexes ) return;
RunThreadsOnIndividual( g_vertexlight_numindexes, true, BuildVertexLights );
}
void VertexPatchLights( void )
{
if( !g_vertexlight_numindexes ) return;
RunThreadsOnIndividual( g_vertexlight_numindexes, true, VertexPatchLights );
}
void FinalLightVertex( void )
{
if( !g_vertexlight_modnum ) return;
AllocVertexLighting();
RunThreadsOnIndividual( g_vertexlight_modnum, true, FinalLightVertex );
Mem_Free( g_vertexlight_indexes );
g_vertexlight_indexes = NULL;
g_vertexlight_numindexes = 0;
}
#endif