engine: common: implement PHS calculation like in QuakeWorld/GoldSrc
This commit is contained in:
parent
27cab8aad5
commit
338399e622
3 changed files with 180 additions and 3 deletions
|
@ -24,6 +24,9 @@ GNU General Public License for more details.
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "server.h" // LUMP_ error codes
|
#include "server.h" // LUMP_ error codes
|
||||||
#include "ref_common.h"
|
#include "ref_common.h"
|
||||||
|
#if defined( HAVE_OPENMP )
|
||||||
|
#include <omp.h>
|
||||||
|
#endif // HAVE_OPENMP
|
||||||
|
|
||||||
#define MIPTEX_CUSTOM_PALETTE_SIZE_BYTES ( sizeof( int16_t ) + 768 )
|
#define MIPTEX_CUSTOM_PALETTE_SIZE_BYTES ( sizeof( int16_t ) + 768 )
|
||||||
|
|
||||||
|
@ -625,7 +628,7 @@ static byte *Mod_DecompressPVS( const byte *in, int visbytes )
|
||||||
return g_visdata;
|
return g_visdata;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t Mod_CompressPVS( byte *out, const byte *in, size_t inbytes )
|
static size_t Mod_CompressPVS( byte *const out, const byte *in, size_t inbytes )
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t i;
|
||||||
byte *dst = out;
|
byte *dst = out;
|
||||||
|
@ -724,7 +727,17 @@ static void Mod_FatPVS_RecursiveBSPNode( const vec3_t org, float radius, byte *v
|
||||||
// if this leaf is in a cluster, accumulate the vis bits
|
// if this leaf is in a cluster, accumulate the vis bits
|
||||||
if(((mleaf_t *)node)->cluster >= 0 )
|
if(((mleaf_t *)node)->cluster >= 0 )
|
||||||
{
|
{
|
||||||
byte *vis = Mod_DecompressPVS( ((mleaf_t *)node)->compressed_vis, world.visbytes );
|
byte *vis;
|
||||||
|
|
||||||
|
if( phs )
|
||||||
|
{
|
||||||
|
int i = ((mleaf_t *)node)->cluster + 1;
|
||||||
|
vis = Mod_DecompressPVS( &world.compressed_phs[world.phsofs[i]], world.visbytes );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vis = Mod_DecompressPVS( ((mleaf_t *)node)->compressed_vis, world.visbytes );
|
||||||
|
}
|
||||||
|
|
||||||
Q_memor( visbuffer, vis, visbytes );
|
Q_memor( visbuffer, vis, visbytes );
|
||||||
}
|
}
|
||||||
|
@ -755,6 +768,14 @@ int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, q
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requested PHS but we don't have PHS for some reason
|
||||||
|
// enable full visibility
|
||||||
|
if( phs && !( world.compressed_phs && world.phsofs ))
|
||||||
|
{
|
||||||
|
memset( visbuffer, 0xFF, bytes );
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
if( !merge ) memset( visbuffer, 0x00, bytes );
|
if( !merge ) memset( visbuffer, 0x00, bytes );
|
||||||
|
|
||||||
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, bytes, worldmodel->nodes, phs );
|
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, bytes, worldmodel->nodes, phs );
|
||||||
|
@ -2728,6 +2749,153 @@ static void Mod_LoadLeafs( model_t *mod, dbspmodel_t *bmod )
|
||||||
SetBits( world.flags, FWORLD_WATERALPHA );
|
SetBits( world.flags, FWORLD_WATERALPHA );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
===========
|
||||||
|
Mod_CalcPHS
|
||||||
|
|
||||||
|
To be called while loading world for multiplayer game server
|
||||||
|
===========
|
||||||
|
*/
|
||||||
|
static void Mod_CalcPHS( model_t *mod )
|
||||||
|
{
|
||||||
|
const qboolean vis_stats = host_developer.value >= DEV_EXTENDED;
|
||||||
|
const size_t rowbytes = ALIGN( world.visbytes, 4 ); // force align rows by 32-bit boundary
|
||||||
|
const size_t count = mod->numleafs + 1; // same as mod->submodels[0].visleafs + 1
|
||||||
|
double t1;
|
||||||
|
double t2;
|
||||||
|
size_t total_compressed_size = 0;
|
||||||
|
size_t hcount = 0;
|
||||||
|
size_t vcount = 0;
|
||||||
|
size_t i;
|
||||||
|
byte *uncompressed_pvs;
|
||||||
|
byte *uncompressed_phs;
|
||||||
|
|
||||||
|
if( !mod->visdata )
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if defined( HAVE_OPENMP )
|
||||||
|
Con_Reportf( "Building PHS in %d threads...\n", omp_get_max_threads( ));
|
||||||
|
#else
|
||||||
|
Con_Reportf( "Building PHS...\n" );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uncompressed_pvs = Mem_Calloc( mod->mempool, rowbytes * count * 2 );
|
||||||
|
uncompressed_phs = &uncompressed_pvs[rowbytes * count];
|
||||||
|
|
||||||
|
world.phsofs = Mem_Calloc( mod->mempool, sizeof( size_t ) * count );
|
||||||
|
world.compressed_phs = NULL;
|
||||||
|
|
||||||
|
t1 = Platform_DoubleTime();
|
||||||
|
|
||||||
|
#pragma omp parallel
|
||||||
|
{
|
||||||
|
// uncompress pvs first
|
||||||
|
#pragma omp for schedule( static, 256 ) // there might be thousands of leafs, split by 256
|
||||||
|
for( i = 0; i < count; i++ )
|
||||||
|
Mod_DecompressPVSTo( &uncompressed_pvs[rowbytes * i], mod->leafs[i].compressed_vis, world.visbytes );
|
||||||
|
|
||||||
|
// now create phs
|
||||||
|
#pragma omp for schedule( static, 256 ) reduction( + : vcount, hcount )
|
||||||
|
for( i = 0; i < count; i++ )
|
||||||
|
{
|
||||||
|
const byte *scan = &uncompressed_pvs[rowbytes * i];
|
||||||
|
byte *dst = &uncompressed_phs[rowbytes * i]; // rowbytes, not rowwords!
|
||||||
|
size_t j;
|
||||||
|
|
||||||
|
memcpy( dst, scan, rowbytes );
|
||||||
|
|
||||||
|
for( j = 0; j < rowbytes; j++ )
|
||||||
|
{
|
||||||
|
size_t k;
|
||||||
|
uint bitbyte = scan[j];
|
||||||
|
|
||||||
|
if( bitbyte == 0 )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for( k = 0; k < 8; k++ )
|
||||||
|
{
|
||||||
|
size_t index;
|
||||||
|
|
||||||
|
if( !FBitSet( bitbyte, BIT( k )))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// OR this pvs row into the phs
|
||||||
|
// +1 because pvs is 1 based
|
||||||
|
index = (( j * 8 ) + k + 1 );
|
||||||
|
if( index >= count )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Q_memor( dst, &uncompressed_pvs[rowbytes * index], rowbytes );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( vis_stats && i != 0 )
|
||||||
|
{
|
||||||
|
size_t j;
|
||||||
|
|
||||||
|
for( j = 0; j < count; j++ )
|
||||||
|
{
|
||||||
|
if( CHECKVISBIT( scan, j ))
|
||||||
|
vcount++;
|
||||||
|
|
||||||
|
if( CHECKVISBIT( dst, j ))
|
||||||
|
hcount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// since I can't predict at which spot compressed array
|
||||||
|
// should be put, this loop is single threaded
|
||||||
|
for( i = 0; i < count; i++ )
|
||||||
|
{
|
||||||
|
const byte *src = &uncompressed_phs[rowbytes * i];
|
||||||
|
byte temp_compressed_row[(MAX_MAP_LEAFS+1)/4]; // compression for this row might be ineffective
|
||||||
|
size_t compressed_size;
|
||||||
|
|
||||||
|
compressed_size = Mod_CompressPVS( temp_compressed_row, src, rowbytes );
|
||||||
|
|
||||||
|
world.compressed_phs = Mem_Realloc( mod->mempool, world.compressed_phs, total_compressed_size + compressed_size );
|
||||||
|
memcpy( &world.compressed_phs[total_compressed_size], temp_compressed_row, compressed_size );
|
||||||
|
world.phsofs[i] = total_compressed_size;
|
||||||
|
|
||||||
|
total_compressed_size += compressed_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
t2 = Platform_DoubleTime();
|
||||||
|
|
||||||
|
if( vis_stats )
|
||||||
|
Con_Reportf( "Average leaves visible / audible / total: %i / %i / %i\n", vcount / count, hcount / count, count );
|
||||||
|
Con_Reportf( "Uncompressed PHS size: %s\n", Q_memprint( rowbytes * count ));
|
||||||
|
Con_Reportf( "Compressed PHS size: %s\n", Q_memprint( total_compressed_size + sizeof( *world.phsofs ) * count ));
|
||||||
|
Con_Reportf( "PHS building time: %.2f ms\n", ( t2 - t1 ) * 1000.0f );
|
||||||
|
|
||||||
|
// TODO: rewrite this into a unit test
|
||||||
|
// NOTE: how to get GoldSrc fat PHS and PVS data
|
||||||
|
// start a multiplayer server with some op4_bootcamp (for example)
|
||||||
|
// attach to process with GDB:
|
||||||
|
// (gdb) p gPAS[0]
|
||||||
|
// $0 = (byte *) ...
|
||||||
|
// (gdb) p gPAS[gPVSRowBytes * (cl.worldmodel->numleafs + 1)]
|
||||||
|
// $1 = (byte *) ...
|
||||||
|
// (gdb) dump binary memory op4_bootcamp_gs.phs $0 $1
|
||||||
|
// (gdb) p gPVS[0]
|
||||||
|
// $2 = (byte *) ...
|
||||||
|
// (gdb) p gPVS[gPVSRowBytes * (cl.worldmodel->numleafs + 1)]
|
||||||
|
// $3 = (byte *) ...
|
||||||
|
// (gdb) dump binary memory op4_bootcamp_gs.pvs $0 $1
|
||||||
|
//
|
||||||
|
// NOTE: as of writing, uncompressed PVS and PHS data do match! hooray!
|
||||||
|
//
|
||||||
|
// FS_WriteFile( "op4_bootcamp.pvs", uncompressed_pvs, rowbytes * count );
|
||||||
|
// FS_WriteFile( "op4_bootcamp.phs", uncompressed_phs, rowbytes * count );
|
||||||
|
|
||||||
|
// release uncompressed data
|
||||||
|
Mem_Free( uncompressed_pvs );
|
||||||
|
|
||||||
|
// TODO: cache the PHS somewhere, it might take a long time on giant maps
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
=================
|
=================
|
||||||
Mod_LoadClipnodes
|
Mod_LoadClipnodes
|
||||||
|
@ -3022,6 +3190,9 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, const byte *mod_base, qboolea
|
||||||
world.deluxedata = bmod->deluxedata_out; // deluxemap data pointer
|
world.deluxedata = bmod->deluxedata_out; // deluxemap data pointer
|
||||||
world.shadowdata = bmod->shadowdata_out; // occlusion data pointer
|
world.shadowdata = bmod->shadowdata_out; // occlusion data pointer
|
||||||
#endif // XASH_DEDICATED
|
#endif // XASH_DEDICATED
|
||||||
|
|
||||||
|
if( SV_Active() && svs.maxclients > 1 )
|
||||||
|
Mod_CalcPHS( mod );
|
||||||
}
|
}
|
||||||
|
|
||||||
for( i = 0; i < bmod->wadlist.count; i++ )
|
for( i = 0; i < bmod->wadlist.count; i++ )
|
||||||
|
|
|
@ -33,7 +33,7 @@ GNU General Public License for more details.
|
||||||
|
|
||||||
#define REFPVS_RADIUS 2.0f // radius for rendering
|
#define REFPVS_RADIUS 2.0f // radius for rendering
|
||||||
#define FATPVS_RADIUS 8.0f // FatPVS use radius smaller than the FatPHS
|
#define FATPVS_RADIUS 8.0f // FatPVS use radius smaller than the FatPHS
|
||||||
#define FATPHS_RADIUS 16.0f
|
#define FATPHS_RADIUS 8.0f // see SV_AddToFatPAS in GoldSrc
|
||||||
|
|
||||||
#define WORLD_INDEX (1) // world index is always 1
|
#define WORLD_INDEX (1) // world index is always 1
|
||||||
|
|
||||||
|
@ -115,6 +115,10 @@ typedef struct world_static_s
|
||||||
int max_recursion;
|
int max_recursion;
|
||||||
|
|
||||||
uint32_t version; // BSP version
|
uint32_t version; // BSP version
|
||||||
|
|
||||||
|
// Potentially Hearable Set
|
||||||
|
byte *compressed_phs;
|
||||||
|
size_t *phsofs;
|
||||||
} world_static_t;
|
} world_static_t;
|
||||||
|
|
||||||
#ifndef REF_DLL
|
#ifndef REF_DLL
|
||||||
|
|
|
@ -117,6 +117,8 @@ void Mod_FreeModel( model_t *mod )
|
||||||
|
|
||||||
// data already freed by Mem_FreePool above
|
// data already freed by Mem_FreePool above
|
||||||
world.hull_models = NULL;
|
world.hull_models = NULL;
|
||||||
|
world.compressed_phs = NULL;
|
||||||
|
world.phsofs = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset( mod, 0, sizeof( *mod ));
|
memset( mod, 0, sizeof( *mod ));
|
||||||
|
|
Loading…
Add table
Reference in a new issue